Commit Graph
88 Commits
Author SHA1 Message Date
FredApps 2396054e77 Merge main and retain image-only CCITT guards 2026-11-07 15:39:40 +01:00
FredApps 481f66d287 Merge pull request #633 from dotCooCoo/chore/vendor-linesweeper
linesweeper: keep a copy of the crates.io 0.5.0 package in vendor/
2026-11-07 15:37:48 +01:00
FredApps 9fa0111656 Merge pull request #582 from dotCooCoo/fix/hayro-jpx-unused-import
PDF: remove an unused JPX macro import
2026-11-07 15:37:35 +01:00
FredApps 2eac880de8 Merge pull request #580 from dotCooCoo/fix/hayro-interpret-source-digest
hayro-interpret: refresh the local image-reader digest
2026-11-07 15:37:25 +01:00
dotCooCoo f211604aad linesweeper: the copy's README says which workflow a change here does not start
sonarcloud.yml runs on every pull request, so a change only under vendor/linesweeper starts a workflow; it does not start pdf.yml.
2026-11-06 20:51:55 -08:00
dotCooCoo f14cc7f21e linesweeper: keep a copy of the crates.io 0.5.0 package in vendor/
Add the linesweeper 0.5.0 package to vendor/ with no file changed, so
that a fix to it can reach the Craft apps through this repository
without waiting for a linesweeper release. The GitHub repository its
manifest names is archived, and its README says development moved off
GitHub.

Leave out the test font (fonts live in craft-fonts), the logo artwork,
the snapshot images and the package's repository tooling. SOURCE.json
lists each with its hash.

