diff options
| author | Eric Biedert <github@ericbiedert.de> | 2024-07-14 16:02:50 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-14 14:02:50 +0000 |
| commit | ac322e342b4736bea189f5e7ac39561c0e5a3127 (patch) | |
| tree | 746e2b227b4be9d4efd8326cc198c7fa37100f55 | |
| parent | 17ee3df1ba99183fc074e91dfba3e9189dae1c0c (diff) | |
Save and restore graphics state for every frame (#4496)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
| -rw-r--r-- | crates/typst-pdf/src/content.rs | 53 | ||||
| -rw-r--r-- | tests/ref/issue-4361-transparency-leak.png | bin | 0 -> 3515 bytes | |||
| -rw-r--r-- | tests/suite/visualize/color.typ | 7 |
3 files changed, 35 insertions, 25 deletions
diff --git a/crates/typst-pdf/src/content.rs b/crates/typst-pdf/src/content.rs index 8ae2c424..da9e4ed4 100644 --- a/crates/typst-pdf/src/content.rs +++ b/crates/typst-pdf/src/content.rs @@ -92,7 +92,7 @@ pub struct Builder<'a, R = ()> { state: State, /// Stack of saved graphic states. saves: Vec<State>, - /// Wheter any stroke or fill was not totally opaque. + /// Whether any stroke or fill was not totally opaque. uses_opacities: bool, /// All clickable links that are present in this content. links: Vec<(Destination, Rect)>, @@ -129,7 +129,7 @@ struct State { /// The color space of the current fill paint. fill_space: Option<Name<'static>>, /// The current external graphic state. - external_graphics_state: Option<ExtGState>, + external_graphics_state: ExtGState, /// The current stroke paint. stroke: Option<FixedStroke>, /// The color space of the current stroke paint. @@ -148,7 +148,7 @@ impl State { font: None, fill: None, fill_space: None, - external_graphics_state: None, + external_graphics_state: ExtGState::default(), stroke: None, stroke_space: None, text_rendering_mode: TextRenderingMode::Fill, @@ -191,12 +191,13 @@ impl Builder<'_, ()> { } fn set_external_graphics_state(&mut self, graphics_state: &ExtGState) { - let current_state = self.state.external_graphics_state.as_ref(); - if current_state != Some(graphics_state) { + let current_state = &self.state.external_graphics_state; + if current_state != graphics_state { let index = self.resources.ext_gs.insert(*graphics_state); let name = eco_format!("Gs{index}"); self.content.set_parameters(Name(name.as_bytes())); + self.state.external_graphics_state = *graphics_state; if graphics_state.uses_opacities() { self.uses_opacities = true; } @@ -204,29 +205,27 @@ impl Builder<'_, ()> { } fn set_opacities(&mut self, stroke: Option<&FixedStroke>, fill: Option<&Paint>) { - let stroke_opacity = stroke - .map(|stroke| { - let color = match &stroke.paint { - Paint::Solid(color) => *color, - Paint::Gradient(_) | Paint::Pattern(_) => return 255, - }; - - color.alpha().map_or(255, |v| (v * 255.0).round() as u8) - }) - .unwrap_or(255); - let fill_opacity = fill - .map(|paint| { - let color = match paint { - Paint::Solid(color) => *color, - Paint::Gradient(_) | Paint::Pattern(_) => return 255, - }; - - color.alpha().map_or(255, |v| (v * 255.0).round() as u8) - }) - .unwrap_or(255); + let get_opacity = |paint: &Paint| { + let color = match paint { + Paint::Solid(color) => *color, + Paint::Gradient(_) | Paint::Pattern(_) => return 255, + }; + + color.alpha().map_or(255, |v| (v * 255.0).round() as u8) + }; + + let stroke_opacity = stroke.map_or(255, |stroke| get_opacity(&stroke.paint)); + let fill_opacity = fill.map_or(255, get_opacity); self.set_external_graphics_state(&ExtGState { stroke_opacity, fill_opacity }); } + fn reset_opacities(&mut self) { + self.set_external_graphics_state(&ExtGState { + stroke_opacity: 255, + fill_opacity: 255, + }); + } + pub fn transform(&mut self, transform: Transform) { let Transform { sx, ky, kx, sy, tx, ty } = transform; self.state.transform = self.state.transform.pre_concat(transform); @@ -542,6 +541,8 @@ fn write_color_glyphs(ctx: &mut Builder, pos: Point, text: TextItemView) { let mut last_font = None; + ctx.reset_opacities(); + ctx.content.begin_text(); ctx.content.set_text_matrix([1.0, 0.0, 0.0, -1.0, x, y]); // So that the next call to ctx.set_font() will change the font to one that @@ -671,6 +672,8 @@ fn write_image(ctx: &mut Builder, x: f32, y: f32, image: &Image, size: Size) { image }); + ctx.reset_opacities(); + let name = eco_format!("Im{index}"); let w = size.x.to_f32(); let h = size.y.to_f32(); diff --git a/tests/ref/issue-4361-transparency-leak.png b/tests/ref/issue-4361-transparency-leak.png Binary files differnew file mode 100644 index 00000000..4060d43a --- /dev/null +++ b/tests/ref/issue-4361-transparency-leak.png diff --git a/tests/suite/visualize/color.typ b/tests/suite/visualize/color.typ index 45000ab2..bc8f8be5 100644 --- a/tests/suite/visualize/color.typ +++ b/tests/suite/visualize/color.typ @@ -333,3 +333,10 @@ --- issue-color-mix-luma --- // When mixing luma colors, we accidentally used the wrong component. #rect(fill: gradient.linear(black, silver, space: luma)) + +--- issue-4361-transparency-leak --- +// Ensure that transparency doesn't leak from shapes to images in PDF. The PNG +// test doesn't validate it, but at least we can discover regressions on the PDF +// output with a PDF comparison script. +#rect(fill: red.transparentize(50%)) +#image("/assets/images/tiger.jpg", width: 45pt) |
