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 13185bec7..fd6bf5c34 100644 Binary files a/tests/ref/visualize/gradient-math.png and b/tests/ref/visualize/gradient-math.png differ 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 +) $