A moved comment is drawn where it goes while it is dragged

Moving a rectangle, an oval or any other comment showed only its blue
frame going, the shape staying where it was until release. The comment is
now rendered alone once as the drag starts (Document::comment_layer: a
read-only copy with only that comment, no page content) and laid over the
page where it goes; the page beneath keeps showing it where it was until
the move is applied, as a CAD program's move does. Rendering the page
without it would cost a whole page's render at every drag on a heavy plan.
Stamps and Fill & Sign marks keep their own layers. annot: isolate_annotations
for several comments of a page, so a group can be drawn together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Oliver Hagmann
2026-10-29 05:50:58 +01:00
co-authored by Claude Opus 5.5
parent 3109bb2359
commit 67b5b2dcf6
10 changed files with 330 additions and 49 deletions
+22 -7
View File
@@ -226,16 +226,31 @@ pub fn picture_rotation(doc: &Document, page: usize, index: usize) -> Result<i64
/// over the page while it is dragged. `false`, with `doc` unchanged, when the annotation has no
/// normal appearance (`/AP /N`) for that render to draw.
pub fn isolate_annotation(doc: &mut Document, page: usize, index: usize) -> Result<bool, AnnotError> {
isolate_annotations(doc, page, &[index])
}
/// [`isolate_annotation`] for several annotations of one page: those of them with a normal
/// appearance stay, in their order on the page. `false`, with `doc` unchanged, when none has one.
pub fn isolate_annotations(doc: &mut Document, page: usize, indices: &[usize]) -> Result<bool, AnnotError> {
let p = page_ref(doc, page)?;
let entry = annots(doc, p).get(index).cloned().ok_or(AnnotError::NoSuchAnnotation { page, index })?;
let has_normal = {
let obj = doc.resolve(&entry);
obj.as_dict().and_then(|d| d.get(b"AP")).is_some_and(|ap| doc.resolve(ap).as_dict().is_some_and(|ap| ap.contains(b"N")))
};
if !has_normal {
let list = annots(doc, p);
let mut kept = Vec::new();
for (index, entry) in list.iter().enumerate() {
if !indices.contains(&index) {
continue;
}
let obj = doc.resolve(entry);
if obj.as_dict().and_then(|d| d.get(b"AP")).is_some_and(|ap| doc.resolve(ap).as_dict().is_some_and(|ap| ap.contains(b"N"))) {
kept.push(entry.clone());
}
}
if let Some(&index) = indices.iter().find(|&&i| i >= list.len()) {
return Err(AnnotError::NoSuchAnnotation { page, index });
}
if kept.is_empty() {
return Ok(false);
}
set_annots(doc, p, vec![entry])?;
set_annots(doc, p, kept)?;
doc.update_dict(p, |d| {
d.remove(b"Contents");
})?;