Extraction walked the name tree again with a fresh key budget, so keys
the listing had left out could fill it and a listed embedded file came
back as not found. The lookup compares each key with the one wanted and
keeps none, so it needs no budget; a key object or /Names array that
several places reference is converted once.
A kept web address or attachment text spent only its converted length,
so a long raw value that trimmed short was converted in full for every
entry sharing it. A kept conversion now spends the longer of its raw
and converted lengths.
The embedded-files name tree copied every key, and an attachment
without a file name copied its key again as the name, so many entries
sharing one long key string multiplied it. Keys, and a key kept again
as the name, now spend the 16 MiB link and attachment text budget; an
embedded file whose key does not fit is not listed.
An annotation attachment's /Contents was read and charged to the text
budget before the file specification's /UF or /F, which then named the
attachment, so the discarded text could leave a later web address
unlisted. /Contents is now read only when neither name is there.
A web address or attachment text that converted to more than the budget
left spent nothing, so every link or attachment sharing it converted it
again. A conversion that comes out too long now spends the raw length,
and so does attachment text that trims to nothing.
The link and attachment listings copied a link's web address and an
attachment's name and description for every annotation or name-tree
entry, so many entries sharing one long string multiplied it in
memory. Together they keep at most 16 MiB of that text, and a string
past what is left is not converted. A link past it lists as a URI
action without its address, an attachment under its generic name
without a description.
The annotation listing skipped a repeated /Annots entry by its raw
reference, so an indirect object holding only a reference to a link
listed that link a second time. Entries and page arrays are now told
apart by the object their reference chain ends at. The lopdf reader
reported the first object of a chain, because lopdf's get_object
already follows it; it now reports the last, as the lazy reader does.
Reaching the 64 MiB comment budget ended the whole annotation scan, so
links and file attachments listed after it were missing, and a later
JavaScript link left the document unmarked. Past the budget, comments
are no longer built, and the scan goes on for links and attachments.
Each unmarked redraw of an /ActualText span's glyphs took its own copy
of the replacement text. The work budget counts comparisons, not
bytes, so a long replacement around one glyph followed by many redraws
at the same place copied it once per redraw: gigabytes from a small
page. Redraws on one page share 4 MiB of replacement text; past it a
redraw keeps its own glyphs, as it does past the work budget.
Inspection built an annotation record, with owned copies of its text,
for every /Annots entry on every page, so one comment referenced many
times, a page-shared /Annots array, or many small comments sharing one
long /Contents string multiplied that text in memory. Pages are read in
order, an annotation or /Annots array already listed is skipped, and
the listing stops once the kept text and geometry pass 64 MiB.
crates/render/src/raster.rs: main's tests plus the PR's #2970 test after the knockout test
vendor/README.md: main's rows with the PR's rewording of hayro patch 18
crates/render/src/raster.rs: both new tests (#2975 stencil masks, #2971 knockout groups)
vendor/hayro/src/renderer.rs: set_paint returns main's PaintSetup; the PR's knockout erase early return wraps its clip in PaintSetup::Clip
vendor/README.md: main's rows plus the PR's hayro-interpret patch 39 and hayro patch numbered 22 (19 is #3296's), with tests
vendor/hayro-interpret/src/pattern.rs: ShadingPattern::new takes the default colour spaces (#3018) and no opacity (this PR); without_opacity (#2977) and set_alpha both kept
Conflicts:
- ATTRIBUTION.toml: ours (sha256 lines only); hashes recomputed at the end of the track
- crates/engine/Cargo.toml: main's workspace chrono entry, plus the PR's pdfcraft-testkit dev-dependency
vendor/hayro/src/renderer.rs: main's PaintSetup::Clip match with the PR's fill paint (is_stroke false) and comment
vendor/README.md: main's row plus the PR's hayro-interpret patch and test, numbered 46 (35 is #3296's)
crates/cos/src/document.rs: #2220 moved the header scan into object_headers; it also returns the position after `obj`, and reconstruct applies this PR's /XRef candidate check (same 1024-byte window) to each header there. Tests: kept both sides.
vendor/hayro-interpret/src/encode.rs: both sides; a pixel sampled by either test (corner held, or centre covered) widens the mesh extent from #3296
vendor/README.md: main's row plus the PR's patch 36 and its test