mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 05:58:12 -06:00
M11: the optimizer reads a catalog /Version given by reference
When the optimizer stores an image as JBIG2 (PDF 1.4), it raises the catalog's /Version for an older file. It read /Version only when it was direct: a catalog saying 1.7 by reference looked like no version at all, and was set to /1.4, lowering the document. It is now resolved first. Found in pre-push review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6yUtvWGPsJpMLyh6pxSXG
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
881c4150e9
commit
fe5e8f4431
@@ -674,12 +674,13 @@ pub(crate) fn run(
|
||||
}
|
||||
|
||||
/// JBIG2Decode is PDF 1.4: a document whose header says less, and whose catalog doesn't say 1.4
|
||||
/// or more already, gets `/Version /1.4` in its catalog (ISO 32000-2 §7.7.2). A full save with
|
||||
/// object streams writes a 1.5 header anyway.
|
||||
/// or more already (directly or by reference), gets `/Version /1.4` in its catalog (ISO 32000-2
|
||||
/// §7.7.2). A full save with object streams writes a 1.5 header anyway.
|
||||
fn at_least_pdf_1_4(doc: &mut Document) -> Result<(), OptimizeError> {
|
||||
let below = |v: &[u8]| v.len() < 3 || v < &b"1.4"[..];
|
||||
let Some(root) = doc.root() else { return Ok(()) };
|
||||
let catalog = doc.get(root).as_dict().and_then(|c| c.name(b"Version").map(<[u8]>::to_vec));
|
||||
let version = doc.get(root).as_dict().and_then(|c| c.get(b"Version").cloned());
|
||||
let catalog = version.and_then(|v| doc.resolve(&v).as_name().map(<[u8]>::to_vec));
|
||||
if below(doc.version().as_bytes()) && catalog.as_deref().is_none_or(below) {
|
||||
doc.update_dict(root, |c| c.set(b"Version".to_vec(), Object::name("1.4")))?;
|
||||
}
|
||||
|
||||
@@ -649,6 +649,31 @@ fn black_and_white_images_become_jbig2() {
|
||||
assert!(render(&jbig2) == before, "the page looks the same");
|
||||
}
|
||||
|
||||
/// A catalog `/Version` given by reference is read through it: a PDF 1.3 file whose catalog says
|
||||
/// 1.7 by reference keeps 1.7 when its images become JBIG2, rather than being lowered to 1.4; one
|
||||
/// whose catalog says 1.3 by reference gets 1.4.
|
||||
#[test]
|
||||
fn a_catalog_version_given_by_reference_is_read() {
|
||||
for (catalog, after) in [("1.7", "1.7"), ("1.3", "1.4")] {
|
||||
let mut doc = Document::new_empty();
|
||||
let (w, h) = (1200, 480);
|
||||
let text = bilevel(w, h, false);
|
||||
let im = mono_image(&mut doc, w, h, &text, None, &[("BitsPerComponent", 1.into()), ("ColorSpace", Object::name("DeviceGray"))]);
|
||||
page(&mut doc, &[("Im", im)], "q 300 0 0 120 20 660 cm /Im Do Q");
|
||||
let version = doc.add(Object::name(catalog));
|
||||
let root = doc.root().unwrap();
|
||||
doc.update_dict(root, |c| c.set(b"Version".to_vec(), Object::Ref(version))).unwrap();
|
||||
let mut bytes = write_full(&doc, &SaveOptions { object_streams: false, ..SaveOptions::default() }).unwrap();
|
||||
bytes[..8].copy_from_slice(b"%PDF-1.3");
|
||||
let mut doc = Document::open(Arc::new(bytes)).unwrap();
|
||||
let r = optimize(&mut doc, &Settings { mono: MonoCompression::Jbig2, ..Settings::default() }).unwrap();
|
||||
assert_eq!(r.images_recompressed, 1);
|
||||
let root = doc.root().unwrap();
|
||||
let v = doc.get(root).as_dict().and_then(|c| c.get(b"Version").cloned()).unwrap();
|
||||
assert_eq!(doc.resolve(&v).as_name(), Some(after.as_bytes()), "catalog {catalog} by reference");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_choose_what_happens() {
|
||||
let mut doc = Document::new_empty();
|
||||
|
||||
Reference in New Issue
Block a user