address review: count a reply reached by a /Parent link first

This commit is contained in:
Dylan M. Taylor
2026-11-03 23:03:59 +00:00
parent db862cf4d0
commit c7cc760272
2 changed files with 28 additions and 1 deletions
+7 -1
View File
@@ -458,12 +458,18 @@ fn apply_within(doc: &mut Document, pages: Option<&[usize]>, images: pdfcraft_co
let pages = pdfcraft_model::pages(doc);
let hanging = hanging_annotations(doc, pages.iter().flat_map(|p| annots_of(doc, &p.dict)));
let mut todo: Vec<ObjRef> = removed_everywhere.iter().copied().collect();
// (a reply is counted once, by whichever link reaches it: one with both `/IRT` and `/Parent`
// is a reply however the walk orders them)
let (mut reached, mut counted) = (HashSet::new(), HashSet::new());
while let Some(parent) = todo.pop() {
for &(r, reply) in hanging.get(&parent).into_iter().flatten() {
if removed_everywhere.insert(r) {
report.annotations += usize::from(reply);
reached.insert(r);
todo.push(r);
}
if reply && reached.contains(&r) && counted.insert(r) {
report.annotations += 1;
}
}
}
for p in &pages {
+21
View File
@@ -3872,6 +3872,27 @@ fn a_reply_on_another_page_to_a_removed_popup_goes_too() {
}
}
#[test]
fn a_reply_with_a_parent_link_is_counted_whatever_order_the_walk_takes() {
// 7 hangs off 6 (/Parent) and off 8 (/IRT): the walk can reach it by its /Parent link first
let objs: Vec<Vec<u8>> = vec![
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
b"<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>".to_vec(),
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 300] /Contents 5 0 R /Annots [6 0 R 8 0 R] >>".to_vec(),
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 300] /Contents 5 0 R /Annots [7 0 R] >>".to_vec(),
stream("", b"BT ET"),
b"<< /Type /Annot /Subtype /Text /Rect [20 20 40 40] /Contents (PARENT-NOTE) /Popup 8 0 R >>".to_vec(),
b"<< /Type /Annot /Subtype /Text /Rect [250 250 270 270] /Parent 6 0 R /IRT 8 0 R /Contents (BOTH-LINKS) >>".to_vec(),
b"<< /Type /Annot /Subtype /Popup /Rect [200 150 290 190] /Parent 6 0 R >>".to_vec(),
];
let mut doc = pdf(objs);
mark(&mut doc, 0, &[[10.0, 10.0, 70.0, 50.0]], "");
let report = apply(&mut doc, None).unwrap();
let second = pdfcraft_model::pages(&doc).swap_remove(1);
assert!(annots_of(&doc, &second.dict).is_empty());
assert_eq!(report.annotations, 2, "the mark's own comment and the one reply: {report:?}");
}
#[test]
fn a_rewritten_form_xobject_drops_the_source_text_of_added_content() {
// A form drawing invisible text whose dictionary repeats it in /PCAdded.