The merge gave `prune_font_forms`' walk of each Form a kept glyph
draws the allowance of a page, which its call of `resource_ops` now
needs; no test pinned it. In
`the_form_a_kept_type3_glyph_draws_is_walked_within_the_allowance_of_a_page`
such a Form draws two forms without resources of their own, each past
half of the allowance, and the redaction is refused with the decoder's
limit; with two small ones it goes on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
Three reads of the Type 3 cleanup came in from main in the merge, and
took `/DecodeParms` only when direct: the forms a surviving glyph draws
(`prune_font_forms`), a page's appearances without resources of their
own, and the soft mask groups without them (`add_soft_mask_ops`). Behind
a predictor whose parameters are an indirect object, each read the
predictor's tag bytes and row differences, which lex as operators that
use no resource, so nothing refused. The form's resources then lost the
image it draws, and the page lost the Type 3 font only the appearance
or the soft mask selects, with its glyphs. Each is now read with them
too, as this branch's other reads in the cleanup are; the appearances
still against the page's allowance.
Tests, each the by-reference form of one already on main:
- `an_image_a_type3_glyphs_form_with_parameters_by_reference_draws_is_kept`
- `a_type3_font_only_an_appearance_with_parameters_by_reference_selects_stays_on_the_page`
- `a_type3_font_only_a_soft_mask_with_parameters_by_reference_selects_stays_on_the_page`
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
Main reworked the redaction cleanups this branch changes: #5969 reads
each inherited form once for all names (`names_content_draws`), #5924
prunes the forms a surviving Type 3 glyph draws and passes the page to
`prune_type3_from_resources`, and #6474 reads a page's annotation
appearances toward one operator total and one byte allowance.
- `interp/prune.rs`: `names_content_draws` keeps main's one read per
form and takes this branch's three changes to `content_draws`: it
reads operators one at a time, holding no stream's whole and decoding
each form only in its turn; it reads forms with `/DecodeParms` given
by reference; and a `/Resources` that isn't a dictionary is none of
the form's own. It charges no allowance, as `content_draws` didn't:
it runs after the pages' walks, over every holder, where no page's
allowance is left to spend.
- `type3.rs`: `prune_type3_from_resources` takes both the page's
allowance and main's `page`. The appearances main reads are charged
to that allowance, which the content and its forms spend too, in
place of a fresh one of their own, so one page has one allowance.
Main's `record_used` serves this branch's one-operator-at-a-time read
of glyph programs, and `scan_stream_object`, which took over
`scan_xobject`'s read, reads with `/DecodeParms` by reference.
`prune_font_forms` gives each form's walk the allowance of a page, as
`prune_retired_properties` does for each content.
- `interp.rs`, `lib.rs`, `sanitize.rs`: both arguments at each call.
- `edit/src/rules.rs`: the refusal of incomplete content stays, with
main's `text::joined`.
- Tests: both sides' kept, in `edit/src/tests.rs` and
`redact/src/tests/surviving_references.rs`. A new one,
`a_page_s_appearances_spend_what_its_content_left_of_the_allowance`,
pins the shared allowance: 1 MiB of content and an appearance within
half a MiB of the allowance are refused together, though the
appearance alone fits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
The header check reports a file without the binary comment, and Save as PDF/A then makes the next save a full one. The known-answer documents and the generation-repair file predate it: the minimal document got a 6.1.2 issue, and an undamaged file needed a full save for its header instead of being saved incrementally. They now start with the comment, as the crate's unit test documents do.
Patch 82 sent every soft mask through the decoded-image cache, so a mask that patch 91 keeps failed inside its image's entry got a second entry and counted as another decode. Only a mask averaged down needs an entry of its own; any other decodes as it did before patch 82.