`IndirectObject<f32>` read an object holding `6 0 R` as the number 6, so `/Decode [11 0 R 0]`, with 11 naming 6 and 6 holding 1, read as 6, and `/W` gave glyphs such widths. As anything but a number, such an object was lost. `XRef::get_with` is how every reference is read, to an object stored on its own or in an object stream, so it now follows such objects in a loop, up to 32 references as `Document::resolve` does in #5548. A loop takes no stack however long the chain, and a cycle runs out of references and reads as a missing object, as a reference to nothing does: the image with such a `/Decode` isn't drawn, as with `/Decode [99 0 R 0]`. An object stream isn't reached that way: it has to be a stream stored as that object, and following a reference to one stored inside another let a 1 KB file run out of stack. `XRef::objects` leaves such objects out, or a page came out once more for each one holding a reference to it, and a comment inside a reference counts as white space, as it does everywhere else. Reading an object stream's index now counts against the 64 nested loads, and reads its dictionary alone. That is older than this branch, which only made it slightly worse: a chain of object streams, each with a member of the next for its `/N`, `/First` or `/Length`, recursed with no limit, holding a copy of the chain at each, and aborted on 750 KB with an 8 MiB stack, or 250 KB with 2 MiB, in a test build. Parsing the stream read `/Length` as well as `/N`, two ways to the next stream's index, and a failed index isn't remembered, so the work doubled at each stream: 20 streams in a ring took 20 s on `main`. ISO 32000-2 7.5.7 keeps an object stream's `/Length` out of object streams, so no legal file has a `/Length` chain. Nothing forbids `/N` or `/First` by reference, though no writer I know of makes such a chain. Past the limit it reads as missing unless a stream within 64 of it has been read. `/W` and `/W2` with their list by reference are tested too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
hayro-interpret
A crate for interpreting PDF files.
This crate provides an abstraction to interpret the content of a PDF file and render them
into an abstract [Device], which clients can implement as needed. This allows you, for
example, to render PDF files to bitmaps (which is what the hayro crate does), or other formats
such as SVG.
It should be noted that this crate is still very much in development. Therefore it currently lacks pretty much any documentation on how to use it. It's current API also only really makes it useful for rendering to PNG or SVG, though this will be improved upon in the future.
Examples
See the examples folder on the GitHub repository. Apart from that, you can also consult
the implementation of hayro and hayro-svg to get an idea on how to use this crate.
Safety
This crate forbids unsafe code via a crate-level attribute.
Cargo features
This crate has one optional feature:
embed-fonts: PDF processors are required to support 14 predefined fonts that do not need to be embedded into a PDF file. If you enable this feature, hayro will embed a (permissively-licensed) substitute for each font, so that you don't have to implement your custom font loading logic. This will add around ~240KB to your binary.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
PdfCraft function limits
The PdfCraft patch rejects function construction after 64 nested functions or 10,000 total
function nodes, including repeated references, and detects cycles on the current path. Shared
children in different branches remain valid. Stitching functions reject empty or unresolved
child lists instead of silently truncating them. Calculator functions have a shared limit of 64
procedure levels and 10,000 parsed tokens (operators and procedure openings); evaluation has
its own 10,000-operator limit across selected branches. These are implementation resource
limits rather than PDF format limits. Rejection returns the existing None, and the optional
logging feature reports the reason and limit; no new interface warning is introduced.
Calculator integer division without an integer result is also rejected. Bit shifting discards bits beyond the 32-bit word and fills with zeroes, including logical right shifts of negative words; rotation accepts the full signed shift range without negating the minimum integer. Fixed-size operand and procedure-stack overflow errors propagate instead of truncating work. Normal function signatures and supported color/transfer-function behavior remain unchanged.