diff --git a/crates/cos/src/budget.rs b/crates/cos/src/budget.rs index 51b05e034d..23bcaf1af7 100644 --- a/crates/cos/src/budget.rs +++ b/crates/cos/src/budget.rs @@ -850,6 +850,17 @@ mod tests { dict.set(b"Filter".to_vec(), Object::Array(vec![Object::name("FlateDecode"), Object::name("ASCIIHexDecode")])); let hex = Stream::from_raw(dict, Stream::flate(Dict::new(), b"4142zz4344>").raw.to_vec()); assert_eq!(budget.decode_with_damage(&doc, &hex, usize::MAX).ok(), Some((b"AB".to_vec(), true))); + // So is an earlier stage's, though the stage after it reads all it was given: a stored + // block of whole hex, then a block of the reserved type (RFC 1951 ยง3.2.3). + let readable = b"4142>"; + let len = u16::try_from(readable.len()).unwrap_or_default(); + let mut raw = vec![0x78, 0x01, 0x00]; + raw.extend_from_slice(&len.to_le_bytes()); + raw.extend_from_slice(&(!len).to_le_bytes()); + raw.extend_from_slice(readable); + raw.push(0x07); + let flate_damaged = Stream::from_raw(hex.dict.clone(), raw); + assert_eq!(budget.decode_with_damage(&doc, &flate_damaged, usize::MAX).ok(), Some((b"AB".to_vec(), true))); } /// `/DecodeParms` given by reference, or a parameter given by reference in them, is read: a diff --git a/crates/optimize/tests/optimize.rs b/crates/optimize/tests/optimize.rs index 7250e9a0a0..2785ddac6c 100644 --- a/crates/optimize/tests/optimize.rs +++ b/crates/optimize/tests/optimize.rs @@ -3220,6 +3220,24 @@ fn content_damaged_partway_leaves_every_image_at_its_resolution() { } } +/// One page damaged partway stops resampling for the whole document: another page drawing the same +/// image at its full resolution, whole, doesn't get it resampled either. +#[test] +fn content_damaged_partway_on_one_page_leaves_the_image_another_page_draws() { + let mut doc = Document::new_empty(); + let img = image(&mut doc, 720, 720, 1, false, None); + let damaged = page(&mut doc, &[("Im", img)], ""); + let bad = doc.add(Object::Stream(damaged_flate(b"0 g\n", b"q 720 0 0 720 0 0 cm /Im Do Q"))); + doc.update_dict(damaged, |d| d.set(b"Contents".to_vec(), Object::Ref(bad))).unwrap(); + let whole = page(&mut doc, &[("Im", img)], "q 72 0 0 72 0 0 cm /Im Do Q"); + assert_eq!(effective_resolutions(&doc, &[whole])[&img], 720.0); + let ppi = effective_resolutions(&doc, &[damaged, whole]).get(&img).copied().unwrap_or(f64::INFINITY); + assert_eq!(ppi, f64::INFINITY); + let r = optimize(&mut doc, &Settings::default()).unwrap(); + assert_eq!(r.images_resampled, 0, "{r:?}"); + assert_eq!(doc.get(img).as_dict().and_then(|d| d.int(b"Width")), Some(720)); +} + /// `levels` nested forms with their own resources, the innermost drawing `img` over the unit square. fn nested_forms(doc: &mut Document, img: ObjRef, levels: usize) -> ObjRef { let mut inner = form(doc, Some(Object::Dict(resources(xobjects(&[("Im", img)])))), "/Im Do"); diff --git a/crates/redact/src/tests.rs b/crates/redact/src/tests.rs index 8b91d1128a..428b0b3a84 100644 --- a/crates/redact/src/tests.rs +++ b/crates/redact/src/tests.rs @@ -130,7 +130,14 @@ fn content_with_parameters_given_by_reference_is_redacted() { mark(&mut doc, 0, &marked, ""); let r = apply(&mut doc, None).unwrap(); assert_eq!((r.marks, r.glyphs), (1, 6), "{what}"); - assert_eq!(under(&mut reopen(&doc), 0, &marked), 0, "{what}"); + let mut saved = reopen(&doc); + assert_eq!(under(&mut saved, 0, &marked), 0, "{what}"); + // Nor is the text left in a stream the page no longer draws, read with its parameters. + for (r, o) in saved.scan_objects() { + let Object::Stream(s) = &*o else { continue }; + let data = pdfcraft_cos::DecodeBudget::new(usize::MAX, 0).decode_with_damage(&saved, s, usize::MAX).map(|(d, _)| d).unwrap_or_default(); + assert!(!data.windows(6).any(|w| w == b"SECRET"), "{what}: {r:?}"); + } } }