fix(print,engine): read the page tree once when finding which pages' text reads up

This commit is contained in:
Dylan M. Taylor
2026-11-03 11:46:35 +00:00
parent 14ae839a28
commit 8bec097af7
4 changed files with 33 additions and 3 deletions
+7 -1
View File
@@ -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<usize> = pages.iter().copied().filter(|&p| p < out.len()).collect::<std::collections::BTreeSet<_>>().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).
+1 -1
View File
@@ -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};
+11 -1
View File
@@ -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<bool> {
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;
+14
View File
@@ -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<usize> = (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));
}