mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-31 21:48:09 -06:00
fix(render): key soft masks by inherited context (#716)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Brandon Thomas
parent
72b3b6171a
commit
a211e334a0
@@ -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());
|
||||
|
||||
+27
-3
@@ -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<H: Hasher>(&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)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user