* pdf inspection: count pending text clips and drop page content after reading
- hayro-interpret: state_bytes counts the pending text-clip outlines a
saved state copies, so MAX_SAVED_STATE_BYTES bounds them too.
- plate-test structure: read each page through a ContentReader into a
buffer dropped after the page, instead of page.operations(), which keeps
every page's decoded content for as long as the document.
* address review: cap page content read at the 256 MiB decode limit
* fix the saved-state test: start the clip outline with a MoveTo
Indexed, Separation, DeviceN, Pattern and an ICCBased /Alternate each build
their base space with a recursive ColorSpace::new call, and each level can be
a fresh indirect object. A long chain of indirect /Indexed arrays therefore
recursed once per object and could exhaust the stack.
The depth is now passed down through every child construction and a space
nested 8 or more levels deep is refused (the deepest legitimate nesting,
Pattern over Indexed over ICCBased with an /Alternate, is four).
`path_as_rect` in the vendored hayro-interpret took any five-element path whose
points reached all four corners of its bounding box for that box. An open
polyline of one MoveTo and four LineTo passes that test when its ends sit on
the corners, so it was drawn as a closed rectangle and a clip of that shape was
dropped. VectorCraft #2920: an open zigzag in an .ai file opened as its
bounding box.
Port PdfCraft #3327 (hayro-interpret patch 35): a path is a rectangle only when
its four points are distinct, each step runs along one axis, and the last point
returns exactly to the first, closed with ClosePath or a fourth line.
Tests: `rectangles` in crates/pdf/tests/interpretation.rs (4 of its 5 tests
fail without the change) and `context::path_as_rect_tests` in the vendored
crate. SOURCE.json and the reader ledger record the change.
* hayro-interpret: ink colors, overprint and font bytes for the flattener
Local patches for craft-prepress's PDF-in adapter, labeled Craft patch
(flatten) and listed in vendor/README.craft-reader.md:
- Device::set_overprint and Device::set_isolated, called where the
blend mode is set and after an isolated group is pushed.
- Color::ink_paint, ColorSpace::ink_space and ink_values: colors and
image samples as the inks they print, without converting to RGB.
RasterImage::with_inks gives native samples with Decode and Matte.
- OutlineFontData::embedded and index, and the CFF bytes of Type1C
fonts, so text can be embedded again.
- The sh operator ignores /Background (ISO 32000-2 8.7.4.5.1).
* plate-test: flattened-page checks, image alpha and scenes F01, F02
- report::run_flattened separates each scene's page after a caller's
flattener as well as the original, and structure::transparency lists
what transparency a page still has. plate-test doesn't depend on the
flattener.
- Scene images take an alpha channel, written as /SMask.
- F01: a partly transparent image over a flat fill; where its alpha is
0 the fill keeps its exact inks (vectorcraft#2160).
- F02: art hidden under opaque fills leaves no trace after flattening
(vectorcraft#2161).
* prepress: transparency flattener (flatten feature)
craft_prepress::flatten turns PDF pages with transparency into pages
without it, keeping inks, overprint and live text, for PDF/X-1a and
other output that can't carry transparency.
- pdf: reads a page into a display list in inks through hayro-interpret:
paths, outlined strokes, clips, groups, soft masks, overprint, spot
and DeviceN colors, images in their own color space, gradients,
rasterized mesh shadings, tiling patterns and glyph runs.
- font: prepares the source fonts for embedding again (TrueType,
OpenType, CFF wrapped as OpenType); others fall back to outlines with
ActualText.
- geom: planar regions with the shapes that cover each one (linesweeper).
- compose: composites each region per ink (separable and non-separable
blend modes, isolated and knockout groups, overprint per plate as ISO
32000 defines it); regions under images, gradients and soft masks are
marked complex.
- raster: complex regions as DeviceN images through plate mode, grid
snapped with a one-pixel outset; flatten_page.
- write: writes the result with the vendored krilla; flatten_document
writes every page into one PDF with the output intent, page boxes and
fonts shared across pages when their bytes match.
The regions and the compositor are ported from VectorCraft (commit
c2b15982, crates/pathops and crates/engine/src/cmd/flatten.rs), credited
in the crate README. scripts/plate-check.sh --flatten separates every
scene's flattened page with Ghostscript (local only). CI runs
craft-prepress with all features.
* krilla: DeviceN /Colorants from PDF 1.3, NChannel attributes from 1.6 (patch 11)
* prepress: flatten_document output PDF version and PDF/X version
* prepress: format the PDF/X-1a flatten test
* krilla, craft-prepress: write GTS_PDFXConformance next to GTS_PDFXVersion
PDF/X-1a:2003 files carry GTS_PDFXVersion (PDF/X-1:2003) and
GTS_PDFXConformance (PDF/X-1a:2003). Krilla patch 12 adds
SerializeSettings::pdfx_conformance, and FlattenDocumentOptions passes it
through.
* krilla, craft-pdf, craft-prepress: carry the modification date over
Krilla patch 13 adds Metadata::modification_date, written as /ModDate and
xmp:ModifyDate in place of the creation date. craft-pdf's Metadata gains
modified, and flatten_document takes it from the source's ModDate.
* craft-prepress: count the print annotations a read leaves out
ReadReport::dropped_annotations counts the annotations a printer would
paint (Print flag, not Hidden, with a normal appearance) when
ReadOptions::annotations is false; FlattenTotals sums it.
* craft-prepress: degenerate groups rasterize without a soft mask
A group the region sweep fails on is clipped to its members' boxes, which
no other group's items reach, so the paper is its only backdrop. Its
image is written opaque, as the plates of the items over paper, and the
page keeps no transparency for PDF/X-1a.
* craft-prepress: write untouched JPEG images as they are
A DCT-encoded DeviceGray or DeviceCMYK image that krilla's JPEG
passthrough writes with the same meaning (8 bits, no decode parameters,
the default gray Decode or the inverted Adobe CMYK one) keeps its data
in ImageItem::jpeg, and the writer embeds it unchanged when the image
has no soft mask. Other images are still 8-bit Flate samples.
* craft-prepress: keep group members with no transparency above them
Groups are transitive by bounding box, so a background frame put all the
body text on it under a transparent object far away, and the text came
back as outlined fills. A member is now drawn as it is when it is opaque
and, in every region its shapes are in, only members kept this way are
above it. The region fills under it are the composite of the other
members, and it is drawn over them in paint order.
* prepress: keep the backdrop under elements of a knockout group
* prepress: rasterize complex regions thinner than a point
* prepress: mark flatten errors, reasons and reports non_exhaustive
* prepress: bound the items a tiling pattern fill expands into
Each of up to MAX_TILING_CELLS cells copied the whole cell content, and a
cell can hold many items or an expanded pattern of its own. A fill whose
cells would hold more than MAX_TILING_ITEMS items in all, counting groups,
masks and nested patterns, is read as an image.
* prepress: stroke solid past the dash budget
A dash pattern that cuts a stroke into more than 250,000 pieces is outlined
solid, with the estimate hayro's renderer uses.
* prepress: bound the sampling and memory of rasterized tiling patterns
Each pixel sampled every cell over it, which for a BBox far larger than
its step is millions of cells per pixel; past MAX_TILING_CELLS cells per
pixel the fill paints nothing, and below it the image resolution drops to
keep the samples within MAX_TILING_SAMPLES. The canvases also hold a value
per page ink, so their pixel limits shrink with the ink count to keep the
memory of CMYK canvases, and the canvas size uses checked arithmetic.
* prepress: mark the page reader's error and report non_exhaustive
* prepress: format the tiling tests
* prepress: CodeRabbit fixes: no nonzero overprint mode for images and shadings, count dropped tilings, widen the hhea ascender
* prepress: refuse DeviceN spaces past the output's colorant limit and regions past the separated spot inks
- Patch-mesh triangles are rebuilt after inflation, so the stored dot products match
the vertices.
- A stencil image restores the fill alpha itself when the saved-state bound skipped
its save.
- A resolved JP2 palette decodes to 8 bits whatever /BitsPerComponent says.
Cache the color space of an /ICCBased profile by its stream object, and on
a miss by its dictionary key and raw bytes, instead of the dictionary's
bytes alone, so two profiles with the same /N and /Length each draw through
their own profile. InterpreterCache::icc_profile_reads counts profile reads.
PdfCraft's two raster tests are adapted in tests/upstream_raster.rs.
A resource dictionary's /DefaultGray, /DefaultRGB and /DefaultCMYK
replace the device spaces that color operators, cs/CS, images and
shadings select (ISO 32000-2 8.6.5.6). Decoded images and shadings are
cached per default.
Defaults follow this tree's resource scoping (a form with /Resources
sees only its own), and they do not apply on a plate, so device color
separates onto the same inks as before. PdfCraft's raster tests are
adapted in upstream_raster.rs, with a plate test in plates.rs.
EncodedShadingPattern::extent records the device pixels a mesh's triangles
touched when it was encoded. set_paint returns PaintSetup: the mesh texture
covers that extent with Extend::Pad instead of the whole path, and a mesh with
no triangle on the canvas paints nothing. Output is unchanged; the knockout
erase pass and plate drawing are untouched. Adds hayro's mesh_extent_tests.
An optional content membership dictionary with a visibility expression (/And, /Or, /Not over listed groups) shows its content when the expression is true, ahead of /OCGs and /P. Malformed expressions, or ones nested deeper than 32 levels, fall back to /OCGs and /P. Ledger hayro-interpret (40); the PdfCraft raster test is adapted in upstream_raster.rs.
From PdfCraft 0b670caf4:
- P7 (PdfCraft #3109, #3116): text in mode 7 (clip only) installs its
clip and reaches the device as GlyphDrawMode::Clip, which the hayro
renderer does not paint, so extraction that leaves out invisible OCR
text keeps a heading drawn through its clip. Test:
clipping_only_text_reaches_the_device_apart_from_invisible_text in
crates/pdf/tests/upstream_text.rs.
- (49) (PdfCraft #2949 and 09668ce88): a TrueType post table version
1.0, 2.0 or 3.0 written with its 16-bit halves swapped is accepted,
and a swapped 2.0 table's glyph names are read. Unit test on a
synthetic sfnt in font::blob::tests.
- (50) (PdfCraft #3958): the CFF glyph-name map is built once on first
use and shared between clones instead of scanning the charset per
code. PdfCraft's unit test comes with it.
From PdfCraft 0b670caf4:
- (51): the saved-state accounting (state_bytes, the saved_bytes update,
num_states) uses saturating sums, so a huge dash array or clip list
stays over the 64 MiB input bound on 32-bit targets instead of
wrapping.
- (46) and the hayro stencil paint (PdfCraft #2977): an image XObject's
group applies the fill opacity, so a stencil mask's paint no longer
carries it again (a mask at ca 0.5 was drawn at a quarter), a pattern
paint drops the opacity it captured at scn (Pattern::without_opacity),
and the mask is painted with the fill paint rather than the stroke
paint.
- PdfCraft #3246 (unledgered): a shading pattern takes the alpha current
at paint time (Pattern::set_alpha), so a gs after scn or SCN still
applies; plate paints take it the same way.
- (36) (PdfCraft #3035): a mesh pixel whose corner no triangle holds
strictly takes the triangle that covers its center, edges included
(Triangle::covers), so shared edges leave no seam. Marked
"PdfCraft patch (#3035)": the plate-mode code already uses (35) and
(36) for #2770's numbers.
Tests: the PdfCraft raster tests for (46), #3246 and (36), adapted in
crates/pdf/tests/upstream_raster.rs.
Conflicts: NOTICE, vendor/README.craft-reader.md and the hayro-interpret and
hayro-syntax SOURCE.json keep the ec0a965 provenance and add hayro-cmap
(a2ec27b) and hayro-postscript (crates.io 0.1.0). type0.rs keeps PdfCraft's
flat sample array with its 64-bit largest sample and main's 32-bit test.
Local sha256 values recomputed for the merged files.
* hayro-syntax: port PdfCraft's syntax fixes (ledger 17, 19, 20, 23, 4-ext, #3565, base_dimensions floor)
From PdfCraft a2ec27b47:
- (17), (19): a CCITT image with /Rows 0, or with fewer /Rows than its
/Height, decodes to the image height instead of zero or /Rows rows.
- (20): spaces or tabs between the stream keyword and its line break are
skipped, so the stream data starts after the line break.
- (4) extension: an inline image's EI followed by a delimiter (EI%, EI[)
ends the image; only a regular character makes it part of a longer token.
- (23): bytes above 0x7F between two EI candidates don't reject the first;
the operator look-ahead decides.
- #3565: a doubled sign (--280, +-280) is read and skipped as one number.
- Page::base_dimensions keeps a one-point floor after /UserUnit instead of
one raw unit.
Also a local bound: a CCITT /Columns past u32::MAX is refused instead of
wrapping to a small width that passed ccitt_size_ok.
Tests: content and number unit tests from PdfCraft, a stream unit test, a
synthetic page test for the floor, a CCITT /Columns unit test, and the two CCITT raster tests in
crates/pdf/tests/upstream_raster.rs.
* hayro-interpret: port PdfCraft's text fixes (ledger 30, 44, P2, P6, P8, 26-ext)
From PdfCraft a2ec27b47:
- (30): WinAnsi 0xA0 and 0xAD and MacRoman 0xCA read as an ordinary space
and hyphen (Annex D.2 gives them those glyph names) unless /Differences
names nbspace or sfthyphen.
- (44): a /ToUnicode /Identity-H or /Identity-V name reads two-byte codes
as their Unicode values; controls and lone surrogates give no text.
- P6: glyph names outside the Adobe Glyph List (f_i, hyphen.case,
uniXXXXXXXX) read by the glyph list specification's full mapping; names
that only number a glyph (C39, c251) give no text.
- P8: a substitute font whose charmap lacks a spacing character (U+2002 and
similar) draws its ordinary space instead of .notdef.
- P2: a Type 3 glyph's /FontBBox is transformed to device space before it
bounds the glyph's clip and shadings, so shaded glyphs draw at every
position.
- (26) extension: comma-below letters (s, t, k, l, n, r, G with comma) in
the standard-font stand-ins are the base letter with the font's comma,
scaled and placed under the baseline.
Tests: crates/pdf/tests/upstream_text.rs for (30), (44) and P6; the Type 3
shading and comma-below raster tests in crates/pdf/tests/upstream_raster.rs.
P8's PdfCraft test needs a Japanese substitute font and is not ported.
* hayro-syntax: refresh SOURCE.json local hashes for Cargo.lock, ascii_85.rs and filter/mod.rs
They changed in #241 and #253 without their local sha256 values.
PdfCraft ledger hayro-interpret (31), its overflow part: a Type 0 function
with /BitsPerSample 32 computed 2_u32.pow(32), which overflows (a panic with
overflow checks on). The maximum is now ((1u64 << bps) - 1) as f32. The flat
sample array that (31) also brings is a performance change and is not part of
this commit. Test: function::type0::tests::thirty_two_bit_samples_do_not_overflow.
PdfCraft's other reader limits are already covered here: ASCII85 output is
bounded by the decode limit (#253), object-stream indexes are parsed once with
checked offsets, and direct objects nest at most 64 levels (PdfCraft: 128).
* pdf: keep a CID font's /W2 table past an out-of-range first CID; vendor hayro-cmap from PdfCraft
- hayro-interpret: a /W2 run whose first CID is past 65535 is skipped, as in
/W. A run starting at 4294967295 overflowed the CID counter and discarded
the whole table. Unit test in font::cid.
- hayro-cmap 0.1.0 is vendored from PdfCraft a2ec27b47 with its malformed-CMap
patch (PdfCraft #3177): bad bfchar/bfrange entries, an unresolvable usecmap
and an unparseable tail are skipped instead of failing the whole CMap. The
crate's tests come with it. Wired up like the other vendored Hayro crates
(workspace exclude, patch entry, SOURCE.json, backend test script); NOTICE,
ATTRIBUTION.md and vendor/README.craft-reader.md record the source.
* hayro-syntax: refuse an xref-stream subsection that runs past object number u32::MAX
The object number was start + i in unchecked u32: a subsection starting near
u32::MAX overflowed (a panic in debug builds, a wrong object number in
release). Covered in xref::tests::xref_stream_entries_need_backing_bytes.
* hayro-cmap: a binary usecmap counts against the nesting limit; /W2 checks the CID first
- hayro-cmap: a binary CMap's usecmap passed its own depth to the nested
parse, so a self-referencing binary CMap recursed until the stack
overflowed. It now counts like a text usecmap. Test:
self_referencing_binary_usecmap_stops_at_the_nesting_limit.
- hayro-interpret: the /W2 first-CID check runs before the run's metrics
are read, so an incomplete run past CID 65535 no longer discards the
rest of the table.
* hayro-syntax: refuse xref-stream object numbers past i32::MAX
ObjectIdentifier stores an i32; a number between i32::MAX and u32::MAX
was cast to a negative object number.
- hayro-syntax ascii_85.rs: main's decode-limit version (same bound as
the pdfcraft ec0a965 copy, plus its test).
- hayro-interpret and krilla SOURCE.json: source fields from pdfcraft
ec0a965 / krilla 0.8.2, sha256 recomputed for merged files.
- krilla PNG decode cap from main recorded as patches/11-png-decode-cap.patch
with its ledger row and README section.
* security: bound vendored hayro xref, object-stream, JBIG2 and graphics-state work; krilla PNG size
hayro-syntax:
- xref-stream subsections must be backed by decoded bytes: zero /W widths and counts
longer than the data are refused before inserting (CodeRabbit CWE-400, xref.rs:962)
- object-stream offset tables are parsed once per document and cached in Data
(CodeRabbit CWE-407, xref.rs:1079)
- embedded JBIG2 pages over 2^28 pixels are refused before allocating, with checked,
fallible reservation (CodeRabbit CWE-789, filter/jbig2.rs:34)
- a JBIG2Globals stream with an image filter is ignored, so globals cannot recurse
(CodeRabbit CWE-674, filter/jbig2.rs:20)
hayro-interpret:
- at most 4096 saved graphics states / 64 MiB retained; dash arrays over 256 entries stroke
solid (CodeRabbit CWE-400, context.rs:226)
krilla:
- PNGs implying more than 1 GiB of decoded output are refused at Image::from_png and in
the deferred decoder; the buffer is reserved fallibly (CodeRabbit CWE-789,
graphics/image.rs:565)
SOURCE.json local hashes updated; patches recorded in vendor/README.craft-reader.md and
vendor/krilla/README.craft.md.
* vendor: fix SOURCE.json local hashes; reserve krilla's colour/alpha split buffers fallibly
- SOURCE.json: only the local sha256 changes; the source/upstream hashes of the patched
files are restored (CodeRabbit review)
- krilla handle_u8_image/handle_u16_image reserve their split buffers with try_reserve_exact
and return an error, so a valid image under the 1 GiB decode cap cannot abort the process
on the second allocation (CodeRabbit review)
A luminosity soft mask whose group is DeviceCMYK draws the group's four process plates over the backdrop and applies 1 - min(1, 0.3c + 0.59m + 0.11y + k) (ISO 32000-2 §10.4.2.2), so K 100% hides the masked object. Spot colorants on those plates paint through their alternate space (Plate::spots_as_process).
Bring storytold/pdfcraft#2974 (source commit
bae69819594a6385a027fec1f67e6b3c36c259b4) onto the vendored
hayro-interpret and hayro. Knockout transparency groups (/K true) knock
out: while one is innermost, fills, strokes, glyph outlines and shadings
are drawn with Compose::Copy, weighted by coverage. An unmasked image's
rectangle and a nested group's shape are cleared before their layer
composites; the nested group's shape comes from an erase pass over its
content. Masked images and stencils composite as before.
Code comments label the change "Knockout patch (pdfcraft #2974)": the
pull request numbers it hayro-interpret patch 39 and hayro patch 19,
which pdfcraft may renumber. The erase pass resolves resources with
Resources::from_scope, as this tree's form drawing does.
crates/pdf/tests/knockout.rs adapts the pull request's test to the
raster path (without its preview case: craft-pdf has no preview mode).
A plate-mode test checks that a knockout group of two 50% squares
separates with only the top square in the overlap. Options documents
that color_replacement also maps plate films. Ledger, NOTICE and the
source manifests are updated.
Co-authored-by: mtlcl410 <2368091+mtlcl410@users.noreply.github.com>
CmykSimulation converts process colour to an output intent's CMYK
through a color::CmykConversion: sRGB colours, and CIE XYZ relative to
the D50 PCS white (color::PCS_WHITE) for Lab and ICCBased colours.
CmykSimulation::new keeps the moxcms conversion from pdfcraft #2770,
which takes XYZ on through its ProPhoto hub; with_conversion takes a
caller's, so a crate can separate with its own colour management.
crates/pdf/tests/plates.rs adapts #2770's plate tests to the raster
path: process, spot, DeviceN, All/None, ICC CMYK, gray and RGB colours,
images, shadings, uncoloured patterns, soft masks, OP/op/OPM, the ink
sink, output-intent simulation with #2770's synthetic ICC profiles, and
a caller's conversion. The ledger and NOTICE name the pull request.
Co-authored-by: Guillem Hermida <66697068+guillehr2@users.noreply.github.com>
Bring the vendored interpreter part of storytold/pdfcraft#2770 (Output
Preview, source commit 6fcf2a4aca29e943e28cb69017ff8f87d00656c6) onto
the ec0a965 snapshot. It applies without conflicts.
InterpreterSettings::plate draws one ink's separation: every paint
draws that ink's tint as a gray film, by the colour space's ink use and
tint. The graphics state reads OP, op and OPM, and an overprinting
paint that doesn't specify the plate's ink leaves the plate as it is
(ISO 32000-2 8.6.7). InterpreterSettings::ink_sink reports the spot
inks paints use, and color::CmykSimulation converts process colour to
an output intent's CMYK profile.
PdfCraft's separations.rs, Output Preview UI and automation are not
part of this port. The ledger in vendor/README.craft-reader.md names the
pull request, author and source commit.
Co-authored-by: Guillem Hermida <66697068+guillehr2@users.noreply.github.com>
Bring vendor/hayro, hayro-interpret, hayro-syntax and hayro-jbig2 to
storytold/pdfcraft ec0a965074f376a5bf610435948e4e5184bf3299 (main,
2026-10-19), from 7437bde. The local patches (per-document decode
limits, incomplete-content diagnostics, Hayro 0.8 resource scopes,
nesting cap, iterative page tree, TJ fill order, transfer rounding,
safety repairs) are carried by a three-way merge without conflicts.
The re-sync includes transparency group /I and /K reading (pdfcraft
#2027), patches 28, 29 and 31-34, and the reader fixes listed in
vendor/README.craft-reader.md. SOURCE.json manifests, NOTICE and the
craft-pdf README name the new source revision.