`cargo xtask fuzz` found a 392-byte page whose ExtGState sets `/D [[11] 0]` on a line that starts at x = 92234775807. Stroke expansion keeps every dash, so it cut the line into billions of them and allocated more than 5 GB before the run was stopped. Triage found the same class in ordinary-looking geometry, and a second bug next to it: - A tiny dash on an ordinary line (`[0.000001] 0 d` on 20 pt) passed 10 GB in under two seconds; so did `[1] 0 d` under a shrinking matrix, and a long pattern on many short subpaths, because kurbo restarts the pattern at every subpath. The render watchdog can skip a stuck page but can't stop it allocating, so on a desktop this ends in an abort that takes every open document with it. - A dash array whose entries sum to less than zero (`[-1 -1] 0 d`) made kurbo's search for the starting dash loop forever. ISO 32000-2 §8.4.3.6 requires nonnegative entries. Vendored hayro patch (5, 6): `usable_dash_pattern` bounds the pieces a pattern can cut the path into, in user space where kurbo dashes: (control-polygon length / period + restarts) x entries per period, where restarts are kurbo's: every MoveTo and ClosePath. Over 250,000 the stroke is drawn solid, as it is for a pattern with a negative or non-finite entry or a period that isn't positive. Measured in release builds: a 250,000-piece stroke takes about 16 ms and 75 MB at its peak (a million took 80 ms and 290 MB). A non-finite phase becomes 0. Every case now renders in about 0.2 s and a few MB. Synthetic regression tests in render. Both were red before the fix (the first killed at 2.7 GB, the second stalled past 20 s), and four mutations each turn them red: the first estimate (one piece per subpath) with a 100k cap runs out of memory, a 100k cap draws a fine legitimate dash solid, dropping the negative-entry check draws `[-1 3]` dashed, and dropping it together with the period check hangs. (The period check alone is defence in depth: hayro-interpret turns 0 entries into 0.01, so a pattern without negative entries always has a positive period.) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
hayro
A crate for rendering PDF files.
This crate allows you to render pages of a PDF file into bitmaps. It is supposed to be relatively lightweight, since we do not have any dependencies on the GPU. All the rendering happens on the CPU.
The ultimate goal of this crate is to be a feature-complete and performant PDF rasterizer.
With that said, we are currently still very far away from reaching that goal: So far, no effort
has been put into performance optimizations, as we are still working on implementing missing features.
However, this crate is currently the most comprehensive and feature-complete
implementation of a PDF rasterizer in pure Rust. This claim is supported by the fact that we currently
include over 1000 PDF files in our regression test suite. The majority of those have been scraped
from the pdf.js and PDFBOX test suites and therefore represent a very large and diverse sample
of PDF files.
As mentioned, there are still some serious limitations, including lack of support for encrypted/password-protected PDF files, blending and isolation, knockout groups as well as a range of smaller features such as color key masking. But you should be able to render the vast majority of PDF files without too many issues.
Safety
This crate forbids unsafe code via a crate-level attribute.
Examples
For usage examples, see the example in the GitHub repository.
Cargo features
This crate has one optional feature:
embed-fonts: See the description ofhayro-interpretfor more information.
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.