fix(annot): document-wide annotation scans copy a shared Annots array once

This commit is contained in:
Dylan M. Taylor committed 2026-11-10 22:00:20 -05:00
1 parent d2916decb3
commit 58245d57bc
3 files changed
+39 -3

No files matched your search

+2 -2
View File
@@ -1,5 +1,5 @@
use super::create::{finite, num_array};
use super::lookup::{annot_dict, annots, page_refs};
use super::lookup::{annot_dict, annots, page_refs, with_distinct_annots};
use super::modify::refuse_foreign_text;
use super::types::NOTE_SIZE;
use super::{AnnotError, Dict, Document, ObjRef, Object, Stream, appearance};
@@ -383,7 +383,7 @@ pub fn with_missing_appearances_cached(doc: &Document, cache: &mut AppearanceCac
// Pages' visible regions, read once and only when an annotation drawn through its points is.
let mut crops: Option<std::collections::HashMap<ObjRef, [f64; 4]>> = None;
let mut budget = PASS_BUDGET;
for page in page_refs(doc).ok()? {
for page in crate::lookup::with_distinct_annots(doc, page_refs(doc).ok()?) {
for r in annots(doc, page).iter().filter_map(Object::as_ref) {
if !seen.insert(r) {
continue;
+9 -1
View File
@@ -73,6 +73,14 @@ pub(crate) fn page_ref(doc: &Document, page: usize) -> Result<ObjRef, AnnotError
page_refs(doc)?.get(page).copied().ok_or(AnnotError::NoSuchPage(page))
}
/// `pages` without those whose `/Annots` is an indirect array an earlier page already lists. For a
/// consumer that visits each annotation once however often it is listed: a shared array is then
/// copied and scanned once, not once per page that names it.
pub(crate) fn with_distinct_annots(doc: &Document, pages: Vec<ObjRef>) -> Vec<ObjRef> {
let mut seen = std::collections::HashSet::new();
pages.into_iter().filter(|p| doc.get(*p).as_dict().and_then(|d| d.reference(b"Annots")).is_none_or(|r| seen.insert(r))).collect()
}
/// The entries of a page's `/Annots` (references or inline dictionaries).
pub(crate) fn annots(doc: &Document, page: ObjRef) -> Vec<Object> {
let obj = doc.get(page);
@@ -229,7 +237,7 @@ pub(crate) fn drawn_upright(doc: &Document, d: &Dict) -> bool {
/// Whether any page has a Fill & Sign annotation that flatten would bake in (not hidden).
pub fn has_visible_fill_sign(doc: &Document) -> bool {
let Ok(pages) = page_refs(doc) else { return false };
for page in pages {
for page in with_distinct_annots(doc, pages) {
for entry in annots(doc, page) {
let obj = doc.resolve(&entry);
let Some(d) = obj.as_dict() else { continue };
+28
View File
@@ -5619,3 +5619,31 @@ endstream",
assert!(list(&doc, 0)[0].contains(b"AAPL:AKExtras"));
assert!(doc.repair_log().is_empty(), "{:?}", doc.repair_log());
}
/// Pages sharing one indirect `/Annots` array have it copied once by the consumers that visit each
/// annotation once, not once per page.
#[test]
fn document_wide_scans_copy_a_shared_annots_array_once() {
let mut doc = fixture();
let root = ObjRef { num: 2, generation: 0 };
let mut note = Dict::new();
note.set(b"Type".to_vec(), Object::name("Annot"));
note.set(b"Subtype".to_vec(), Object::name("Text"));
note.set(b"Rect".to_vec(), Object::Array([10, 10, 30, 30].map(Object::Int).to_vec()));
let note = doc.add(note);
let shared = doc.add(Object::Array(std::iter::repeat_n(Object::Ref(note), 100).collect()));
let pages: Vec<Object> = (0..500)
.map(|_| {
let mut d = Dict::new();
d.set(b"Type".to_vec(), Object::name("Page"));
d.set(b"Annots".to_vec(), Object::Ref(shared));
Object::Ref(doc.add(d))
})
.collect();
doc.update_dict(root, |d| d.set(b"Kids".to_vec(), Object::Array(pages))).unwrap();
ANNOTS_ENTRIES_COPIED.with(|c| c.set(0));
assert!(!has_visible_fill_sign(&doc));
assert!(with_missing_appearances(&doc).is_some());
let copied = ANNOTS_ENTRIES_COPIED.with(std::cell::Cell::get);
assert!(copied <= 200, "{copied} annotation entries copied");
}