diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 7c21f6c30b..d553704a71 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -1986,6 +1986,47 @@ trailer << /Root 1 0 R >> assert_ne!(second_pixel, [255, 255, 255, 255]); } + #[test] + fn shared_soft_mask_uses_each_inherited_resource_context() { + let page_body = "/A Do /B Do"; + let form_a = "/GS gs 1 0 0 rg 0 0 40 40 re f"; + let form_b = "/GS gs 0 0 1 rg 60 0 40 40 re f"; + let mask_body = "/FILL Do"; + let white = "1 g 0 0 100 100 re f"; + let black = "0 g 0 0 100 100 re f"; + let pdf = format!( + "%PDF-1.7\n\ + 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n\ + 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 100 100] >> endobj\n\ + 3 0 obj << /Type /Page /Parent 2 0 R /Resources << /XObject << /A 4 0 R /B 5 0 R >> >> /Contents 6 0 R >> endobj\n\ + 4 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 100 100] /Resources << /ExtGState << /GS 8 0 R >> /XObject << /FILL 9 0 R >> >> /Length {} >> stream\n{form_a}\nendstream endobj\n\ + 5 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 100 100] /Resources << /ExtGState << /GS 8 0 R >> /XObject << /FILL 10 0 R >> >> /Length {} >> stream\n{form_b}\nendstream endobj\n\ + 6 0 obj << /Length {} >> stream\n{page_body}\nendstream endobj\n\ + 8 0 obj << /Type /ExtGState /SMask 11 0 R >> endobj\n\ + 9 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 100 100] /Length {} >> stream\n{white}\nendstream endobj\n\ + 10 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 100 100] /Length {} >> stream\n{black}\nendstream endobj\n\ + 11 0 obj << /Type /Mask /S /Luminosity /G 12 0 R >> endobj\n\ + 12 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 100 100] /Group << /S /Transparency /CS /DeviceGray >> /Length {} >> stream\n{mask_body}\nendstream endobj\n\ + trailer << /Root 1 0 R >>\n%%EOF", + form_a.len(), + form_b.len(), + page_body.len(), + white.len(), + black.len(), + mask_body.len() + ) + .into_bytes(); + let mut renderer = PageRenderer::new(Arc::new(pdf), RenderConfig::default()); + let page = renderer.render(RenderRequest { page: 0, scale: 1.0, ..Default::default() }); + assert!(page.error.is_none(), "{:?}", page.error); + let pixel = |x: u32, y: u32| { + let offset = ((y * page.width + x) * 4) as usize; + [page.rgba[offset], page.rgba[offset + 1], page.rgba[offset + 2], page.rgba[offset + 3]] + }; + assert_eq!(pixel(20, 80), [255, 0, 0, 255], "the white inherited mask makes form A visible"); + assert_eq!(pixel(80, 80), [255, 255, 255, 255], "the black inherited mask must not reuse form A's cached pixels"); + } + #[test] fn renders_a_blue_rectangle() { let pool = RenderPool::new(Arc::new(ONE_PAGE.to_vec()), 1, RenderConfig::default()); diff --git a/vendor/hayro-interpret/src/soft_mask.rs b/vendor/hayro-interpret/src/soft_mask.rs index 048035e7e1..4d97d565b5 100644 --- a/vendor/hayro-interpret/src/soft_mask.rs +++ b/vendor/hayro-interpret/src/soft_mask.rs @@ -48,6 +48,7 @@ impl TransferFunction { } struct Repr<'a> { + cache_key: u128, obj_id: ObjectIdentifier, group: FormXObject<'a>, mask_type: MaskType, @@ -65,7 +66,6 @@ struct Repr<'a> { impl Hash for Repr<'_> { fn hash(&self, state: &mut H) { self.obj_id.hash(state); - self.root_transform.cache_key().hash(state); } } @@ -89,7 +89,7 @@ impl Eq for SoftMask<'_> {} impl CacheKey for SoftMask<'_> { fn cache_key(&self) -> u128 { - hash128(self) + self.0.cache_key } } @@ -134,12 +134,23 @@ impl<'a> SoftMask<'a> { _ => return None, }; let nesting_depth = context.nesting_depth() + 1; + let root_transform = context.get().ctm; + let bbox = context.bbox(); + let resources_key = resources_cache_key(&parent_resources); + let cache_key = hash128(&( + dict.cache_key(), + root_transform.cache_key(), + [bbox.x0.to_bits(), bbox.y0.to_bits(), bbox.x1.to_bits(), bbox.y1.to_bits()], + resources_key, + nesting_depth, + )); Some(Self(Rc::new(Repr { + cache_key, obj_id, group, mask_type, - root_transform: context.get().ctm, + root_transform, transfer_function, bbox: context.bbox(), interpreter_cache: context.interpreter_cache.clone(), @@ -188,3 +199,16 @@ impl<'a> SoftMask<'a> { self.0.transfer_function.as_ref() } } + +fn resources_cache_key(resources: &Resources<'_>) -> u128 { + let local = ( + resources.ext_g_states.obj_id(), + resources.fonts.obj_id(), + resources.properties.obj_id(), + resources.color_spaces.obj_id(), + resources.x_objects.obj_id(), + resources.patterns.obj_id(), + resources.shadings.obj_id(), + ); + hash128(&(local, resources.parent().map(resources_cache_key))) +}