mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-07 12:12:47 +00:00
fix(annot): document-wide annotation scans copy a shared Annots array once
This commit is contained in:
@@ -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};
|
||||
@@ -163,7 +163,7 @@ pub fn with_missing_appearances_cached(doc: &Document, cache: &mut AppearanceCac
|
||||
// reads `doc`, which never gets the appearance, so every repeat used to add another stream to
|
||||
// the copy, and the display bytes grew with the number of listings.
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for page in page_refs(doc).ok()? {
|
||||
for page in 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;
|
||||
|
||||
@@ -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);
|
||||
@@ -172,7 +180,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 };
|
||||
|
||||
@@ -2274,3 +2274,31 @@ fn indirect_typewriter_names_keep_outer_whitespace_in_summaries() {
|
||||
check(&doc);
|
||||
check(&reopen(&doc));
|
||||
}
|
||||
|
||||
/// 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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user