Add a workspace exclude, a [patch.crates-io] entry, SOURCE.json with
the archive hash and per-file hashes, NOTICE and ATTRIBUTION.md rows,
and the package's tests in the backend test script. craft-prepress's
flatten feature now builds against the copy, so Cargo.lock drops the
package's registry source and checksum.
2026-11-06 19:05:18 -08:00
dotCooCoo 6e909558c1 PDF: compile the CCITT row helper only with images 2026-11-03 09:59:55 -08:00
dotCooCoo c8aeab2226 hayro-interpret: refresh the local image-reader digest 2026-11-03 09:05:50 -08:00
dotCooCoo a017817164 PDF: remove an unused JPX macro import 2026-11-03 09:03:01 -08:00
dotCooCoo a5a162a87a PDF: bound cancellation work in coded blocks and stream repair 2026-11-03 07:14:43 -08:00
dotCooCoo f686f6c3f6 PDF: allow opening to be canceled during parsing 2026-11-03 06:33:31 -08:00
Dylan M. Taylor 23738e1650 Merge pull request #532 from storytold/fix/sec-color-space-depth
hayro-interpret: bound how deeply color spaces may nest
2026-11-02 22:40:31 -05:00
Dylan M. Taylor 31e54f7e41 Merge pull request #531 from storytold/fix/sec-xref-entry-cap
hayro-syntax: cap a document's xref map at the PDF object limit
2026-11-02 22:40:16 -05:00
Dylan M. Taylor 80bbc2939c Merge pull request #530 from storytold/fix/sec-jbig2-decode-limit
hayro-syntax: hold a JBIG2 page to the stream's decode limit
2026-11-02 22:39:43 -05:00
Dylan M. Taylor f52b9dc547 Merge pull request #475 from dotCooCoo/fix/hayro-syntax-debug-closing-brace
hayro-syntax: tests check that valid entries survive an unreadable value, and Debug prints the closing brace
2026-11-02 21:57:54 -05:00
Dylan M. Taylor 40bb05042f Merge pull request #474 from dotCooCoo/fix/krilla-gray-black
krilla: a gray stop in a CMYK gradient becomes its exact black (patch 17)
2026-11-02 21:57:50 -05:00
Dylan M. Taylor 9be9a63782 Merge pull request #426 from mockydevs/fix/path-as-rect-open-polyline
pdf reader: a five-point open polyline is not a rectangle
2026-11-02 21:53:06 -05:00
Dylan M. Taylor 69e3708e5c Merge pull request #451 from MarekSpodBiedry/issue-328
hayro-interpret: pad a missing low byte in 16-bit image data instead of panicking (#328)
2026-11-02 21:53:03 -05:00
Dylan M. Taylor 7576553034 hayro-interpret: bound how deeply color spaces may nest
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).
2026-11-02 20:57:05 -05:00
Dylan M. Taylor 52824cf779 hayro-syntax: cap a document's xref map at the PDF object limit
An xref-stream entry can be a single decoded byte, so one xref stream at the
256 MiB decode limit could expand into hundreds of millions of map entries
(several gigabytes) while the document was opened. Every insert into the xref
map now goes through insert_entry, which refuses a new object past 8,388,607
entries (the PDF 32000-1 Annex C limit on indirect objects). This covers
xref tables, xref streams, merges of Prev/XRefStm sections and the repair
scan. Replacing an object already in the map is still allowed.
2026-11-02 20:50:22 -05:00
Dylan M. Taylor 5b4f71f988 hayro-syntax: hold a JBIG2 page to the stream's decode limit
The JBIG2 filter ignored the limit every other filter gets, so a stream
decoded with decoded_within (an output intent's 16 MiB ICC profile in
craft-prepress) could still build a page up to 2^28 one-byte pixels. The
page header's size is now checked against the limit before any output is
allocated: the returned bytes when the stream has no image dimensions,
the 1-bit packed size for an image of known size.
2026-11-02 20:44:12 -05:00
dotCooCoo 7b7b8b4015 hayro-syntax: note that an empty dictionary's Debug output has no braces
DebugStruct::finish() writes the closing brace only when a field was
added, so an empty Dict prints as Dict. Qualify the Debug line in
vendor/README.craft-reader.md and the repair entry in SOURCE.json.
2026-10-31 21:24:56 -07:00
dotCooCoo c04f773850 hayro-syntax: tests check that valid entries survive an unreadable value, and Debug prints the closing brace
Dict's and Array's Debug impls built their output with debug_struct and
debug_list but returned Ok(()) instead of calling finish(), so the
closing brace or bracket was never written: a dictionary printed as
Dict { "B": Boolean(true). Both now end with finish(), as upstream
hayro's do.

The tests from #165 only checked that nothing panics. They now parse a
dictionary with a valid entry on each side of an unreadable value (/#zz,
12-.5, and a dictionary followed by stream with no endstream) and check
that both valid entries come back with their values. The array test
checks that the items before an unreadable item (/#zz, 12-.5) come back
with their values. Both tests also check the Debug output.

Update the local hashes of dict.rs and array.rs and the repair list in
SOURCE.json, and note the change in vendor/README.craft-reader.md.
2026-10-31 21:03:34 -07:00
dotCooCoo b373482f0b krilla: a gray stop in a CMYK gradient becomes its exact black (patch 17)
A gray stop rewritten into a CMYK space took K as 1 - gray in f32, so gray 0.96 wrote K 0.04000002 and the 8-bit gray 128 wrote 0.4980392 rather than 127/255. K is now subtracted from the shortest decimal that writes the gray, and an 8-bit gray n gives (255 - n) / 255. Patch 17, SOURCE.json and the ledger are updated.
2026-10-31 20:35:25 -07:00
.CooCoo fb08be5fa1 krilla: CMYK, gray and spot colors keep their exact inks (patch 17) (#417)
* krilla: CMYK, gray and spot colors keep their exact inks (patch 17)

Gray, CMYK, Separation and DeviceN colors stored each component as a
u8, so every ink was written as a multiple of 1/255: C75 became
0.7490196 and C4 M4 Y4 K4 became 0.039215688.

- cmyk, luma, separation and device_n Color::new_f32 take components
  from 0 to 1 and write them as given. The u8 constructors are
  unchanged.
- Gradient stops rewritten into one DeviceN, Separation or CMYK space
  keep their components.
- Mesh gradients write corner colors at 16 bits a component.

* Tests: the 75% K mesh corner comment gives the asserted 0.749996
2026-10-31 23:30:23 -04:00
.CooCoo 9d17829194 hayro-syntax: skip a dictionary entry whose value does not parse (#165)
Dict validates a dictionary by skipping its values, and the skip accepts
some values that the parser cannot read: a name with a broken # escape
(/#zz), a number such as 12-.5, and a dictionary followed by stream with
no endstream. Dict::entries and Dict's Debug output unwrapped the result
of parsing every value, so either one panicked on such a dictionary.

Port LaurenzV/hayro#1501 to the vendored copy: entries skips an entry
whose value does not parse, and Debug prints the entries. ArrayIter::next
already ends the iteration on such an item here. Add LaurenzV/hayro#1501's
two tests, update the local hashes of dict.rs and array.rs and the repair
list in SOURCE.json, and note the change in vendor/README.craft-reader.md.
2026-10-31 23:30:03 -04:00
MarekSpodBiedry a2c28ae762 hayro-interpret: pad a missing low byte in 16-bit image data instead of panicking (#328) 2026-10-30 16:38:22 +01:00
Don Omondi 67cbb60759 pdf reader: a five-point open polyline is not a rectangle
`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.
2026-10-29 19:10:31 +03:00
aiongg 24a472717a prepress: transparency flattener (flatten feature) (#194)
* 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
2026-10-24 16:15:41 -04:00
aiongg 2ab6049d90 krilla: link annotations and page boxes refuse non-finite geometry (patch 13) (#283)
* krilla: link annotations refuse non-finite or degenerate geometry (patch 13)

Link quad points that are empty or non-finite, link rects and page boxes
that overflow when flipped into PDF coordinates, and XYZ destinations with
a missing page or a non-finite point made krilla panic, which aborts a
WASM app. Document::finish returns InvalidAnnotationGeometry,
InvalidPageBox or InvalidDestination instead. Constructor signatures are
unchanged.

* krilla: error messages format page indices without overflow (patch 13)
2026-10-24 09:38:24 -04:00
aiongg fcbf08de14 Merge remote-tracking branch 'upstream/main' into pdf-reader-sync
# Conflicts:
#	vendor/hayro-syntax/SOURCE.json
2026-10-24 12:11:27 +00:00
Dylan M. TaylorandClaude Opus 5.5 13d766e074 security: bound vendored hayro CCITT rows of unknown height and JPEG 2000 codestream size (#255)
* security: bound vendored hayro CCITT rows of unknown height and JPEG 2000 codestream size

- CCITT: `/Rows 0` decoded to the end of the data; a bit of 2D code can repeat a whole row,
  so a small stream produced gigabytes. Rows are now capped by the output limit (in the
  stream's 1-bit rows) and 2^28 pixels.
- JPEG 2000: the decoder allocated its output from the codestream's own dimensions,
  infallibly. Over 2^28 pixels or the output limit (at least 64 MiB) is refused before
  decoding, and the buffer is reserved fallibly.
- SOURCE.json local hashes updated (ascii_85.rs's had not been after #253).

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

* fix(ccitt): pass the output limit in the columns test

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 04:29:32 -04:00
aiongg d0f4ab75a8 hayro-interpret, hayro-syntax: review fixes for the reader-sync batch
- 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.
2026-10-24 08:07:16 +00:00
aiongg 89a5b2cf2e hayro-interpret: port PdfCraft's ICC profile cache key (#3020)
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.
2026-10-24 07:46:17 +00:00
aiongg d2b25a0dc1 Merge part 5: mesh shading extent
# Conflicts:
#	vendor/README.craft-reader.md
#	vendor/hayro/SOURCE.json
2026-10-24 07:40:58 +00:00
aiongg 197e5137fa Merge part 5: /Default color spaces
# Conflicts:
#	crates/pdf/tests/upstream_raster.rs
#	vendor/README.craft-reader.md
2026-10-24 07:40:52 +00:00
aiongg 4952238c66 hayro-interpret: port PdfCraft's /Default color spaces (#3018)
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.
2026-10-24 07:39:43 +00:00
aiongg a672020a96 Merge part 5: /VE expressions and page transparency group
# Conflicts:
#	vendor/README.craft-reader.md
2026-10-24 07:39:41 +00:00
aiongg 5ff42f2642 hayro-interpret, hayro: port PdfCraft's mesh shading extent (#3296)
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.
2026-10-24 07:38:33 +00:00
aiongg 30ee2f832f hayro: port PdfCraft's page transparency group (#2972)
The page group composites over a transparent backdrop and is then put on the background, so a blend mode over an empty part of the page keeps its color. With a color replacement, and on a plate render, the background stays inside the group as its backdrop, so plate films are as before. Ledger hayro (18); the PdfCraft raster test is adapted in upstream_raster.rs.
2026-10-24 07:38:08 +00:00
aiongg 7c7ec9c8ca hayro-interpret: port PdfCraft's optional-content /VE expressions (#2987)
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.
2026-10-24 07:36:15 +00:00
aiongg c45caf2cd1 Merge part 4 (syntax)
# Conflicts:
#	vendor/README.craft-reader.md
2026-10-24 07:33:56 +00:00
aiongg d118f471b8 hayro-syntax: port PdfCraft's unfiltered inline-image length (#3107)
From PdfCraft #3438 with its look-ahead bound (01af6cba3): an inline
image without a filter ends at the byte length its /W, /H, /CS, /BPC
and /IM give when white space and EI follow there, so an EI inside its
samples does not end it. Anything else keeps the EI look-ahead. A
computed end past the content window waits for more data only while it
fits within MAX_DECODED_STREAM.

Tests: PdfCraft's six content::tests unit tests.
2026-10-24 07:33:40 +00:00
aiongg d2ffefcddf hayro-syntax: port PdfCraft's JP2 palette without /ColorSpace (ledger P5)
A JPX image whose dictionary has no /ColorSpace, or one that does not resolve, takes its colors from the JP2 palette instead of drawing its raw indices as gray. Resolved palette colors are 8-bit samples whatever the index precision. From PdfCraft #3184, with its three jpx unit tests.
2026-10-24 07:32:06 +00:00
aiongg d9d02c42be hayro-syntax: port PdfCraft's trailer /Prev reference (ledger 24)
A trailer /Prev written as N G R is followed: it is the offset the referenced object holds when the section's own entries place it, and otherwise offset N. Each section's entries are read before its Prev is followed. From PdfCraft #2400, with its two xref unit tests; the local xref-stream bounds are kept.
2026-10-24 07:26:55 +00:00
aiongg 647156d354 Merge part 3 (fonts and text), #266
# Conflicts:
#	vendor/README.craft-reader.md
#	vendor/hayro/SOURCE.json
2026-10-24 07:12:51 +00:00
aiongg 860f437063 Merge part 2 (paint), #265 2026-10-24 07:12:35 +00:00
aiongg 2cbbf81b1d hayro-interpret: a transparent plate paint stays transparent when a stencil drops pattern opacity 2026-10-24 06:57:41 +00:00
aiongg 429258979d hayro-interpret, hayro: port PdfCraft's font and text fixes (ledger P7, 49, 50)
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.
2026-10-24 06:55:26 +00:00
aiongg f4f0e88985 hayro-interpret, hayro: port PdfCraft's paint fixes (ledger 51, 46, P4, 36)
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.
2026-10-24 06:39:40 +00:00