mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
Type 3 pruning counts a page's appearances toward one operator total
When a page lists a Type 3 font, prune_type3_from_resources (#5197) adds the operators of the page's annotation appearances without resources of their own. Each appearance was checked against the 2,000,000 cap on its own, so several under it could add up to far more, and the prune went on with a total no other path allows. The appearances now share the running total, and passing it refuses the redaction with the same kind of error as the other caps.
This commit is contained in:
1 parent
2468d8e470
commit
d2974e1fa5
2 files changed
+33
-1
No files matched your search
@@ -386,6 +386,32 @@ fn a_type3_font_only_an_appearance_without_resources_selects_stays_on_the_page()
|
||||
assert!(saved_first_page_lists_font(&doc, b"T4"), "the page no longer lists the font the appearance selects");
|
||||
}
|
||||
|
||||
/// The operators of a page's appearances count toward one total, as the content's do. Two
|
||||
/// appearances under the cap alone pass it together, and the redaction is refused rather than
|
||||
/// judged on part of what draws through the page's fonts.
|
||||
#[test]
|
||||
fn a_page_s_appearances_share_one_operator_total() {
|
||||
let big = || stream("/Type /XObject /Subtype /Form /BBox [0 0 100 20]", b"n\n".repeat(1_100_000));
|
||||
let mut doc = pdf(vec![
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
||||
b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
|
||||
b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 300] /Contents 4 0 R /Resources 5 0 R /Annots [7 0 R 8 0 R] >>".to_vec(),
|
||||
stream("", b"BT /T3 10 Tf 10 100 Td (AC) Tj ET"),
|
||||
b"<< /Font << /T3 6 0 R >> >>".to_vec(),
|
||||
type3_abc(11),
|
||||
b"<< /Type /Annot /Subtype /FreeText /Rect [150 200 250 220] /P 3 0 R /AP << /N 9 0 R >> >>".to_vec(),
|
||||
b"<< /Type /Annot /Subtype /FreeText /Rect [150 240 250 260] /P 3 0 R /AP << /N 10 0 R >> >>".to_vec(),
|
||||
big(),
|
||||
big(),
|
||||
stream("", b"% GLYPH_A"),
|
||||
stream("", b"% GLYPH_B"),
|
||||
stream("", b"% GLYPH_C"),
|
||||
]);
|
||||
mark(&mut doc, 0, &[[9.0, 95.0, 14.9, 110.0]], "");
|
||||
let refused = apply(&mut doc, None);
|
||||
assert!(refused.is_err(), "two appearances over the operator cap together were judged in part: {refused:?}");
|
||||
}
|
||||
|
||||
/// Whether the first page of a full save of `doc` lists the font `name` in its resources.
|
||||
fn saved_first_page_lists_font(doc: &Document, name: &[u8]) -> bool {
|
||||
let bytes = write_full(doc, &SaveOptions::default()).unwrap();
|
||||
|
||||
@@ -494,8 +494,14 @@ pub(crate) fn prune_type3_from_resources(doc: &Document, res: &mut Dict, content
|
||||
// other files are not refused over them.
|
||||
if fonts.iter().any(|(_, f)| doc.resolve(f).as_dict().is_some_and(|d| d.name(b"Subtype") == Some(b"Type3"))) {
|
||||
if let Some(page) = page {
|
||||
// One total for all of them, as for the content: each appearance under the cap could
|
||||
// still add up to far more than it.
|
||||
for data in resourceless_appearances(doc, page)? {
|
||||
ops.extend(crate::interp::resource_ops(doc, res, std::slice::from_ref(&data))?);
|
||||
let more = crate::interp::resource_ops(doc, res, std::slice::from_ref(&data))?;
|
||||
if ops.len().saturating_add(more.len()) > 2_000_000 {
|
||||
return Err(CosError::Syntax { offset: 0, detail: "redaction could not fully inspect annotation appearances".into() });
|
||||
}
|
||||
ops.extend(more);
|
||||
}
|
||||
}
|
||||
let known = ops.len();
|
||||
|
||||
Reference in new issue
Block a user