From d7fea7077ebe4df44edff32b2b3a6e4a00775974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20d=27Herbais=20de=20Thun?= Date: Wed, 8 Nov 2023 15:51:25 +0100 Subject: [PATCH] Gradient Part 5c: Fix gradient rotation on text & math (#2606) --- crates/typst-pdf/src/color.rs | 15 ++--- crates/typst-pdf/src/gradient.rs | 90 ++++++++------------------ crates/typst-pdf/src/page.rs | 17 ++--- tests/ref/visualize/gradient-math.png | Bin 29775 -> 38030 bytes tests/typ/visualize/gradient-math.typ | 23 ++++++- 5 files changed, 64 insertions(+), 81 deletions(-) diff --git a/crates/typst-pdf/src/color.rs b/crates/typst-pdf/src/color.rs index 80d277edc..999f604e2 100644 --- a/crates/typst-pdf/src/color.rs +++ b/crates/typst-pdf/src/color.rs @@ -277,7 +277,7 @@ pub(super) trait PaintEncode { fn set_as_fill(&self, ctx: &mut PageContext, on_text: bool, transforms: Transforms); /// Set the paint as the stroke color. - fn set_as_stroke(&self, ctx: &mut PageContext, on_text: bool, transforms: Transforms); + fn set_as_stroke(&self, ctx: &mut PageContext, transforms: Transforms); } impl PaintEncode for Paint { @@ -288,15 +288,10 @@ impl PaintEncode for Paint { } } - fn set_as_stroke( - &self, - ctx: &mut PageContext, - on_text: bool, - transforms: Transforms, - ) { + fn set_as_stroke(&self, ctx: &mut PageContext, transforms: Transforms) { match self { - Self::Solid(c) => c.set_as_stroke(ctx, on_text, transforms), - Self::Gradient(gradient) => gradient.set_as_stroke(ctx, on_text, transforms), + Self::Solid(c) => c.set_as_stroke(ctx, transforms), + Self::Gradient(gradient) => gradient.set_as_stroke(ctx, transforms), } } } @@ -355,7 +350,7 @@ impl PaintEncode for Color { } } - fn set_as_stroke(&self, ctx: &mut PageContext, _: bool, _: Transforms) { + fn set_as_stroke(&self, ctx: &mut PageContext, _: Transforms) { match self { Color::Luma(_) => { ctx.parent.colors.d65_gray(&mut ctx.parent.alloc); diff --git a/crates/typst-pdf/src/gradient.rs b/crates/typst-pdf/src/gradient.rs index 37702cea8..3d5084155 100644 --- a/crates/typst-pdf/src/gradient.rs +++ b/crates/typst-pdf/src/gradient.rs @@ -26,21 +26,21 @@ pub struct PdfGradient { pub aspect_ratio: Ratio, /// The gradient. pub gradient: Gradient, - /// Whether the gradient is applied to text. - pub on_text: bool, + /// The corrected angle of the gradient. + pub angle: Angle, } /// Writes the actual gradients (shading patterns) to the PDF. /// This is performed once after writing all pages. pub(crate) fn write_gradients(ctx: &mut PdfContext) { - for PdfGradient { transform, aspect_ratio, gradient, on_text } in + for PdfGradient { transform, aspect_ratio, gradient, angle } in ctx.gradient_map.items().cloned().collect::>() { let shading = ctx.alloc.bump(); ctx.gradient_refs.push(shading); let mut shading_pattern = match &gradient { - Gradient::Linear(linear) => { + Gradient::Linear(_) => { let shading_function = shading_function(ctx, &gradient); let mut shading_pattern = ctx.pdf.shading_pattern(shading); let mut shading = shading_pattern.function_shading(); @@ -49,14 +49,24 @@ pub(crate) fn write_gradients(ctx: &mut PdfContext) { ctx.colors .write(gradient.space(), shading.color_space(), &mut ctx.alloc); - let angle = Gradient::correct_aspect_ratio(linear.angle, aspect_ratio); let (sin, cos) = (angle.sin(), angle.cos()); - let length = sin.abs() + cos.abs(); + let (x1, y1, x2, y2): (f64, f64, f64, f64) = match angle.quadrant() { + Quadrant::First => (0.0, 0.0, cos, sin), + Quadrant::Second => (1.0, 0.0, cos + 1.0, sin), + Quadrant::Third => (1.0, 1.0, cos + 1.0, sin + 1.0), + Quadrant::Fourth => (0.0, 1.0, cos, sin + 1.0), + }; + + let clamp = |i: f64| if i < 1e-4 { 0.0 } else { i.clamp(0.0, 1.0) }; + let x1 = clamp(x1); + let y1 = clamp(y1); + let x2 = clamp(x2); + let y2 = clamp(y2); shading .anti_alias(gradient.anti_alias()) .function(shading_function) - .coords([0.0, 0.0, length as f32, 0.0]) + .coords([x1 as f32, y1 as f32, x2 as f32, y2 as f32]) .extend([true; 2]); shading.finish(); @@ -90,7 +100,7 @@ pub(crate) fn write_gradients(ctx: &mut PdfContext) { shading_pattern } Gradient::Conic(conic) => { - let vertices = compute_vertex_stream(conic, aspect_ratio, on_text); + let vertices = compute_vertex_stream(conic, aspect_ratio); let stream_shading_id = ctx.alloc.bump(); let mut stream_shading = @@ -265,15 +275,10 @@ impl PaintEncode for Gradient { ctx.content.set_fill_pattern(None, name); } - fn set_as_stroke( - &self, - ctx: &mut PageContext, - on_text: bool, - transforms: Transforms, - ) { + fn set_as_stroke(&self, ctx: &mut PageContext, transforms: Transforms) { ctx.reset_stroke_color_space(); - let id = register_gradient(ctx, self, on_text, transforms); + let id = register_gradient(ctx, self, false, transforms); let name = Name(id.as_bytes()); ctx.content.set_stroke_color_space(ColorSpaceOperand::Pattern); @@ -296,33 +301,20 @@ fn register_gradient( if transforms.size.y.is_zero() { transforms.size.y = Abs::pt(1.0); } - let size = match gradient.unwrap_relative(on_text) { Relative::Self_ => transforms.size, Relative::Parent => transforms.container_size, }; - // Correction for y-axis flipping on text. - let angle = gradient.angle().unwrap_or_else(Angle::zero); - let angle = if on_text { Angle::rad(TAU as f64) - angle } else { angle }; - let (offset_x, offset_y) = match gradient { Gradient::Conic(conic) => ( -size.x * (1.0 - conic.center.x.get() / 2.0) / 2.0, -size.y * (1.0 - conic.center.y.get() / 2.0) / 2.0, ), - _ => match angle.quadrant() { - Quadrant::First => (Abs::zero(), Abs::zero()), - Quadrant::Second => (size.x, Abs::zero()), - Quadrant::Third => (size.x, size.y), - Quadrant::Fourth => (Abs::zero(), size.y), - }, + _ => (Abs::zero(), Abs::zero()), }; - let rotation = match gradient { - Gradient::Conic(_) => Angle::zero(), - _ => angle, - }; + let rotation = gradient.angle().unwrap_or_else(Angle::zero); let transform = match gradient.unwrap_relative(on_text) { Relative::Self_ => transforms.transform, @@ -344,13 +336,9 @@ fn register_gradient( .pre_concat(Transform::scale( Ratio::new(size.x.to_pt() * scale_offset), Ratio::new(size.y.to_pt() * scale_offset), - )) - .pre_concat(Transform::rotate(Gradient::correct_aspect_ratio( - rotation, - size.aspect_ratio(), - ))), + )), gradient: gradient.clone(), - on_text, + angle: Gradient::correct_aspect_ratio(rotation, size.aspect_ratio()), }; let index = ctx.parent.gradient_map.insert(pdf_gradient); @@ -383,16 +371,9 @@ fn write_patch( c0: [u16; 3], c1: [u16; 3], angle: Angle, - on_text: bool, ) { - let mut theta = -TAU * t + angle.to_rad() as f32 + PI; - let mut theta1 = -TAU * t1 + angle.to_rad() as f32 + PI; - - // Correction for y-axis flipping on text. - if on_text { - theta = (TAU - theta).rem_euclid(TAU); - theta1 = (TAU - theta1).rem_euclid(TAU); - } + let theta = -TAU * t + angle.to_rad() as f32 + PI; + let theta1 = -TAU * t1 + angle.to_rad() as f32 + PI; let (cp1, cp2) = control_point(Point::new(Abs::pt(0.5), Abs::pt(0.5)), 0.5, theta, theta1); @@ -453,11 +434,7 @@ fn control_point(c: Point, r: f32, angle_start: f32, angle_end: f32) -> (Point, } #[comemo::memoize] -fn compute_vertex_stream( - conic: &ConicGradient, - aspect_ratio: Ratio, - on_text: bool, -) -> Arc> { +fn compute_vertex_stream(conic: &ConicGradient, aspect_ratio: Ratio) -> Arc> { // Generated vertices for the Coons patches let mut vertices = Vec::new(); @@ -534,18 +511,9 @@ fn compute_vertex_stream( conic.space.convert(c), c0, angle, - on_text, ); - write_patch( - &mut vertices, - t_prime, - t_prime, - c0, - c1, - angle, - on_text, - ); + write_patch(&mut vertices, t_prime, t_prime, c0, c1, angle); write_patch( &mut vertices, @@ -554,7 +522,6 @@ fn compute_vertex_stream( c1, conic.space.convert(c_next), angle, - on_text, ); t_x = t_next; @@ -570,7 +537,6 @@ fn compute_vertex_stream( conic.space.convert(c), conic.space.convert(c_next), angle, - on_text, ); t_x = t_next; diff --git a/crates/typst-pdf/src/page.rs b/crates/typst-pdf/src/page.rs index c842a01f1..04640945a 100644 --- a/crates/typst-pdf/src/page.rs +++ b/crates/typst-pdf/src/page.rs @@ -322,7 +322,7 @@ impl State { } /// Subset of the state used to calculate the transform of gradients and patterns. -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub(super) struct Transforms { /// The transform of the current item. pub transform: Transform, @@ -385,6 +385,9 @@ impl PageContext<'_, '_> { fn transform(&mut self, transform: Transform) { let Transform { sx, ky, kx, sy, tx, ty } = transform; self.state.transform = self.state.transform.pre_concat(transform); + if self.state.container_transform.is_identity() { + self.state.container_transform = self.state.transform; + } self.content.transform([ sx.get() as _, ky.get() as _, @@ -449,7 +452,7 @@ impl PageContext<'_, '_> { miter_limit, } = stroke; - paint.set_as_stroke(self, false, transforms); + paint.set_as_stroke(self, transforms); self.content.set_line_width(thickness.to_f32()); if self.state.stroke.as_ref().map(|s| &s.line_cap) != Some(line_cap) { @@ -517,12 +520,10 @@ fn write_group(ctx: &mut PageContext, pos: Point, group: &GroupItem) { if group.frame.kind().is_hard() { ctx.group_transform( - translation - .pre_concat( - ctx.state - .transform - .post_concat(ctx.state.container_transform.invert().unwrap()), - ) + ctx.state + .transform + .post_concat(ctx.state.container_transform.invert().unwrap()) + .pre_concat(translation) .pre_concat(group.transform), ); ctx.size(group.frame.size()); diff --git a/tests/ref/visualize/gradient-math.png b/tests/ref/visualize/gradient-math.png index 13185bec7d419255b5f1c7d946b981b1a14d8365..fd6bf5c34b27593edf4b765be4e7cf1438d8ba56 100644 GIT binary patch delta 12668 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 delta 4487 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 C-*D;x diff --git a/tests/typ/visualize/gradient-math.typ b/tests/typ/visualize/gradient-math.typ index 2030ecbbb..03d7c477f 100644 --- a/tests/typ/visualize/gradient-math.typ +++ b/tests/typ/visualize/gradient-math.typ @@ -55,7 +55,6 @@ $ x_"1,2" = frac(-b +- sqrt(b^2 - 4 a c), 2 a) $ --- // Test miscelaneous - #show math.equation: set text(fill: gradient.linear(..color.map.rainbow)) #show math.equation: box @@ -66,3 +65,25 @@ $ attach( Pi, t: alpha, b: beta, tl: 1, tr: 2+3, bl: 4+5, br: 6, ) $ + +--- +// Test radial gradient +#show math.equation: set text(fill: gradient.radial(..color.map.rainbow, center: (30%, 30%))) +#show math.equation: box + +$ A = mat( + 1, 2, 3; + 4, 5, 6; + 7, 8, 9 +) $ + +--- +// Test conic gradient +#show math.equation: set text(fill: gradient.conic(red, blue, angle: 45deg)) +#show math.equation: box + +$ A = mat( + 1, 2, 3; + 4, 5, 6; + 7, 8, 9 +) $