mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 21:11:52 +00:00
M5: a pop-up is its comment's when /Annots lists the comment through a reference to it (#2137)
Review on #2137: when /Annots holds 5 0 R and object 5 is only a reference to the comment 6 0 R, the pop-up's /Parent names 6 0 R, but the ownership check compared it with 5 0 R. The comment's pop-up was then missing from both the on-open listing (render inspect) and the summaries after an edit (annot). Both now compare the references they resolve to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
This commit is contained in:
1 parent
45f5a3c591
commit
65eb3257c5
4 files changed
+24
-4
No files matched your search
@@ -60,7 +60,10 @@ pub struct PopupInfo {
|
||||
pub(crate) fn own_popup(doc: &Document, own: Option<ObjRef>, annot: &Dict) -> Option<ObjRef> {
|
||||
let pr = annot.reference(b"Popup")?;
|
||||
let is_own = doc.get(pr).as_dict().is_some_and(|pd| {
|
||||
pd.name(b"Subtype") == Some(b"Popup") && pd.get(b"Parent").is_none_or(|parent| parent.as_ref().is_some_and(|parent| Some(parent) == own))
|
||||
pd.name(b"Subtype") == Some(b"Popup")
|
||||
&& pd
|
||||
.get(b"Parent")
|
||||
.is_none_or(|parent| parent.as_ref().is_some_and(|parent| own.is_some_and(|own| final_ref(doc, parent) == final_ref(doc, own))))
|
||||
});
|
||||
is_own.then_some(pr)
|
||||
}
|
||||
@@ -183,7 +186,7 @@ pub fn summaries(doc: &Document) -> Vec<Summary> {
|
||||
fill_sign: is_fill_sign(doc, d),
|
||||
no_rotate: drawn_upright(doc, d),
|
||||
points,
|
||||
popup: popup_info(doc, entry.as_ref(), d),
|
||||
popup: popup_info(doc, entry.as_ref().map(|r| final_ref(doc, r)), d),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2255,6 +2255,15 @@ fn pop_ups_open_close_and_move_and_are_reported() {
|
||||
assert!(matches!(set_popup(&mut doc, 0, note, Some([0.0, 0.0, 0.5, 0.5]), None, &meta("n1")), Err(AnnotError::Invalid(_))));
|
||||
assert!(matches!(set_popup(&mut doc, 0, note, Some([f64::NAN, 0.0, 10.0, 10.0]), None, &meta("n1")), Err(AnnotError::Invalid(_))));
|
||||
assert!(matches!(set_popup(&mut doc, 0, 99, None, Some(true), &meta("n1")), Err(AnnotError::NoSuchAnnotation { .. })));
|
||||
|
||||
// #2137 review: /Annots may list the note through an object that is only a reference to it.
|
||||
// Its pop-up's /Parent names the note itself, so the pop-up is still the note's.
|
||||
let p = page_ref(&doc, 0).unwrap();
|
||||
let via = doc.add(Object::Ref(r));
|
||||
let list: Vec<Object> = annots(&doc, p).into_iter().map(|o| if o.as_ref() == Some(r) { Object::Ref(via) } else { o }).collect();
|
||||
set_annots(&mut doc, p, list).unwrap();
|
||||
let listed = summaries(&doc).into_iter().find(|s| s.index == note).unwrap();
|
||||
assert_eq!(listed.popup, Some(PopupInfo { rect: [150.0, 100.0, 290.0, 200.0], open: false }), "listed through {via:?}");
|
||||
}
|
||||
|
||||
/// #2137 review: a pop-up that is not the mark's own is neither reported as its pop-up nor
|
||||
|
||||
@@ -160,10 +160,11 @@ impl<'a> Inspector<'a> {
|
||||
};
|
||||
// Only the comment's own pop-up: a pop-up annotation whose /Parent, if any, is a
|
||||
// reference to this comment (keep in step with `pdfcraft_annot::summaries`).
|
||||
let own = a.as_reference().ok();
|
||||
let own = self.resolved_id(a);
|
||||
let is_own = |p: &&Dictionary| {
|
||||
self.name(p, b"Subtype").as_deref() == Some("Popup")
|
||||
&& p.get(b"Parent").map_or(true, |parent| parent.as_reference().ok().is_some_and(|id| Some(id) == own))
|
||||
&& p.get(b"Parent")
|
||||
.map_or(true, |parent| parent.as_reference().is_ok() && self.resolved_id(parent).is_some_and(|id| Some(id) == own))
|
||||
};
|
||||
let popup = d.get(b"Popup").ok().and_then(|o| self.dict(o)).filter(is_own).map(|p| {
|
||||
let [x0, y0, x1, y1] = rect4(self.resolve(p.get(b"Rect").unwrap_or(&Object::Null)));
|
||||
|
||||
@@ -1875,6 +1875,13 @@ fn a_comments_pop_up_is_read_with_its_place_and_state() {
|
||||
let note = info.annotations.iter().find(|a| a.subtype == "Text").expect("the note");
|
||||
assert_eq!(note.popup.is_some(), own, "/Popup {target} {extra}");
|
||||
}
|
||||
|
||||
// #2137 review: /Annots may list the note through an object that is only a reference to
|
||||
// it; its pop-up's /Parent names the note itself, and is still its own.
|
||||
let text = String::from_utf8_lossy(bytes).replace("/Annots [4 0 R", "/Annots [8 0 R").replace("trailer", "8 0 obj 4 0 R endobj\ntrailer");
|
||||
let info = inspect(Arc::new(text.into_bytes()), None).expect("opens");
|
||||
let note = info.annotations.iter().find(|a| a.subtype == "Text").expect("the note");
|
||||
assert_eq!(note.popup, Some(Popup { rect: [50.0, 160.0, 190.0, 280.0], open: true }), "listed through 8 0 R");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user