From ce1cd804775ce51fcd8623d49de6c6139cb0850f Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Mon, 9 Nov 2026 05:18:44 -0500 Subject: [PATCH] fix(xfa): remember every picture hash written in a pass (#6035) --- crates/xfa/src/pdf.rs | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/crates/xfa/src/pdf.rs b/crates/xfa/src/pdf.rs index ab96abbb3f..b8377cee9f 100644 --- a/crates/xfa/src/pdf.rs +++ b/crates/xfa/src/pdf.rs @@ -65,7 +65,7 @@ pub const ROUND_KEY: &[u8] = b"PCXfaRound"; const IMAGE_SHA_KEY: &[u8] = b"PCXfaSHA256"; /// Most pages searched for earlier pictures. const MAX_PAGES_SEARCHED: usize = 10_000; -/// Most pictures remembered. +/// Most pictures remembered from earlier passes. const MAX_REMEMBERED_IMAGES: usize = 1000; struct Emitter<'a> { @@ -492,9 +492,9 @@ impl Emitter<'_> { return Some(r); } let r = self.new_image_xobject(data, content_type, &hash)?; - if self.images.len() < MAX_REMEMBERED_IMAGES { - self.images.insert(hash, r); - } + // Always remembered: each entry is a hash and a reference to an object this pass wrote + // anyway, and a picture past a cap would be decoded and embedded again at every use. + self.images.insert(hash, r); Some(r) } @@ -1040,7 +1040,7 @@ pub fn pdf_rect(r: Rect, page_height: f64) -> [f64; 4] { #[cfg(test)] mod tests { - use super::parse_hex32; + use super::*; #[test] fn parse_hex32_rejects_non_hex_digits() { @@ -1053,4 +1053,31 @@ mod tests { ok.resize(64, b'0'); assert_eq!(parse_hex32(&ok).map(|h| h[0]), Some(0xff)); } + + #[test] + fn a_picture_past_the_earlier_pass_cap_is_still_embedded_once() { + let mut doc = Document::new_empty(); + let mut em = Emitter { + doc: &mut doc, + images: HashMap::new(), + fields: Vec::new(), + radio_groups: HashMap::new(), + radio_selected: HashMap::new(), + field_count: 0, + warnings: Vec::new(), + signed: Default::default(), + taken: Default::default(), + nodes: HashMap::new(), + }; + let png = crate::fixtures::tiny_png(); + let picture = |i: u32| [png.clone(), i.to_be_bytes().to_vec()].concat(); + let last = MAX_REMEMBERED_IMAGES as u32 + 10; + for i in 0..=last { + em.image_xobject(&picture(i), "image/png").expect("decodes"); + } + let before = em.doc.object_numbers().len(); + let again = em.image_xobject(&picture(last), "image/png").expect("decodes"); + assert_eq!(em.doc.object_numbers().len(), before, "no new object for a repeated picture"); + assert_eq!(em.image_xobject(&picture(last), "image/png"), Some(again)); + } }