M5: an exported appearance leaves the source page out (comment.export-fdf-xfdf)

A page or page tree reached from a comment's appearance is written as
NULL in <appearance>, as FDF export already nulls source /Page and
/Pages dictionaries, so the XFDF never carries the document's pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
This commit is contained in:
mtlcl410andClaude Opus 5.5 committed 2026-11-08 17:46:31 +08:00
1 parent a90502f2c9
commit 6ae0527883
2 files changed
+23

No files matched your search

+6
View File
@@ -120,6 +120,12 @@ impl Writer<'_> {
}
self.out.push_str("</ARRAY>\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!(
"<NULL{k}/>
"
)),
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.
+17
View File
@@ -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("<appearance>").nth(1).unwrap().split("</appearance>").next().unwrap().split_whitespace().collect();
let inner = String::from_utf8(base64::engine::general_purpose::STANDARD.decode(b64).unwrap()).unwrap();
assert!(inner.contains(r#"<NULL KEY="PieceInfo"/>"#), "{inner}");
assert!(!inner.contains("Kids") && !inner.contains("MediaBox"));
}
#[test]
fn fdf_carries_appearances_with_their_resources() {
let mut src = pages(1);