From 8bec097af75d5bebeedc7c1f484c2db2cb499177 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 11:46:35 +0000 Subject: [PATCH] fix(print,engine): read the page tree once when finding which pages' text reads up --- crates/engine/src/session/files.rs | 8 +++++++- crates/print/src/lib.rs | 2 +- crates/print/src/sheets.rs | 12 +++++++++++- crates/print/src/tests.rs | 14 ++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/session/files.rs b/crates/engine/src/session/files.rs index 553ddd6aa3..a73cd6d442 100644 --- a/crates/engine/src/session/files.rs +++ b/crates/engine/src/session/files.rs @@ -171,7 +171,13 @@ impl Session { Err(_) => return Vec::new(), }, }; - (0..doc.info.pages.len()).map(|i| pages.contains(&i) && pdfcraft_print::text_reads_up(&cos, i)).collect() + // The page tree is read once, and each page is looked up, not searched for, in `pages`. + let mut out = vec![false; doc.info.pages.len()]; + let wanted: Vec = pages.iter().copied().filter(|&p| p < out.len()).collect::>().into_iter().collect(); + for (p, up) in wanted.iter().zip(pdfcraft_print::texts_read_up(&cos, &wanted)) { + out[*p] = up; + } + out } /// Combine whole files, in order, into new PDF bytes (one bookmark per file). diff --git a/crates/print/src/lib.rs b/crates/print/src/lib.rs index 067112b2e7..0d857e6bb4 100644 --- a/crates/print/src/lib.rs +++ b/crates/print/src/lib.rs @@ -42,4 +42,4 @@ pub use settings::{ }; #[cfg(test)] use sheets::text_direction; -pub use sheets::{LABEL_MARGIN, MARGIN, Placement, Sheet, layout, matching_paper, text_reads_up}; +pub use sheets::{LABEL_MARGIN, MARGIN, Placement, Sheet, layout, matching_paper, text_reads_up, texts_read_up}; diff --git a/crates/print/src/sheets.rs b/crates/print/src/sheets.rs index 061a6ed5c3..69c78feea5 100644 --- a/crates/print/src/sheets.rs +++ b/crates/print/src/sheets.rs @@ -7,7 +7,17 @@ use super::{DecodeBudget, Dict, Document, Matrix, Object}; /// sideways on a letter or A4 sheet), counting each text-showing operator once; form XObjects /// are followed a few levels deep. pub fn text_reads_up(doc: &Document, index: usize) -> bool { - let Some(page) = pdfcraft_model::pages(doc).into_iter().nth(index) else { return false }; + texts_read_up(doc, &[index]).first().copied().unwrap_or(false) +} + +/// [`text_reads_up`] for each of `indices`, reading the page tree once: once per page, the +/// whole tree would be walked for every page asked about. +pub fn texts_read_up(doc: &Document, indices: &[usize]) -> Vec { + let pages = pdfcraft_model::pages(doc); + indices.iter().map(|&i| pages.get(i).is_some_and(|page| page_reads_up(doc, page))).collect() +} + +fn page_reads_up(doc: &Document, page: &pdfcraft_model::Page) -> bool { let to_display = Matrix(page.view_matrix(doc)).invert().unwrap_or_default(); let res = page.dict.get(b"Resources").and_then(|r| doc.resolve(r).as_dict().cloned()).unwrap_or_default(); let mut score = 0i64; diff --git a/crates/print/src/tests.rs b/crates/print/src/tests.rs index 642af0b8c9..e4c2b44a6e 100644 --- a/crates/print/src/tests.rs +++ b/crates/print/src/tests.rs @@ -1245,3 +1245,17 @@ fn contents_pieces_join_with_a_space_across_a_trailing_comment() { let printed_ctrl = impose(&with_newline, &settings(vec![0], Layout::Size(SizeMode::Actual))).unwrap(); assert_eq!(form_strings(printed_ctrl), [b"HIDDEN".to_vec(), b"SHOWN".to_vec()]); } + +/// Many pages asked about at once are answered from one walk of the page tree, each as +/// [`text_reads_up`] answers it alone; a page past the end is `false`. +#[test] +fn texts_read_up_answers_many_pages_from_one_walk() { + let doc = fixture(3_000); + let all: Vec = (0..3_001).collect(); + let started = std::time::Instant::now(); + let up = texts_read_up(&doc, &all); + assert!(started.elapsed() < std::time::Duration::from_secs(20), "{:?}", started.elapsed()); + assert_eq!(up.len(), all.len()); + assert!(up.iter().all(|u| !u), "upright text, and no page 3000"); + assert_eq!(up[1], text_reads_up(&doc, 1)); +}