201 Commits
Author SHA1 Message Date
aiongg 0c748e34a8 fix(render): bound xref streams, JBIG2 referred-to counts, saved graphics states and /W2 runs (#4350)
* fix(render): bound xref streams, JBIG2 referred-to counts, saved graphics states and /W2 runs

Input bounds and crash fixes in the vendored hayro crates, ported from
the snapshot craft-libs carries (storytold/craft-libs#176, #239, #241):

- hayro-syntax: an xref stream subsection must be backed by its decoded
  data. All-zero /W widths and a count longer than the data are refused
  before any entry is inserted; a tiny stream with a large /Size or
  /Index count inserted that many entries.
- hayro-syntax: a subsection running past object number u32::MAX is
  refused; start + i overflowed.
- hayro-syntax: the xref stream type field is read big-endian at its
  full /W[0] width, and /W[0] over 8 bytes is refused like /W[1].
- hayro-jbig2: the long-form referred-to segment count is capped at
  u16::MAX before its retention bytes are read; the 29-bit count went
  straight to Vec::with_capacity (about 2 GiB) and aborted the process.
- hayro-interpret: at most 4096 saved graphics states holding at most
  64 MiB together. A q past either limit is counted but not copied, and
  its Q still removes the clips added since.
- hayro-interpret: a /W2 run whose first CID is past MAX_CID stops there,
  as /W does; a first CID of 4294967295 discarded the whole /W2 table.

Each patch is marked in the source, has a regression test in its crate,
and is listed in vendor/README.md.

* fix(render): a binary usecmap counts against the CMap 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 (ledger hayro-cmap 2).
- hayro-interpret: the /W2 first-CID check runs before the run's metrics
  are read, so an incomplete run past MAX_CID no longer discards the rest
  of the table (ledger 52).

* fix(render): 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 (ledger hayro-syntax 26).
2026-10-23 22:17:12 -04:00
Aleksander HelgakerandClaude 71efe25d02 docs: one row per crate in the vendor README table (#4015)
Parallel PRs had each copied their crate's row and appended their patch as
the next number, leaving two hayro rows, four hayro-syntax rows and three
lopdf rows that disagreed with one another.

- hayro: the rows were identical except item (10), the native-gradient
  backport in one and the `/Interpolate` pre-resize (#624) in the other.
  The `/Interpolate` row is kept and the gradient backport is appended as
  (23). The row already skipped (21); that gap is left as it was.
- hayro-syntax: the 24-item row is kept (it alone has the delimiter
  extension of (4)); item (7) takes the wording that names ASCII85 (its
  `decode_limit` patch is in filter/ascii_85.rs); the direct-object nesting
  cap, numbered (14) in its own row, is appended as (25).
- lopdf: the six-item row is kept; the others were its prefix.

A blank line after the hayro-interpret row also ended the table, so the
crates after it rendered as loose text; it is removed. All 368 patch and
test entries in the old rows are still present, and each moved patch has
its `PdfCraft patch:` marker in vendor/.


Claude-Session: https://claude.ai/code/session_012rLxgUTc4atMXXgQEuJxh8

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-23 10:41:57 -04:00
aiongg 2c13fd9373 Merge r2 track t16: mixed: automation-mcp, crate:misc, crypt-security, export-convert, optimize, preflight-pdfa, save-io
# Conflicts:
#	ATTRIBUTION.toml
#	contributors/people.toml
#	crates/automation/src/comments.rs
#	crates/edit/src/tests.rs
#	crates/engine/src/tests.rs
#	parity/acrobat-features.toml
2026-10-22 02:23:58 +00:00
aiongg 8bb2eef740 Merge r2 track t13: cos-parse, vendored-crate fixes
# Conflicts:
#	ATTRIBUTION.md
#	crates/annot/src/tests.rs
#	crates/forms/src/tests.rs
#	crates/organize/src/tests.rs
2026-10-22 02:21:53 +00:00
aiongg 69e755c439 Merge r2 track t08: fonts
# Conflicts:
#	crates/automation/src/tools.rs
#	vendor/README.md
2026-10-22 02:20:50 +00:00
aiongg 4d5025c6da Merge r2 track t07: text-extract-select
# Conflicts:
#	crates/edit/src/tests.rs
#	crates/render/src/raster.rs
#	vendor/README.md
2026-10-22 02:20:03 +00:00
aiongg ded4bee6b8 Integration: #2974 tiling early return in set_paint returns PaintSetup::Clip (#3296's return type) 2026-10-22 02:15:20 +00:00
aiongg e22767f706 Integration: #2974 knockout erase pass reads form resources through Resources::for_form (#3637 made them optional) 2026-10-22 02:15:20 +00:00
aiongg b2f913a7b6 Merge PR #3438: fix(render): use unfiltered inline image byte length before EI heuristic (#3107)
docs/roadmap.md: main's M2 notes with the PR's sentence appended
ROADMAP.md: the PR's progress-log line moved to the top (newest first)
2026-10-22 01:52:27 +00:00
aiongg ec6a8166bb Integration: #3020 number its hayro-interpret patch 48 (40 is #2987's) 2026-10-22 01:51:55 +00:00
aiongg e2d3f30c63 Merge PR #3020: fix(render): draw each ICC profile through itself when two have the same stream dictionary (vendored hayro-interpret patch 40)
vendor/README.md: main's row plus the PR's hayro-interpret patch and tests, numbered 48 (40 is #2987's)
2026-10-22 01:51:47 +00:00
aiongg ad23e6e6e9 Merge PR #3184: fix(render): apply the JP2 palette of a JPX image with no /ColorSpace (#3179) 2026-10-22 01:51:22 +00:00
aiongg e61515f324 Merge PR #2987: fix(render): a visibility expression decides what an OCMD shows (#2986)
vendor/README.md: main's row plus the PR's hayro-interpret patch 40 and its test
2026-10-22 01:51:22 +00:00
aiongg b84121e978 Merge PR #3327: Render: a five-point polyline is no longer painted as its bounding box
vendor/README.md: main's row plus the PR's hayro-interpret patch and tests, numbered 47 (35 is #3296's)
2026-10-22 01:51:10 +00:00
aiongg 6fc4a18d9c Merge PR #2972: fix(render): blend modes over an empty part of the page keep their colour (#2970)
crates/render/src/raster.rs: main's tests plus the PR's #2970 test after the knockout test
vendor/README.md: main's rows with the PR's rewording of hayro patch 18
2026-10-22 01:50:51 +00:00
aiongg b85b0d2dae Integration: #2974 number its hayro patch 22 (19 is #3296's) 2026-10-22 01:50:11 +00:00
aiongg 554d9105f3 Merge PR #2974: fix(render): knockout transparency groups knock out (#2971)
crates/render/src/raster.rs: both new tests (#2975 stencil masks, #2971 knockout groups)
vendor/hayro/src/renderer.rs: set_paint returns main's PaintSetup; the PR's knockout erase early return wraps its clip in PaintSetup::Clip
vendor/README.md: main's rows plus the PR's hayro-interpret patch 39 and hayro patch numbered 22 (19 is #3296's), with tests
2026-10-22 01:49:59 +00:00
aiongg 61cbaf3e23 Merge PR #3246: Shading patterns use the stroke or fill alpha current at paint time (#3176)
vendor/hayro-interpret/src/pattern.rs: ShadingPattern::new takes the default colour spaces (#3018) and no opacity (this PR); without_opacity (#2977) and set_alpha both kept
2026-10-22 01:49:02 +00:00
aiongg c4fa66ecbd Integration: #3668 vendor/README.md: describe the delimiter after EI in hayro-syntax patch (4)
Adds content::tests::inline_image_ei_may_be_followed_by_a_delimiter to its test list.
2026-10-22 01:48:59 +00:00
aiongg d9486a1239 Merge PR #3668: Accept a delimiter after EI when ending an inline image
vendor/hayro-syntax/src/content/mod.rs tests: kept #2483's test and this PR's three.
2026-10-22 01:48:59 +00:00
aiongg c3c0bea0a1 Integration: #2977 number its hayro-interpret patch 46 (35 is #3296's) 2026-10-22 01:48:40 +00:00
aiongg 1ddbb5f3ff Merge PR #2977: fix(render): a stencil mask is drawn at the fill opacity once (#2975)
vendor/hayro/src/renderer.rs: main's PaintSetup::Clip match with the PR's fill paint (is_stroke false) and comment
vendor/README.md: main's row plus the PR's hayro-interpret patch and test, numbered 46 (35 is #3296's)
2026-10-22 01:48:34 +00:00
aiongg 69c87d2f3e Integration: #3195 keep CMap entries read before a stream ends inside a block
A begin… block cut off at the end of the stream returned None through ? and discarded the whole map; it now ends the scan like any other unparseable tail. Test: stream_cut_off_inside_a_block_keeps_the_entries_before_it.
2026-10-22 01:48:30 +00:00
aiongg 84e091d9b3 Merge PR #3195: Vendor and patch hayro-cmap for tolerant CMap parsing
ATTRIBUTION.md: generated header count set to 277 (main's 276 plus the CMap blob moved from bundled to asset).
2026-10-22 01:48:06 +00:00
aiongg b093dbca84 Merge PR #3630: Render: a doubled sign is one number, so a cm with it still has a matrix 2026-10-22 01:47:42 +00:00
aiongg 7bcf25991d Merge PR #2397: Issue #2365: a rebuilt file keeps the /Info and /ID of its cross-reference stream
crates/cos/src/document.rs: #2220 moved the header scan into object_headers; it also returns the position after `obj`, and reconstruct applies this PR's /XRef candidate check (same 1024-byte window) to each header there. Tests: kept both sides.
2026-10-22 01:47:39 +00:00
aiongg a8b7d14709 Merge PR #3035: fix(render): mesh triangles that share an edge leave no seam
vendor/hayro-interpret/src/encode.rs: both sides; a pixel sampled by either test (corner held, or centre covered) widens the mesh extent from #3296
vendor/README.md: main's row plus the PR's patch 36 and its test
2026-10-22 01:47:35 +00:00
aiongg 5f256d53fd Merge PR #3958: perf(render): look CFF glyph names up in a map (vendored hayro-interpret patch 44)
vendor/README.md: hayro-interpret row keeps patches (38) and (44) and both tests, in number order
2026-10-22 01:47:18 +00:00
aiongg 7a381676c4 Merge PR #2172: fix(docs): correct spelling of "Formatting" in comments 2026-10-22 01:47:13 +00:00
aiongg 40d4566c61 Merge PR #3296: Renderer: sample a mesh shading over its triangles alone, not the whole path
vendor/README.md: main's rows plus the PR's hayro-interpret patch 35 and hayro patch 19 with their tests
2026-10-22 01:47:04 +00:00
aiongg e47b64b009 Merge PR #2949: M2: a post table version with swapped halves doesn't make a font unusable (#2946)
vendor/README.md: hayro-interpret row keeps #2914's patch (26) extension and #2949's new patch (38) with both tests
2026-10-22 01:46:55 +00:00
aiongg 0edf07969c Merge PR #2400: Issue #2366: a trailer whose /Prev is a reference still reaches the earlier sections
crates/cos/src/document.rs: kept #2220's shifted_subsection_end and object_headers and this PR's prev_offset.
vendor/README.md: hayro-syntax row already has (22) and (23); this PR's patch and tests are numbered (24).
2026-10-22 01:46:50 +00:00
aiongg b1c8ae231a Merge PR #3643: fix(render): draw missing spacing glyphs in Japanese CID substitutes as spaces (#3601)
ROADMAP.md: progress log keeps both sides' lines
2026-10-22 01:46:04 +00:00
aiongg fd0c47f7be Merge PR #3637: Fix named properties in forms without resources
ROADMAP.md: progress log union, newest first
2026-10-22 01:45:57 +00:00
aiongg 6bb89022d0 Merge PR #2483: fix(content): an inline image ends at its own EI, not a later one
vendor/README.md: hayro-syntax row already has a (22) on main; this PR's patch and tests are numbered (23).
2026-10-22 01:45:49 +00:00
aiongg 2a17980645 Merge PR #2914: fix(render): draw comma-below letters in non-embedded standard fonts … 2026-10-22 01:45:44 +00:00
aiongg 0b51ea4baa Merge PR #3018: fix(render): draw device colour in the resources' /DefaultGray, /DefaultRGB and /DefaultCMYK (vendored hayro-interpret patch 38)
vendor/README.md: main's single hayro-interpret row plus the PR's patch 38 and its tests, placed before 41
2026-10-22 01:45:41 +00:00
aiongg add6907f6a Merge PR #3239: M3: extract text from glyph names outside the reference-app Glyph List (#3169, #3170) 2026-10-22 01:45:33 +00:00
aiongg dbb71754d0 Merge PR #3497: M2: Type 0 fonts with /ToUnicode /Identity-H read their codes as Unicode
contributors/people.toml: kept both new contributor entries.
2026-10-22 01:45:33 +00:00
aiongg 4a73cacb85 Merge PR #3116: fix(render): emit clip-only text (7 Tr) to devices for text extraction (#3109) 2026-10-22 01:44:50 +00:00
mtlcl410andClaude Opus 5.5 7a2907601d docs: patch (38)'s regression in the vendor README's Test column (#2946)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-22 08:40:08 +08:00
mtlcl410andClaude Opus 5.5 09668ce885 M10: a swapped post 2.0 table's glyph names serve /Differences (#2946)
Review follow-up on #2949.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-22 08:40:08 +08:00
mtlcl410andClaude Opus 5.5 b67a8bf268 M2: a post table version with swapped halves doesn't make a font unusable (#2946)
Vendored hayro-interpret refuses an embedded font whose `post` table
version isn't 1.0, 2.0, 2.5 or 3.0 (a guard from pdf.js issue 9462).
ScanSoft PDF Create! writes the version of every font it embeds with its
16-bit halves swapped (`00 00 00 03` for 3.0), so those fonts were refused
and their text drawn with a substitute: Hebrew came out as Latin
look-alikes or not at all.

1.0, 2.0 and 3.0 with swapped halves are now accepted too; any other
unknown version is still refused. PdfCraft patch (38) in vendor/README.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-22 08:40:08 +08:00
narain 9c2f4e3529 perf(render): look CFF glyph names up in a map (vendored hayro-interpret patch)
CffFontBlob::glyph_index_by_name searched the whole charset, with a string lookup per glyph, for every character code of a font (there was a TODO noting it). Build a name to glyph map on first use and share it between clones. The first glyph with a name still wins, so results are unchanged.
2026-10-21 17:14:05 -07:00
jonathanwxh-cell b9c50c7267 Merge current main into inline-image repair branch
Keep both roadmap progress entries and all upstream changes while
preserving the inline-image parser fix and regression coverage.
2026-10-22 07:40:28 +08:00
jonathanwxh-cell 01af6cba3f fix(syntax): bound inline-image lookahead by content window
Fall back only when missing sample or terminator bytes cannot fit the
content-window budget, including retained header and separator overhead.
Preserve available deterministic boundaries and ordinary large streams.
Add oversized, exact-budget, overflow and large-stream regressions.
2026-10-22 07:35:54 +08:00
PiohrunandClaude Opus 5.5 d90cee4669 fix(render): read each object stream's index once (vendored hayro-syntax patch 22) (#2427)
* fix(render): read each object stream's index once (vendored hayro-syntax patch 22)

`XRef::get_with` parsed an object stream's whole index of `objnum offset`
pairs again for every object it fetched from that stream, so reading all of a
stream of N objects took N² steps. Firefox keeps every object of a page in one
stream: the pdf.js corpus file bug1978317.pdf (65,542 objects in one stream)
took 37 s to draw and 40 s to extract its text, and was a known timeout in the
robustness baseline.

The parsed index is now kept per stream, beside the decoded stream data it
indexes (`ObjectStreamIndexes`), and never cleared, as that data isn't. A
malformed index is remembered as malformed; `/N` or `/First` that can't be
read (a reference that can't be followed while another stream loads) is not
remembered. An offset past `usize` is malformed instead of wrapping. A poisoned
lock is still used: entries are inserted whole.

Release build, Linux: bug1978317.pdf draws in 0.5 s (was 37 s) and extracts
its text in 1.9 s (was 40 s), and leaves the baseline's known timeouts;
40,000 pages in one stream open in 0.2 s (was 54 s).
`raster::tests::objects_in_one_large_object_stream_are_read_in_linear_time`
times out at 20 s with the fix reverted and passes in 0.2 s with it.

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

* test(render): an object stream offset past usize is malformed, not a panic

Review follow-up on #2427. Mutation testing by mtlcl410 found that replacing
patch (22)'s `checked_add` in `read_object_stream_index` with a plain `+`
survived every test, although in a debug build that `+` panics on the very
input it guards against (and wraps in a release build). Their test pins it:
it fails with "attempt to add with overflow" under that mutant and passes on
this branch.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:01:35 -04:00
Hamza e3fc82938f A rectangle's corners are equal, not nearly equal
is_nearly_equal allows a 256th of a path unit, and stroke_path_impl
applies the matrix after path_as_rect, so a cm of 10,000 turns that
tolerance into tens of device units: a path that only nearly closes was
painted as the rectangle, with a side it does not have, and an edge off
true by that much counted as axis-aligned.

The fast path now compares coordinates exactly. Nothing that is a
rectangle loses it: a rectangle's coordinates are equal in the content
stream, not approximately equal. A path that misses takes the general
route and is painted as written, which is the safe direction.

I could not make a rendered case that distinguishes the two: this
renderer paints a 10,000x cm as a solid page whatever the line width, so
there are no pixels to compare at the scale where the tolerance shows.
The change rests on the argument above, and the three existing tests
still pin the behaviour that is measurable.

Reported by CodeRabbit on #3327.
2026-10-20 23:27:49 +05:00
Hamza 9a997cf1b1 A rectangle is a traversal, not four corners visited
FredApps's note on #3327: five points that are all on the bounding box's
corners still need not trace the rectangle. Three sides and a step back
along the third --

    10 10 m 30 10 l 30 30 l 10 30 l 30 30 l S

-- visits every corner, so it was taken as the box and stroked with a
left side the path does not have.

path_as_rect now asks what a rectangle actually is: four distinct
points, each step from one to the next along a single axis, and the last
point back at the first (written as the fourth LineTo or as ClosePath).
That is a rectangle and nothing else is, so the corner test and its
off_corner flag are gone -- four points of an axis-aligned traversal are
necessarily the corners of their own bounding box. A degenerate path,
whose points are not distinct, now takes the general route as well,
where it painted a zero-width or zero-height rectangle before.

The vendored-patch table has the row this change was missing: without
it a re-vendor of hayro-interpret would drop the patch silently, which
is the other half of FredApps's review.
2026-10-20 21:28:05 +05:00
NetterSilverandClaude Haiku 5.5 b85af272c3 Accept a delimiter after EI when ending an inline image
Fixes #3578

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 14:11:33 +00:00