Extract/Combine: a page repeated in one selection gets its own annotations (#818) (#852)

This commit is contained in:
MarekSpodBiedry authored and GitHub committed 2026-10-10 15:47:45 -04:00
1 parent c933782039
commit c49148a290
2 files changed
+39 -2

No files matched your search

+10 -2
View File
@@ -237,8 +237,16 @@ fn import_pages_mapped(dst: &mut Document, src: &Document, src_pages: &[usize],
let mut new_pages = Vec::new();
let mut done: HashMap<ObjRef, ObjRef> = HashMap::new();
for (page, new, inherited) in targets {
// A page listed twice: make a second, independent page object sharing resources.
let new = if done.contains_key(&page) { dst.add(Object::Null) } else { new };
// A page listed twice: make a second, independent page object sharing resources. Its
// annotations (with their popups and form fields) are copied again, so each page owns its
// copies and their `/P` names it, as Duplicate Pages does (#818).
let new = if done.contains_key(&page) {
let copies: std::collections::HashSet<ObjRef> = copier.annots.iter().chain(&copier.fields).copied().collect();
copier.map.retain(|_, &mut d| !copies.contains(&d));
dst.add(Object::Null)
} else {
new
};
let src_dict = src.get(page).as_dict().cloned().unwrap_or_default();
let mut d = Dict::new();
for (k, v) in src_dict.iter() {
+29
View File
@@ -443,6 +443,35 @@ fn links_are_rewired_to_copied_pages_or_dropped() {
assert!(!link.contains(b"Dest") && !link.contains(b"A"));
}
#[test]
fn repeated_page_gets_independent_annotations() {
// A1 has a link, A2 a link and a form field, A3 a note with a popup; each is listed twice.
let out = full_roundtrip(&extract_pages(&doc_a(), &[0, 0, 1, 1, 2, 2]).unwrap());
let ps = pages(&out).unwrap();
let refs: Vec<Vec<ObjRef>> = (0..6)
.map(|i| page_dict(&out, i).get(b"Annots").map(|a| out.resolve(a).as_array().cloned().unwrap_or_default()).unwrap_or_default())
.map(|list| list.iter().filter_map(|a| a.as_ref()).collect())
.collect();
for i in (0..6).step_by(2) {
assert!(!refs[i].is_empty());
assert_eq!(refs[i].len(), refs[i + 1].len());
for (a, b) in refs[i].iter().zip(&refs[i + 1]) {
assert_ne!(a, b, "each copy of the page has its own annotation objects");
}
}
for (i, list) in refs.iter().enumerate() {
for a in list {
assert_eq!(out.get(*a).as_dict().unwrap().reference(b"P"), Some(ps[i].obj), "/P names the page holding the copy");
}
}
// Each note keeps its own popup, pointing back at that note.
let popups: Vec<ObjRef> = [4, 5].iter().map(|&i| out.get(refs[i][0]).as_dict().unwrap().reference(b"Popup").unwrap()).collect();
assert_ne!(popups[0], popups[1]);
for (k, p) in popups.iter().enumerate() {
assert_eq!(out.get(*p).as_dict().unwrap().reference(b"Parent"), Some(refs[4 + k][0]));
}
}
#[test]
fn form_fields_come_along_and_are_registered() {
let out = full_roundtrip(&extract_pages(&doc_a(), &[1]).unwrap());