diff --git a/crates/engine/src/optimizer.rs b/crates/engine/src/optimizer.rs index 68f5a143da..b403b82373 100644 --- a/crates/engine/src/optimizer.rs +++ b/crates/engine/src/optimizer.rs @@ -95,12 +95,12 @@ impl OptimizeJob { let discarded = if discard.is_empty() { Vec::new() } else { - // `remove_hidden` refuses a run that removes nothing. Ticked categories that hold - // nothing are nothing to discard, not a reason to end the whole optimization. - match pdfcraft_redact::sanitize::remove_hidden(&mut cos, &discard) { - Err(pdfcraft_redact::RedactError::NothingToApply) => Vec::new(), - r => r?, - } + // Only the ticked categories its scan finds something in: `remove_hidden` refuses when + // it counts every one it is given as 0, which would end the whole optimization, and + // may have changed the copy by then (an empty outline root goes with bookmarks). + let counts = pdfcraft_redact::sanitize::scan(&cos); + let which: Vec = discard.iter().copied().filter(|h| counts.iter().any(|(c, n)| c == h && *n > 0)).collect(); + if which.is_empty() { Vec::new() } else { pdfcraft_redact::sanitize::remove_hidden(&mut cos, &which)? } }; let report = optimize::optimize_with_progress(&mut cos, &settings, &mut |s| { step(match s { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 18108c44da..9f5dece295 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3618,6 +3618,21 @@ fn optimize_discarding_an_empty_category_still_runs() { let (bytes, report) = s.optimized_bytes(id, &optimize::Settings::default(), &[crate::Hidden::Bookmarks, crate::Hidden::Comments]).unwrap(); assert!(bytes.starts_with(b"%PDF-")); assert!(report.discarded.is_empty(), "{:?}", report.discarded); + // An outline root with no entries is no bookmarks: it stays, as Remove Hidden Information, + // which refuses then, leaves it. + let outline = pdfcraft_testkit::Pdf::new() + .obj("<< /Type /Catalog /Pages 2 0 R /Outlines 4 0 R /PageMode /UseOutlines >>") + .obj("<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 612 792] >>") + .obj("<< /Type /Page /Parent 2 0 R >>") + .obj("<< /Type /Outlines /Count 0 >>") + .build(); + let mut s = Session::new(); + let id = s.open("outline.pdf", None, Arc::new(outline), None).unwrap(); + let (bytes, report) = s.optimized_bytes(id, &optimize::Settings::default(), &[crate::Hidden::Bookmarks]).unwrap(); + assert!(report.discarded.is_empty(), "{:?}", report.discarded); + let saved = pdfcraft_cos::Document::open(bytes).unwrap(); + let catalog = saved.get(saved.root().unwrap()).as_dict().cloned().unwrap(); + assert!(catalog.contains(b"Outlines") && catalog.contains(b"PageMode"), "{catalog:?}"); } #[test]