mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
fix(redact): read the page tree once per sanitize pass, not per page (#5983)
This commit is contained in:
1 parent
8f29ed66f9
commit
4cdc3d15dd
2 files changed
+26
-2
No files matched your search
@@ -587,9 +587,12 @@ fn content_pass(doc: &mut Document, text: bool, layers: bool, write: bool) -> Re
|
||||
// The original image and form XObjects the rewrite drops or replaces, on any page.
|
||||
let mut doomed = Vec::new();
|
||||
let mut retired_properties = Vec::new();
|
||||
let n = pdfcraft_model::pages(doc).len();
|
||||
// The page tree is read once, and again only after a page's rewrite (pinning its inherited
|
||||
// resources can change its ancestors), not once per page: a scan of many pages stays linear.
|
||||
let mut pages = pdfcraft_model::pages(doc);
|
||||
let n = pages.len();
|
||||
for pi in 0..n {
|
||||
let page = pdfcraft_model::pages(doc).swap_remove(pi);
|
||||
let Some(page) = pages.get(pi).cloned() else { break };
|
||||
let mut budget = pdfcraft_cos::DecodeBudget::page();
|
||||
let Ok((list, data)) = page_streams_within(doc, &page.dict, pi, &mut budget) else {
|
||||
if write {
|
||||
@@ -672,6 +675,7 @@ fn content_pass(doc: &mut Document, text: bool, layers: bool, write: bool) -> Re
|
||||
pdfcraft_content::Pieces::join(&page_content).data(),
|
||||
);
|
||||
crate::tags::clean(doc, page.obj, None, &changed_ids, &empty_ids)?;
|
||||
pages = pdfcraft_model::pages(doc);
|
||||
}
|
||||
for (old, new, changed, empty) in out.tag_changes {
|
||||
crate::tags::clean(doc, page.obj, Some((old, new)), &changed, &empty)?;
|
||||
|
||||
@@ -53,3 +53,23 @@ fn a_change_inside_nested_marked_content_touches_every_enclosing_mcid() {
|
||||
assert_eq!(changed, HashSet::from([0, 1]));
|
||||
assert_eq!(empty, HashSet::from([1]));
|
||||
}
|
||||
|
||||
/// The hidden-information scan read the whole page tree again for every page: 20 000 pages cost
|
||||
/// 400 million page visits. It reads the tree once per pass now.
|
||||
#[test]
|
||||
fn a_scan_of_many_pages_reads_the_page_tree_once() {
|
||||
use crate::sanitize::{Hidden, scan};
|
||||
const N: usize = 20_000;
|
||||
let kids: String = (0..N).map(|i| format!("{} 0 R ", 4 + i)).collect();
|
||||
let mut objs = vec![
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
||||
format!("<< /Type /Pages /Kids [{kids}] /Count {N} /MediaBox [0 0 300 300] >>").into_bytes(),
|
||||
stream("", b"BT 3 Tr (x) Tj ET"),
|
||||
];
|
||||
objs.extend((0..N).map(|_| b"<< /Type /Page /Parent 2 0 R /Contents 3 0 R >>".to_vec()));
|
||||
let doc = pdf(objs);
|
||||
let started = std::time::Instant::now();
|
||||
let counts: HashMap<Hidden, usize> = scan(&doc).into_iter().collect();
|
||||
assert_eq!(counts[&Hidden::HiddenText], N, "one invisible glyph on every page");
|
||||
assert!(started.elapsed() < std::time::Duration::from_secs(60), "{:?}", started.elapsed());
|
||||
}
|
||||
Reference in new issue
Block a user