diff --git a/crates/xfdf/src/appearance.rs b/crates/xfdf/src/appearance.rs index d8cbddb4ad..80105c715c 100644 --- a/crates/xfdf/src/appearance.rs +++ b/crates/xfdf/src/appearance.rs @@ -120,6 +120,12 @@ impl Writer<'_> { } self.out.push_str("\n"); } + // A page (or the page tree) reached from the appearance is the source document's, not + // the comment's: written in place it would carry every page along. + Object::Dict(d) if matches!(d.name(b"Type"), Some(b"Page" | b"Pages")) => self.out.push_str(&format!( + " +" + )), Object::Dict(d) => self.dict(key, d, depth)?, Object::Stream(s) => { // The data plus its hexadecimal form must fit what is left of the budget. diff --git a/crates/xfdf/src/tests_format.rs b/crates/xfdf/src/tests_format.rs index 83e91983e2..da0aa932f3 100644 --- a/crates/xfdf/src/tests_format.rs +++ b/crates/xfdf/src/tests_format.rs @@ -567,6 +567,23 @@ fn appearance_cycles_and_strings_survive_export() { assert!(sub_dict(&dst, &res, b"Pattern").is_empty()); } +#[test] +fn an_appearance_that_names_its_page_does_not_carry_the_page_along() { + let mut src = pages(1); + image_stamp(&mut src, "pg", "Stamp"); + let d = named(&src, "pg"); + let n = sub_dict(&src, &d, b"AP").get(b"N").and_then(Object::as_ref).unwrap(); + let page = page_ref(&src, 0); + let Object::Stream(mut form) = (*src.get(n)).clone() else { panic!("no form") }; + form.dict.set(b"PieceInfo".to_vec(), Object::Ref(page)); + src.set(n, Object::Stream(form)); + let xml = export_xfdf(&src, true, false, "src.pdf"); + let b64: String = xml.split("").nth(1).unwrap().split("").next().unwrap().split_whitespace().collect(); + let inner = String::from_utf8(base64::engine::general_purpose::STANDARD.decode(b64).unwrap()).unwrap(); + assert!(inner.contains(r#""#), "{inner}"); + assert!(!inner.contains("Kids") && !inner.contains("MediaBox")); +} + #[test] fn fdf_carries_appearances_with_their_resources() { let mut src = pages(1);