mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 22:41:47 -06:00
fix(comments): previews lift a stamp whatever its replies' locks; Delete greys out over a locked reply
The locked-reply check in delete_annotation also reached stamp_preview and signature_image, which only take the annotation off a copy to draw the page beneath it: an unlocked stamp with a locked reply lost its drag preview. They now use lift_annotation, which checks no locks. The Comments panel's Delete (comment and reply menus) and the page's context menu are greyed out while a reply below is locked (comments::locked_below), rather than refusing after the click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
72edd945e5
commit
28a31aebc0
@@ -36,8 +36,8 @@ use lookup::{annots, page_ref};
|
||||
pub use lookup::{has_visible_fill_sign, is_fill_sign, isolate_annotation, page_refs, picture_rotation, set_annots, signature_image};
|
||||
pub(crate) use modify::n;
|
||||
pub use modify::{
|
||||
add_reply, add_text_replacement, delete_annotation, erase_ink, fillable, goes_with, move_annotation, set_contents, set_fill, set_font_size,
|
||||
set_locked, set_marked, set_points, set_rect, set_review_state, set_style, with_replies_and_popups,
|
||||
add_reply, add_text_replacement, delete_annotation, erase_ink, fillable, goes_with, lift_annotation, move_annotation, set_contents, set_fill,
|
||||
set_font_size, set_locked, set_marked, set_points, set_rect, set_review_state, set_style, with_replies_and_popups,
|
||||
};
|
||||
pub use types::{
|
||||
AttachIcon, FillMark, LineEnding, Markup, Meta, NOTE_SIZE, NewAnnotation, NoteIcon, OverlayFont, OverlayLook, ReviewState, Rgb, Shape,
|
||||
|
||||
@@ -70,11 +70,23 @@ pub(crate) fn touch(d: &mut Dict, meta: &Meta) {
|
||||
}
|
||||
|
||||
/// Delete a comment together with its pop-up and its replies (and theirs), as Acrobat does.
|
||||
/// Refused while it, or a reply that would go with it, is locked.
|
||||
pub fn delete_annotation(doc: &mut Document, page: usize, index: usize) -> Result<(), AnnotError> {
|
||||
remove_with_replies(doc, page, index, true)
|
||||
}
|
||||
|
||||
/// Take the annotation at `(page, index)` out of its page with its pop-up and replies, as
|
||||
/// [`delete_annotation`] does but whatever their locks: for drawing the page beneath it on a
|
||||
/// copy of the document (a stamp or signature being dragged), which changes nothing.
|
||||
pub fn lift_annotation(doc: &mut Document, page: usize, index: usize) -> Result<(), AnnotError> {
|
||||
remove_with_replies(doc, page, index, false)
|
||||
}
|
||||
|
||||
fn remove_with_replies(doc: &mut Document, page: usize, index: usize, check_locks: bool) -> Result<(), AnnotError> {
|
||||
let p = page_ref(doc, page)?;
|
||||
let list = annots(doc, p);
|
||||
let target = list.get(index).cloned().ok_or(AnnotError::NoSuchAnnotation { page, index })?;
|
||||
if let Some(r) = target.as_ref() {
|
||||
if check_locks && let Some(r) = target.as_ref() {
|
||||
unlocked(doc, r)?;
|
||||
}
|
||||
// An inline comment has no reference to collect; the pop-up it names still goes.
|
||||
@@ -82,12 +94,14 @@ pub fn delete_annotation(doc: &mut Document, page: usize, index: usize) -> Resul
|
||||
let doomed = with_replies_and_popups(doc, &list, seed);
|
||||
// A locked reply can't be deleted on its own, so it doesn't go with the comment or reply it
|
||||
// answers either. Pop-ups follow their comment.
|
||||
for (_, e) in list.iter().enumerate().filter(|(i, e)| *i != index && goes_with(e, &doomed)) {
|
||||
let obj = doc.resolve(e);
|
||||
if let Some(d) = obj.as_dict().filter(|d| d.name(b"Subtype") != Some(b"Popup".as_slice()))
|
||||
&& annotation_flags(doc, d) & FLAG_LOCKED != 0
|
||||
{
|
||||
return Err(AnnotError::Invalid("a reply to it is locked".into()));
|
||||
if check_locks {
|
||||
for (_, e) in list.iter().enumerate().filter(|(i, e)| *i != index && goes_with(e, &doomed)) {
|
||||
let obj = doc.resolve(e);
|
||||
if let Some(d) = obj.as_dict().filter(|d| d.name(b"Subtype") != Some(b"Popup".as_slice()))
|
||||
&& annotation_flags(doc, d) & FLAG_LOCKED != 0
|
||||
{
|
||||
return Err(AnnotError::Invalid("a reply to it is locked".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
let kept: Vec<Object> = list.into_iter().enumerate().filter(|(i, e)| *i != index && !goes_with(e, &doomed)).map(|(_, e)| e).collect();
|
||||
|
||||
@@ -242,6 +242,10 @@ fn a_locked_reply_keeps_what_it_answers() {
|
||||
assert_eq!(delete_annotation(&mut doc, 0, reply), Err(AnnotError::Invalid("a reply to it is locked".into())));
|
||||
assert_eq!(delete_annotation(&mut doc, 0, note), Err(AnnotError::Invalid("a reply to it is locked".into())));
|
||||
assert_eq!(list(&doc, 0).len(), before, "nothing went");
|
||||
// Lifted on a copy, to draw the page beneath, the locks don't count.
|
||||
let mut copy = doc.clone();
|
||||
lift_annotation(&mut copy, 0, note).unwrap();
|
||||
assert!(list(©, 0).len() < before, "the thread is lifted off the copy");
|
||||
set_locked(&mut doc, 0, answer, false).unwrap();
|
||||
delete_annotation(&mut doc, 0, reply).unwrap();
|
||||
let left: Vec<String> = list(&doc, 0).iter().map(|d| text(d, b"Contents")).collect();
|
||||
|
||||
Reference in New Issue
Block a user