The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
XFA forms aren't read yet, and opening one gave no hint: a dynamic XFA form
showed only its "Please wait…" placeholder page, and a static one looked like
an ordinary form though Acrobat shows its XFA data in preference to the
fields that get filled.
- render: DocInfo.xfa (Static / Dynamic) from /AcroForm /XFA; dynamic when
/NeedsRendering is true or there are no fields. Exported for doc_info
(automation and CLI) as "xfa".
- ui: the notice bar explains each case (above the form-fields notice); a
static form keeps its Highlight fields button.
- Tests: tests/xfa.rs (dynamic with and without /NeedsRendering, static,
ordinary forms, a malformed /XFA). Parity notes for form.xfa-static and
form.xfa-dynamic (still planned: this explains XFA, it doesn't support it).
Reported by @Sudachi-dev.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The nightly fuzz job found a Type 1 font that hung hayro: read-fonts 0.39
(through skrifa 0.42) looked ahead after every integer by parsing the next
token, which looked ahead again, recursing through a whole run of numbers.
A long run hung; 20,000 numbers overflowed the stack and aborted the
process. Upstream fixed the tokenizer; the vendored hayro-interpret now uses
skrifa 0.47 (read-fonts 0.44), with its one API change. The finding finishes
in 0.5 s. Synthetic regression test (it aborts with the old skrifa).
Corpus sweep: 0 regressions; text oracle unchanged (median F1 0.980, mean
0.866, every per-file score identical).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The nightly fuzz job found a JBIG2 symbol dictionary that decoded for
minutes: each bitmap side may be 65535, and up to 65535 symbols are decoded
pixel by pixel. Vendored hayro-jbig2 0.3.0 with two bounds: at most 2^28
pixels per bitmap (also the page bitmap, halftone grid and grayscale image),
and at most 2^26 symbol pixels per dictionary. The finding now finishes in
about a second. Synthetic regression test (a 65535 x 65535 page and region
behind a small /Width and /Height); the corpus sweep shows 0 regressions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every hang the nightly fuzz job found (11 findings, 8 source files) now
finishes in 0.1-2 s; each cause has a synthetic regression test that times
out without its fix.
- cos (7 findings, our own parser): a stream whose /Length pointed at
itself, with no endstream, made every parse attempt fail and each attempt
reload the same object: ~2^32 parses. Objects being loaded are a seen-set,
/Length values are memoised per load, and the shifted-header retry only
runs when the header is shifted.
- hayro-syntax (3): deciding where an inline image's data ends re-parsed the
rest of the stream, nesting through every inline image; 50 images ran for
over ten minutes. The look-ahead no longer nests and is bounded (256).
- hayro-interpret (1): a Type 3 glyph showing eight glyphs of its own font,
sixteen deep. Nested paints share a per-page budget (50,000).
The pdf.js corpus sweep matches its baseline (963 ok, same 20 known).
xtask check writes its report under CARGO_TARGET_DIR too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second full nightly fuzz run on main found two more:
- hayro-syntax computed a Flate/LZW predictor row as columns × bits in
wrapping arithmetic: /Columns 9223372036854775807 asked for 2^61 bytes and
aborted on any machine. Row widths are now saturating usize (colors × bits
also overflowed u8), a zero width is refused (it divided by zero), and a
row longer than the data is not allocated (empty output, as before).
- hayro stroked /LW 9223372036854775807 as given, and stroke expansion
allocated 10 GB. Widths are clamped to 4 × (canvas width + height) device
pixels, which covers the same pixels.
Both findings now run in 8 MB. Synthetic regression test in render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first full nightly fuzz run on main (now that the runner survives) found
two 4 GiB allocations, caught by the 4 GiB child cap:
- an inline stencil mask claiming /W 4294967295 was decoded in hayro-interpret
before the renderer's MAX_IMAGE_PIXELS check could skip it (locally 9.6 GB,
42 s). Vendored patch image_size_ok: images and masks over 2^28 pixels are
not decoded.
- a CCITT image whose /DecodeParms claimed /Columns 4294967295 (with a small
/Width) sized the decoder's line buffers (locally 4.3 GB). Vendored patch
ccitt_size_ok in hayro-syntax: at most 2^20 columns and 2^28 pixels.
Both findings now run in 7 MB. Synthetic regression test in render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fuzzed inputs drove one check-one process to 2-6 GB (the 16 GB CI runner
running two of them was killed every night). Three causes, each with a test:
- lopdf (bootstrap inspector) sized PNG predictor rows from /Columns before
reading data: /Columns 4294967295 allocated two 4 GiB rows. Vendored lopdf
0.45.0 refuses rows longer than the data (and uses saturating row widths);
its load-time stream decoding also gets a 256 MiB limit (it had none).
- hayro sized a tiling pattern's cell pixmap from /XStep x scale, bounded only
by u16: a step far beyond /BBox allocated 2 GB. The vendored patch keeps
the cell within 3000 px a side, lowering the resolution instead.
- cos decoded cross-reference streams up to the general 1 GiB limit; they now
stop at what 8,388,607 objects' rows can hold (~200 MB at most), and the
section is reconstructed (logged) instead.
On the nightly's seed (41) the worst captured input fell from 6.2 GB to 6 MB,
and a 300 s fuzz run had no process above 300 MB (before: many at 2-5 GB).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The text reading order uses `let … else`, `while let` and `get_mut` instead of `expect`, and
named destinations bind their key with an or-pattern instead of `unreachable!`. The crate now
denies the panic lints.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stable clippy 1.99 denies chunks_exact/chunks_exact_mut with a constant
size (32 sites), which failed the test job on main and every open PR.
Behaviour unchanged; MSRV 1.90 has as_chunks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>