mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 06:28:49 -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
e76c4f5613
commit
a88c8afb73
@@ -662,12 +662,13 @@ pub(crate) fn run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// JBIG2Decode is PDF 1.4: a document whose header says less, and whose catalog doesn't say 1.4
|
/// 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
|
/// or more already (directly or by reference), gets `/Version /1.4` in its catalog (ISO 32000-2
|
||||||
/// object streams writes a 1.5 header anyway.
|
/// §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> {
|
fn at_least_pdf_1_4(doc: &mut Document) -> Result<(), OptimizeError> {
|
||||||
let below = |v: &[u8]| v.len() < 3 || v < &b"1.4"[..];
|
let below = |v: &[u8]| v.len() < 3 || v < &b"1.4"[..];
|
||||||
let Some(root) = doc.root() else { return Ok(()) };
|
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) {
|
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")))?;
|
doc.update_dict(root, |c| c.set(b"Version".to_vec(), Object::name("1.4")))?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -522,6 +522,31 @@ fn black_and_white_images_become_jbig2() {
|
|||||||
assert!(render(&jbig2) == before, "the page looks the same");
|
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]
|
#[test]
|
||||||
fn settings_choose_what_happens() {
|
fn settings_choose_what_happens() {
|
||||||
let mut doc = Document::new_empty();
|
let mut doc = Document::new_empty();
|
||||||
|
|||||||
Reference in New Issue
Block a user