Commit Graph
84 Commits
Author SHA1 Message Date
BFlat b6cbf9bb30 Merge pull request #6710 from storytold/merge/forms-links-text2
Land forms, links, find and redaction-save fixes (#4496, #4532, #3567, #3773, #4470, #4474, #4497, #5658)
2026-11-09 19:45:39 +08:00
bflatastic 70aa8e5c0c Merge #4459: M2: the standard 14 fonts fit the Euro to its standard width (view.render-pages)
# Conflicts:
#	vendor/README.md
2026-11-09 18:19:55 +07:00
bflatastic baa131de70 Merge #4433: M2: zero-area fills and clips mark the pixels they touch (view.render-pages)
# Conflicts:
#	vendor/README.md
#	vendor/hayro-interpret/src/interpret/path.rs
2026-11-09 18:19:48 +07:00
bflatastic 06a9385543 Merge #4410: fix(render): degenerate subpaths, all-zero dash arrays and negative line widths stroke as the spec says (hayro-interpret patch 61)
# Conflicts:
#	vendor/README.md
2026-11-09 18:19:14 +07:00
mtlcl410 cf7734b17e Merge upstream/main into fix/standard14-euro-width 2026-11-09 19:07:08 +08:00
mtlcl410 5ac623ff9f Merge upstream/main into fix/zero-area-fill-and-clip 2026-11-09 19:05:11 +08:00
mtlcl410 299e263bf9 Merge upstream/main into fix/stroke-degenerate-and-dash 2026-11-09 19:02:50 +08:00
mtlcl410 ad42d5c276 Merge upstream/main into fix/indexed-short-lookup 2026-11-09 18:48:21 +08:00
mtlcl410 60de09e1a5 Merge remote-tracking branch 'upstream/main' into lane4-4470b
# Conflicts:
#	crates/render/src/text/search.rs
#	crates/render/tests/it/search.rs
2026-11-09 18:43:43 +08:00
mtlcl410 bd5db5dc35 M2: a page whose content was cut at the renderer's limits says so (#623) (#2113) 2026-11-09 18:09:37 +08:00
mtlcl410 cc1d76f3fd M3: whole-word Find matches inside Chinese, Japanese and Thai text (view.find-options) (#2075) 2026-11-09 18:09:20 +08:00
mtlcl410andClaude Opus 5.5 98a8fbbd9b Merge upstream/main into fix/search-ligature-accent-hyphen
Clean merge; brings in main's fix for the ui-egui dialog and security-notice
tests that failed this branch's gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 11:34:34 +08:00
mtlcl410andClaude Opus 5.5 02db83e15d Merge upstream/main into fix/indexed-short-lookup
Main's #6663 added InterpreterWarning/RenderWarning::Type3WidthsMissing (patch
90) and a decoded-image cache that records warnings as flags (patch 91). Both
warning variants are kept in every match (renderer, automation, CLI), and
MalformedIndexedImage takes flag 32 in warning_bit/warnings_in. The ledger
keeps main's rows with (80) added; both sides' raster test modules are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 11:23:00 +08:00
mtlcl410andClaude Opus 5.5 96e96e14f4 Merge upstream/main into fix/standard14-euro-width
Keeps main's vendor ledger (now with patches 82-102) with hayro-interpret
patch (77) added, and both sides' render test modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 11:10:04 +08:00
mtlcl410andClaude Opus 5.5 59461ba3cd Merge upstream/main into fix/zero-area-fill-and-clip
Keeps main's vendor ledger (now with patches 82-102) with hayro-interpret
patch (65) added, and both sides' render test modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 11:06:44 +08:00
mtlcl410andClaude Opus 5.5 0d42620d0f Merge upstream/main into fix/stroke-degenerate-and-dash
Keeps main's vendor ledger (now with patches 82-102) and adds hayro-interpret
patch (61) to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 11:04:55 +08:00
BFlat cde331fb7e Merge pull request #6663 from storytold/merge/render-interpret-patches
Land render and font patches (#6292, #6287, #6314, #6353, #6359, #6368, #6409, #6491, #6545, #6516, #6521, #6528, #6533, #6529, #6542, #6543, #6569, #6572)
2026-11-09 10:54:50 +08:00
mtlcl410andClaude Opus 5.5 b3e21261e8 Merge upstream/main into fix/standard14-euro-width
Keeps main's vendor ledger with hayro-interpret patch (77) added, and both
sides' render test modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 10:50:52 +08:00
mtlcl410andClaude Opus 5.5 e75384ea90 Merge upstream/main into fix/zero-area-fill-and-clip
Keeps main's vendor ledger with hayro-interpret patch (65) added, and main's
BlendMode import beside the patch's kurbo imports in path.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 10:47:14 +08:00
mtlcl410andClaude Opus 5.5 c615bd8224 Merge upstream/main into fix/stroke-degenerate-and-dash
Keeps main's vendor ledger with hayro-interpret patch (61) added, main's
BlendMode import beside the patch's Point and Cow in path.rs, and both sides'
stroke test modules (main's stroke_degenerate_cubic and stroke_zero_end_tangent
fix vello_common's stroker; this patch shapes subpaths in hayro-interpret).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 10:45:00 +08:00
mtlcl410andClaude Opus 5.5 f49ea0f70c Merge upstream/main into fix/indexed-short-lookup
Keeps main's vendor ledger with hayro-interpret patch (80) added, and both
sides' test modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 10:42:14 +08:00
mtlcl410andClaude Opus 5.5 f86f709c3e M3: merge main's soft hyphen, zero width and CJK line-join search with the ligature and line-end hyphen readings (#4469)
Main's #6595 made a soft hyphen invisible and a line ending in one run on, so
the line-end hyphen readings now cover HYPHEN-MINUS and HYPHEN only, pass over
invisible characters, and keep main's zero width space word boundaries in each
reading. The reference oracle combines both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 10:29:15 +08:00
jonathanwxh-cell 5e671ec301 fix(render): treat combined fill-and-stroke operators as a knockout group (#3229) (#4788) 2026-11-09 10:09:06 +08:00
mtlcl410andClaude Opus 5.5 f13b480871 Merge upstream/main into fix/search-ligature-accent-hyphen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 10:00:42 +08:00
bflatastic 183c66ddb5 Merge #6529: fix(render): a non-embedded SymbolMT draws Symbol's glyphs, whatever encoding it declares (hayro-interpret patch 98)
# Conflicts:
#	vendor/README.md
2026-11-09 08:11:26 +07:00
bflatastic e2502b2f9b Merge #6528: fix(render): a CID-keyed CFF font with an emptied font dict still draws its other glyphs (hayro-interpret patch 93)
# Conflicts:
#	vendor/README.md
2026-11-09 08:10:53 +07:00
bflatastic c90a4115d5 Merge #6368: fix(render): a form field's widget is drawn above the page's comments (hayro-interpret patch 86)
# Conflicts:
#	vendor/README.md
2026-11-09 08:06:57 +07:00
mtlcl410 87bc00a991 M3: Find matches words divided by soft hyphens, zero width characters or a Chinese or Japanese line end (view.find) (#6595) 2026-11-09 08:58:34 +08:00
c02341f6c0 M2: a wide stroke of a path with y curves is drawn, not dropped (vello_common patch 4) (#6526)
A cubic whose second control point is its end point (as the `y` operator builds,
ISO 32000-2 8.5.2.1 Table 58) has a zero derivative at its end. kurbo 0.13.1's
offset curve looks for cusps with a root search on the curvature, which grows
without bound towards that end; at some widths the search stops on the end point
and normalising the zero tangent there gives NaN. vello_common's fill drops any
outline with NaN, so the whole stroke vanished (a two-curve `y` path drew nothing
at 16, 32 and 63 pt; a PDFBox tracker file lost its 32 pt "XX" outline).

The NaN fallback that #4913 added to `flatten::stroke` now also moves such a
control point the stroke tolerance towards the previous control point before
stroking again (the curve moves by at most 4/9 of the tolerance). Outlines that
come back finite take the old path unchanged. The vendor ledger row also gets
back #4913's entry, which a merge had dropped.


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

Co-authored-by: mtlcl410 <chuanlin@niar.org.tw>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 08:31:00 +08:00
mtlcl410andClaude Opus 5.5 cd9b6b8a0f fix(render): only Symbol's style variants are Symbol, and SymbolMT keeps its differences and embedded program (hayro-interpret patch 98)
- is_symbol_name: after `,` or `-` only a style name (SYMBOL_STYLES) keeps
  the Symbol font, so `Symbol-Helvetica` keeps its declared encoding and
  Latin stand-in.
- Tests: the negative names draw the same letters as Helvetica; a SymbolMT
  with /Differences [109 /alpha] draws alpha; an embedded SymbolMT draws
  its own program.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 19:01:05 +08:00
mtlcl410andClaude Opus 5.5 22d211fa4a fix(render): the CFF subfonts read once are kept, and a program with no usable font dict renders as if absent (hayro-interpret patch 93)
- CffFontBlob::new moves the subfonts it validated into the yoke instead
  of reading every font dict a second time.
- a_program_with_no_usable_font_dict_draws_no_square now checks that the
  page renders exactly as it does with no embedded program; the old
  inequality passed with the empty-dict check removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 18:55:11 +08:00
mtlcl410andClaude Opus 5.5 c420a0edf8 M2: a non-embedded SymbolMT draws Symbol's glyphs, whatever encoding it declares (hayro-interpret patch 98)
A non-embedded TrueType `SymbolMT` (Symbol's TrueType name) with
/WinAnsiEncoding and the nonsymbolic flag was not recognised as the
standard Symbol font and fell back to Helvetica, so code 0x6D drew "m"
where PDFium, MuPDF and Poppler draw "mu". `SymbolMT`, and `Symbol` or
`SymbolMT` with a `,`/`-` style suffix, now select Symbol, which draws
through its built-in encoding like `/Symbol` already does.

ISO 32000-2 §9.6.2.2, §9.6.5.1. PDFBOX-5844 (13069708-exportByExcel.pdf).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 17:11:10 +08:00
mtlcl410andClaude Opus 5.5 ef37d634b2 M2: a CID-keyed CFF font with an emptied font dict still draws its other glyphs (hayro-interpret patch 93)
A CID CFF program whose subsetter emptied the font dicts no kept glyph
uses (no Private DICT) dropped the whole font: CffFontBlob collected its
subfonts with `collect::<Option<Vec<_>>>()?`. The subfonts are now kept
as `Option`s; a glyph whose font dict can't be read is missing, the rest
draw. The program is still refused when no font dict is usable.

ISO 32000-2 §9.7.4.2, §9.9 Table 124; Adobe TN #5176 (FDArray, FDSelect,
Private DICT). PDFBOX-5843 (13069603-123.pdf) now matches PDFium, MuPDF
and Poppler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 17:05:57 +08:00
mtlcl410andClaude Opus 5.5 3dca7f3b01 Widgets are drawn in a second pass over /Annots, holding nothing per entry; comment order with a widget among them in the test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 09:22:11 +08:00
mtlcl410andClaude Opus 5.5 2bace2144f fix(render): a form field's widget is drawn above the page's comments (hayro-interpret patch 86)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 09:03:01 +08:00
mtlcl410andClaude Opus 5.5 3d086bef29 Merge upstream/main into fix/indexed-short-lookup
vendor/README.md conflicted: taken from main, with this PR's Indexed
lookup patch added to the hayro-interpret row as patch (80), past main's
highest (75) and the numbers open PRs use (76-79). Its code comments
(cache.rs, color.rs, context.rs, interpret/mod.rs, x_object.rs) and the
render tests that name it say (80) too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 01:39:48 +08:00
mtlcl410andClaude Opus 5.5 c40e251880 Merge storytold/pdfcraft main into fix/standard14-euro-width
Conflicts:
- vendor/README.md (hayro-interpret row): kept main's row; main does not
  use patch number (67), so this PR's (67) entries go in numeric position
  (before (71)) in both the Patches and Test columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 21:39:02 +08:00
mtlcl410andClaude Opus 5.5 2b08357b13 Merge storytold/pdfcraft main into fix/stroke-degenerate-and-dash
Conflicts:
- vendor/README.md: the hayro and hayro-jbig2 rows are main's (this PR
  does not change them). The hayro-interpret row is main's, with this
  PR's (61) entry inserted after (59) in both the patch column and the
  tests column; no other number moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 12:17:42 +08:00
mtlcl410andClaude Opus 5.5 959a85dbf5 Merge storytold/pdfcraft main into fix/zero-area-fill-and-clip
Conflict in vendor/README.md (hayro-interpret row): main added patches (71), (72) and (74); this branch adds (65). main's row is kept, with (65) placed after (64) in the patch list and in the tests list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 11:54:24 +08:00
mtlcl410andClaude Opus 5.5 ca258d8fb5 Merge storytold/pdfcraft main into fix/search-ligature-accent-hyphen
No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 11:22:48 +08:00
mtlcl410andClaude Opus 5.5 7a84239ad7 Merge storytold/pdfcraft main into fix/indexed-short-lookup
main gave the colour-space constructors a nesting depth (ColorSpace::within) and read the
Indexed lookup once by type (patch (71)). Indexed::new keeps this PR's recovery (hival
truncated and clamped, black past a short table, no table skipped) with main's depth bound;
vendor/README.md takes main's rows with this PR's patch (60) entries after (59).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 09:29:41 +08:00
mtlcl410andClaude Opus 5.5 78c5262600 M2: test the W* clip of overlapping zero-area subpaths too
disconnected_subpaths_on_one_line_leave_the_gap_between_them_unpainted
checked overlapping subpaths drawn in opposite directions under f* only.
It now checks the W* clip of the same path keeps their union too: with
the clip area pushed under the path's own even-odd rule instead of
non-zero, the overlap goes missing and the new assertion fails.

Review on #4433 (CodeRabbit, crates/render/tests/it/zero_area_fill_clip.rs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 09:25:20 +08:00
mtlcl410andClaude Opus 5.5 ba16b9b5b8 M2: a segment after h begins a new subpath (hayro patch 61)
ISO 32000-2 §8.5.2.1, Table 58: h "terminates the current subpath.
Appending another segment to the current path shall begin a new subpath,
even if the new segment begins at the endpoint reached by the h
operation." kurbo carries a segment after ClosePath on from the subpath
start without a MoveTo, so resolve_degenerate_subpaths saw "h" then an "l"
back to that point as part of the closed subpath, and the round-cap dot
of that degenerate subpath was lost (main drew it, through the nudge this
PR removed). l, c, v and y after h now start with a MoveTo to the point h
reached.

Review on #4410 (CodeRabbit, vendor/hayro-interpret/src/interpret/mod.rs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 08:00:22 +08:00
mtlcl410andClaude Opus 5.5 b56bb840bf M2: a stroke colour and a shading in an Indexed space with no lookup table keep the upstream handling (ISO 32000-2 8.6.6.3)
Tests for the mutation pass on c709dd99e:
- a_fill_colour_in_a_space_with_no_table_keeps_the_fallback now covers
  CS/SC too;
- a_shading_in_a_space_with_no_table_is_not_drawn checks that only
  images and the colour operators see a tableless space.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 07:34:01 +08:00
mtlcl410andClaude Opus 5.5 c709dd99ec M2: indices past a short Indexed table draw black in every base space, and an image with no lookup table is not drawn (ISO 32000-2 8.6.6.3)
Seven readers were compared on synthetic files. Most draw the entries a
short table holds and black past them, and none draws an image whose
lookup is missing.
- Indexed::new keeps how many entries the table holds in full. Indices
  at or past that are set to black after conversion, so DeviceCMYK,
  ICCBased, Separation, DeviceN and Lab come out black too (zero bytes
  gave white, or blue in Lab). Fill and stroke colours follow the same
  rule. An incomplete last entry counts as missing.
- A lookup that is missing, null, a number, or a stream that does not
  decode (or decodes to nothing although it holds data) is no table.
  ColorSpace::new_allowing_tableless builds such a space marked
  tableless (for the resource cache and ImageXObject::new only), and
  the image is skipped with MalformedIndexedImage. A cs/CS colour in it
  keeps the upstream DeviceGray fallback with ColorFallback. Every other
  caller still gets None, as upstream.
- hival clamping and truncation are unchanged, and so is case C.
- The warning now reads "an image with a malformed Indexed colour table
  may be drawn wrongly or not at all".
- vendor/README.md: the patch (60) row is one line again. 3275f853c
  split it.

Tests: indexed_lookup.rs now has 25 tests, including one per base space
(DeviceGray, DeviceCMYK, Lab, Separation, DeviceN, and ICCBased N=4
drawn through its alternate). Against 6179cf795, 13 fail, including
every base space except DeviceGray, where zero is black. Against
b6f93a3ae, 19 fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 07:32:46 +08:00
b36bdaeac6 M2: a shrunk image on a turned or mirrored page snaps to whole pixels (#5530) (#5640)
* M2: a shrunk image on a turned or mirrored page snaps to whole pixels (#5530)

hayro patch 14 (`snap_minified_image`) shrinks an interpolated image once
to its whole-pixel device rectangle and draws that 1:1, instead of
shrinking to a rounded-up size and resampling again at a fractional
scale and offset. It only accepted an upright transform (a > 0, d > 0,
b = c = 0), so every image on a page under /Rotate 90, 180 or 270 -- and
any image placed turned or mirrored by its own matrix -- still took the
double resample. That is #5530: a scanned page that matches PDFium
upright drifts and softens when the same page is rotated.

The snap now accepts every transform whose axes lie along the device's
(the eight quarter-turns and mirrors) and returns the 1:1 transform
that draws the shrunk raster from the snapped corner, one source-axis
step a whole device pixel, in whichever direction that axis runs.
Skewed and freely rotated images keep the old path.

On a synthetic page (crop box and scale on whole pixels, image at
fractional coordinates), /Rotate 90, 180 and 270 renders turned back
differed from the upright render in 72% of pixels, by up to 200 grey
levels; now they are identical. Non-interpolated images were already
identical and stay so.

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

* M2: snap only when the dropped skew is invisible, and only a resize that succeeded

Review of #5640:

- The snap judged an image axis-aligned by a tolerance on its
  coefficients (1e-4 of the scale). For a very wide image a skew that
  small still moves the far edge by more than a pixel (40 000 samples
  at 4e-5: 1.6 px), and the snap flattened it. It now measures what it
  drops: how far each off-axis term moves the far edges, at most a
  hundredth of a device pixel (SNAP_SKEW). Upright and turned alike.
- A resize that fails hands back the source raster at source size; the
  1:1 snapped transform then drew it at that size. The snapped
  transform is now used only when the shrink produced the snapped
  size; otherwise the full transform, as before.
- The mirrored case of the edge test drew a uniform image, which looks
  the same mirrored; it now also mirrors a half-black image and checks
  which side the black lands on.

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

* M2: the turned-page test draws every kind of image the snap reaches

The /Rotate oracle test drew one 8-bit grey image. Stencil masks (CCITT
scans often are one), 1-bit images and images with a soft mask reach
the snap through other paths, so it now draws all five: 8-bit and
1-bit grey, a stencil mask, a soft mask of the image's size and one of
another size. On main, every interpolated kind is off by 113-217 levels
over 63-73% of the image when turned, and so is a non-interpolated
image with a smaller soft mask (110 levels, 16%): hayro patch 15
filters such a mask whatever /Interpolate says, so it is snapped
upright and was not when turned. All are identical with the fix; the
other non-interpolated kinds were already identical and stay as guards.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-01 15:07:29 -05:00
mtlcl410andClaude Opus 5.5 6179cf795d M2: two Indexed resources are cached apart, and a stroke colour in a recovered Indexed space is reported (ISO 32000-2 8.6.6.3)
Mutation pass on patch (60):
- keying the resource colour-space cache with one key for every space
  survived the earlier tests, which used one Indexed resource a page:
  raster::tests::indexed_lookup_cache::two_indexed_resources_are_cached_apart
  kills it;
- the ColorFallback report for a recovered space set with CS had no
  test (only cs was tested): a stroke case in
  a_fill_or_stroke_colour_in_a_short_table_is_padded_and_reported fails
  with that report removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 02:08:46 +08:00
mtlcl410andClaude Opus 5.5 3275f853c3 M2: an Indexed image whose lookup table is short or whose hival is out of range is drawn as other readers draw it, with a warning (ISO 32000-2 8.6.6.3)
Patch (60) skipped such an image. A reviewer's side-by-side tests on #4393
(seven cases against main and Chrome's viewer) showed the skip dropped
colours other readers draw, and regressed a real hival that main draws.
Indexed::new now recovers instead:
- a lookup table shorter than m x (hival + 1) bytes, missing, or not a
  string or decodable stream is padded with zero bytes;
- a hival past 255 is taken as 255;
- a real hival is truncated toward zero (as upstream read it).
The space records that it was recovered. ImageXObject::new raises
MalformedIndexedImage from the space it built; a cs/CS colour in such a
space reports ColorFallback, as the upstream DeviceGray fallback did. A
hival below zero or not a number has no reading: the space falls back as
upstream, and the image reports the warning. Index values past hival
stay clamped (8.6.6.3).

The separate is_malformed_indexed check is gone, so no lookup table is
read twice: inline images naming an Indexed resource read its table once
through the resource cache (CodeRabbit, interpret/mod.rs:945). The read
count is exposed as InterpreterCache::indexed_lookup_reads.

Tests: indexed_lookup.rs rewritten as draw tests (17, cases A-G and
bounds); raster::tests::indexed_lookup_cache. On b6f93a3ae logic 14 of
the 17 fail (the three that pass are guards: full table, case C, device
name) and the cache test reads [21, 41] instead of [1, 1].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 02:02:49 +08:00
mtlcl410andClaude Opus 5.5 b6f93a3aef M2: every match on RenderWarning names MalformedIndexedImage, and an Indexed lookup string given by reference is read (ISO 32000-2 8.6.6.3)
- render_warning_text (crates/automation/src/pages.rs, on main since this
  branch was cut) matched RenderWarning exhaustively and did not build
  with the new variant. It now names it, in the CLI's words, so
  page_render and render_preview report the skip. A workspace search
  finds no other match on RenderWarning outside pdfcraft-render.
- An Indexed lookup is a string or a stream (8.6.6.3). Indexed::new and
  is_malformed_indexed tried it as a stream first; a failed stream read
  of a reference consumed the reference, so a string lookup given by
  reference was never found and the image was skipped. The lookup is now
  read once and dispatched by type, in both places.
- hival is judged before the base: an out-of-range hival rules the space
  out even when the base cannot be read (the table size still needs the
  base, so a usable hival keeps today's handling there).

The size check is unchanged: hival is range-checked to 0..=255 before
(hival + 1) * m is formed with checked_add/checked_mul.

Tests (each seen failing with a fault injected): bounds at DeviceCMYK,
DeviceGray and hival 255 (exact and one short); hivals past u32/u64/any
integer, negative and fractional (with unchecked arithmetic and the range
widened the test panics with "attempt to multiply with overflow"); a
lookup string by reference (fails with the old stream-then-string read);
an unreadable base with hival 300; an undecodable lookup stream; and
page_render reporting the skip (without the new arm the crate does not
build, E0004).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-01 19:20:55 +08:00
mtlcl410andClaude Opus 5.5 a215cc7bd6 M2: zero-area fill and clip tests quote the half-open pixel rule and cover each case on and off a boundary
The test module now quotes ISO 32000-2 §10.7.4 on which pixel a point on
a boundary belongs to (i = floor(x), j = floor(y); shapes include their
floor sides but not their ceiling sides), so the row a line on a pixel
boundary marks follows from the text. A new test runs zero-height and
zero-width lines, as a fill and as a clip, on a boundary, off one, and with
their ends inside pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-01 19:15:09 +08:00