Save and restore graphics state for every frame (#4496)

Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
Eric Biedert 2024-07-14 16:02:50 +02:00 committed by GitHub
parent 17ee3df1ba
commit ac322e342b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 33 additions and 23 deletions

View File

@ -92,7 +92,7 @@ pub struct Builder<'a, R = ()> {
state: State, state: State,
/// Stack of saved graphic states. /// Stack of saved graphic states.
saves: Vec<State>, saves: Vec<State>,
/// Wheter any stroke or fill was not totally opaque. /// Whether any stroke or fill was not totally opaque.
uses_opacities: bool, uses_opacities: bool,
/// All clickable links that are present in this content. /// All clickable links that are present in this content.
links: Vec<(Destination, Rect)>, links: Vec<(Destination, Rect)>,
@ -129,7 +129,7 @@ struct State {
/// The color space of the current fill paint. /// The color space of the current fill paint.
fill_space: Option<Name<'static>>, fill_space: Option<Name<'static>>,
/// The current external graphic state. /// The current external graphic state.
external_graphics_state: Option<ExtGState>, external_graphics_state: ExtGState,
/// The current stroke paint. /// The current stroke paint.
stroke: Option<FixedStroke>, stroke: Option<FixedStroke>,
/// The color space of the current stroke paint. /// The color space of the current stroke paint.
@ -148,7 +148,7 @@ impl State {
font: None, font: None,
fill: None, fill: None,
fill_space: None, fill_space: None,
external_graphics_state: None, external_graphics_state: ExtGState::default(),
stroke: None, stroke: None,
stroke_space: None, stroke_space: None,
text_rendering_mode: TextRenderingMode::Fill, text_rendering_mode: TextRenderingMode::Fill,
@ -191,12 +191,13 @@ impl Builder<'_, ()> {
} }
fn set_external_graphics_state(&mut self, graphics_state: &ExtGState) { fn set_external_graphics_state(&mut self, graphics_state: &ExtGState) {
let current_state = self.state.external_graphics_state.as_ref(); let current_state = &self.state.external_graphics_state;
if current_state != Some(graphics_state) { if current_state != graphics_state {
let index = self.resources.ext_gs.insert(*graphics_state); let index = self.resources.ext_gs.insert(*graphics_state);
let name = eco_format!("Gs{index}"); let name = eco_format!("Gs{index}");
self.content.set_parameters(Name(name.as_bytes())); self.content.set_parameters(Name(name.as_bytes()));
self.state.external_graphics_state = *graphics_state;
if graphics_state.uses_opacities() { if graphics_state.uses_opacities() {
self.uses_opacities = true; self.uses_opacities = true;
} }
@ -204,29 +205,27 @@ impl Builder<'_, ()> {
} }
fn set_opacities(&mut self, stroke: Option<&FixedStroke>, fill: Option<&Paint>) { fn set_opacities(&mut self, stroke: Option<&FixedStroke>, fill: Option<&Paint>) {
let stroke_opacity = stroke let get_opacity = |paint: &Paint| {
.map(|stroke| { let color = match paint {
let color = match &stroke.paint { Paint::Solid(color) => *color,
Paint::Solid(color) => *color, Paint::Gradient(_) | Paint::Pattern(_) => return 255,
Paint::Gradient(_) | Paint::Pattern(_) => return 255, };
};
color.alpha().map_or(255, |v| (v * 255.0).round() as u8) 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) let stroke_opacity = stroke.map_or(255, |stroke| get_opacity(&stroke.paint));
}) let fill_opacity = fill.map_or(255, get_opacity);
.unwrap_or(255);
self.set_external_graphics_state(&ExtGState { stroke_opacity, fill_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) { pub fn transform(&mut self, transform: Transform) {
let Transform { sx, ky, kx, sy, tx, ty } = transform; let Transform { sx, ky, kx, sy, tx, ty } = transform;
self.state.transform = self.state.transform.pre_concat(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; let mut last_font = None;
ctx.reset_opacities();
ctx.content.begin_text(); ctx.content.begin_text();
ctx.content.set_text_matrix([1.0, 0.0, 0.0, -1.0, x, y]); 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 // 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 image
}); });
ctx.reset_opacities();
let name = eco_format!("Im{index}"); let name = eco_format!("Im{index}");
let w = size.x.to_f32(); let w = size.x.to_f32();
let h = size.y.to_f32(); let h = size.y.to_f32();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -333,3 +333,10 @@
--- issue-color-mix-luma --- --- issue-color-mix-luma ---
// When mixing luma colors, we accidentally used the wrong component. // When mixing luma colors, we accidentally used the wrong component.
#rect(fill: gradient.linear(black, silver, space: luma)) #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)