Files
pdfcraft/vendor/hayro-interpret
9c875656de fix(render, ui): form-heavy documents draw and fill fast (spot-colour images, widget placement, Fields panel) (#1691)
* fix(render): convert spot-colour images once per pixel value (vendored hayro-interpret patch 28)

An image whose pixels hold at most 16 bits of samples (an 8-bit Separation or
DeviceN image, a 1-, 2- or 4-bit gray or indexed one) is now converted to RGB
through a table of every possible pixel value instead of pixel by pixel.

A Separation or DeviceN tint transform is a PDF function, often a PostScript
calculator program, and the image decoder ran it once per pixel. Fillable
character sheets draw their background art as an 8-bit DeviceN image: one with
a 3304 x 4105 background took 3.8 s and 430 MB to draw each page (Poppler: 0.27 s),
when opening it and again after every field was filled. It now draws in 0.11 s
and 100 MB, pixel for pixel the same.

The table decodes each sample through the same `decode_sample` as the per-pixel
path, keeps `fix_image_length`'s rules for short and overlong data (the pixels
past the data are converted as before), and is used only when the image has
more pixels than the table has entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnNgjPEDfKJM1wj17Q8Za8

* fix(render): place widgets without /P in one pass over the pages

Opening a document, the inspector finds the page of a form field whose widget
has no /P by searching every page's /Annots for it, once per field: O(fields x
annotations). A form of 8,000 such fields took 1.17 s to open instead of
0.27 s. The pages' /Annots are now read once, on the first such field, into a
map from annotation to page.

A widget that several pages list is now placed on the first of them, as
pdfcraft-forms places it; the search took whichever page a HashMap visited
first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnNgjPEDfKJM1wj17Q8Za8

* fix(ui): the Fields panel keeps up with large forms

Every frame the Fields panel searched the form for each row by name, once to
sort the rows into tab order and again to find what a click selects: O(fields²)
a frame. It also laid out, drew and built the tooltip of every row, scrolled
into view or not. With 8,000 fields the app used 13.4 s of CPU for 20 pointer
moves with the panel open (0.2 s with it closed).

The rows are now worked out by `field_rows`, which pairs each row with the
first form field of its name through one map a frame, as the engine pairs
them, and a row out of view is allocated and keeps its clicks, keyboard focus
and accessibility label but isn't drawn and builds no tooltip. The same 20
moves now take 0.86 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnNgjPEDfKJM1wj17Q8Za8

* docs: roadmap entries for faster form-heavy documents

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnNgjPEDfKJM1wj17Q8Za8

* fix(render): convert each lookup-table entry on its own (vendored hayro-interpret patch 28)

Patch 28 converted the whole table of possible pixel values in one batch.
Separation and DeviceN `convert_f32` concatenate each tint result, and a Type 4
program that returns the wrong number of components is only warned about, so a
malformed result for a value the image never uses shifted every later entry. A
32 x 32 image whose only sample (255) is red drew black because tint 0 returned
one component (PR #1691 review).

`convert_each` now converts the table one entry at a time, so a malformed entry
changes only itself. The largest table (65,536 entries, a two-ink DeviceN image
over DeviceCMYK) takes about 2 ms longer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:20:31 -04:00
..
2026-09-30 11:00:02 -04:00
2026-09-30 11:00:02 -04:00
2026-09-30 11:00:02 -04:00
2026-09-30 11:00:02 -04:00
2026-09-30 11:00:02 -04:00
2026-09-30 11:00:02 -04:00

hayro-interpret

Crates.io Documentation

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

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.