Commit Graph
473 Commits
Author SHA1 Message Date
bflatastic 4dfd9bd564 Merge #6516: fix(render): a picture that doesn't decode is decoded once, not at every draw (hayro-interpret patch 91)
# Conflicts:
#	crates/render/src/raster/tests.rs
#	vendor/README.md
#	vendor/hayro-interpret/src/x_object.rs
2026-11-09 08:10:02 +07:00
bflatastic 574968a03b Merge #6545: fix(render): a Type 3 font without /Widths advances by its glyphs' d0/d1 widths, and the page warns about it (hayro-interpret patch 90)
# Conflicts:
#	crates/render/src/raster/tests.rs
#	vendor/README.md
#	vendor/hayro-interpret/src/font/type3.rs
2026-11-09 08:08:39 +07:00
bflatastic 4c219bb055 Merge #6491: fix(render): a non-embedded Times New Roman draws the glyphs its codes name (hayro-interpret patch 88)
# Conflicts:
#	crates/render/src/raster/tests.rs
#	vendor/README.md
2026-11-09 08:07:55 +07:00
bflatastic e81b45a117 Merge #6409: fix(render): a Type 3 glyph shown again isn't decoded again (hayro-interpret patch 87)
# Conflicts:
#	vendor/README.md
2026-11-09 08:07:12 +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
bflatastic cdf7f1b178 Merge #6359: fix(render): a ToUnicode written with CID ranges gives its text (hayro-interpret patch 85)
# Conflicts:
#	crates/render/src/text/tests.rs
#	vendor/README.md
#	vendor/hayro-interpret/src/font/cid.rs
2026-11-09 08:06:43 +07:00
bflatastic 67c1e6a145 Merge #6353: fix(render): an embedded CJK font without a usable ToUnicode reads its text through its collection's UCS2 CMap (hayro-interpret patch 84)
# Conflicts:
#	vendor/README.md
2026-11-09 08:05:10 +07:00
bflatastic d568f0d3d6 Merge #6314: fix(render): a TrueType font marked with an out-of-range loca format draws its letters instead of boxes (hayro-interpret patch 83)
# Conflicts:
#	vendor/README.md
2026-11-09 08:04:53 +07:00
bflatastic 581b5364ec Merge #6287: fix(render): a small image with a soft mask far larger than it draws in seconds, its mask averaged down once (hayro-interpret patch 82)
# Conflicts:
#	vendor/README.md
2026-11-09 08:04:38 +07:00
bflatastic 88edc03ef4 Merge #6292: fix(render): a page of stencil masks painted with a tiling pattern draws in seconds instead of running out of memory (hayro patch 30)
# Conflicts:
#	vendor/README.md
2026-11-09 08:04:05 +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
mtlcl410 58c30af312 fix(render): a JBIG2 scan whose data starts with a JBIG2 file header draws instead of a white page (hayro-jbig2 patch 17) (#6279) 2026-11-09 08:54:03 +08:00
mtlcl410 38ba2e7495 M5: a font a page lists among its annotations is not a comment (comment.list-view) (#6447) 2026-11-09 08:50:25 +08:00
mtlcl410 e83c200598 M6: a field whose /Kids loop back or name a root field keeps its other kids (form.field-list) (#6446) 2026-11-09 08:50:11 +08:00
mtlcl410 608ff62dae M6: a widget whose /Parent names a missing object is a field of its own (form.field-list) (#6445) 2026-11-09 08:49:56 +08:00
mtlcl410 1a28606c64 M10: a layer the document marks screen-only stays off the printed sheet (print.print-ready-pdf) (#6387) 2026-11-09 08:48:28 +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
42b21e5362 fix(render): a form XObject that draws itself is drawn once (hayro-interpret patch 89) (#6515)
* M2: a form XObject that draws itself is drawn once (hayro-interpret patch 89)

A form whose content names itself, directly or through another form, a
pattern or a glyph, drew itself again at every level until the page's
nested-paint budget (50,000) ran out. PDFBOX-5782's poppler-748 page 2,
whose form names a /Pages dictionary as /X0 and so finds itself in the
page's resources, took 38 s.

hayro-interpret now keeps the forms being drawn on the context and skips
a form already on it, reporting ContentTruncated (ISO 32000-2 §8.10.1: a
form is a self-contained description). The lenient fallback from a form's
own resources to the page's stays: MuPDF, Poppler and PDFium all draw a
name from the page's resources when the form's /Resources has no
XObject subdictionary (MuPDF and Poppler also when it has an empty one).

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

* fix(render): a form that reaches itself through a pattern or a glyph is drawn once (hayro-interpret patch 89)

The forms being drawn are now kept per page and thread rather than per
context, so the tiling patterns, Type 3 glyphs, soft masks and groups
painted inside a form, which hayro interprets in contexts of their own,
see that the form is being drawn and skip it inside themselves. A form
drawn again after it has finished draws as before.

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

* chore: re-run CI after the Fill & Sign save comparison failed on the clock (#6066)

`fill_sign_enter_adds_lines_outside_click_commits_and_escape_cancels`
compares two saves whose /ModDate fell in different seconds; it passes
locally on this commit. No change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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:30:14 +08:00
9b231f241d perf(render): undo TIFF predictor 2 a byte row at a time at 8 and 16 bits (#6505)
TIFF predictor 2 (ISO 32000-2 §7.4.4.4, Table 10) went through the
bit-level BitChunks/BitWriter path for every bit depth, re-reading the
previous output pixel through a new BitReader per pixel. Whole-byte
samples (8 and 16 bits per component) are now undone on byte slices,
each sample added to the same component of the pixel to its left,
wrapping at its width; 1-, 2- and 4-bit samples keep the bit-level path.
The output is byte-identical, including the dropped partial last row.

A page of 45 full-page 8-bit grey images so predicted (PDFBOX-1350)
renders in about 4 s instead of 23 s with the same pixels.

Vendored hayro-syntax patch 42.


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

Co-authored-by: mtlcl410 <chuanlin@niar.org.tw>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 08:29:44 +08:00
b07a84f003 M5: a comment whose colour is written [r g b null] is drawn, as CMYK with the null read as 0 (view.render-annotation-appearances) (#6503)
* M5: a comment whose colour is written [r g b null] is drawn, as CMYK with the null read as 0 (view.render-annotation-appearances)

ISO 32000-2 Table 166 /C (and /IC, Tables 178, 180, 181; /MK /BC and /BG,
Table 192) is an array of 0, 1, 3 or 4 numbers whose length picks the
colour space. A four-entry array with a non-number in it, such as
[1 0 0 null] (PDFBOX-2699), made the /C parser give up, so a shape
without an appearance stream was not drawn at all. PDFium, MuPDF and
Poppler all read such an array as CMYK with the non-number as 0.

- annot: a four-entry /C or /IC reads non-numbers as 0; other lengths
  still need every entry to be a number.
- annot: the properties' fill uses the same parser (it read
  [r g b null] as RGB before), and /C no longer drops a non-number
  to make three numbers.
- forms: /MK /BC and /BG read a four-entry array the same way instead
  of dropping the non-number and drawing RGB.

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

* M5: comment properties report the stroke colour as drawn; a reference among four colour entries is not read as 0 (view.render-annotation-appearances)

props() reads a non-FreeText /C through the appearance parser, so a
[1 0 0 null] stroke reports CMYK cyan as drawn, and grey and CMYK
strokes report their converted colour. A reference among the four
entries of a /C array (ISO 32000-2 7.3.10) is left unreadable instead
of standing in as 0, so /C [1 0 0 6 0 R] is never drawn cyan.

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

* M5: re-run CI (fill_sign Escape test depends on the clock's second, passes locally)

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

* M5: the comment list reports a comment's colour as drawn (view.render-annotation-appearances)

summaries() accepted only a three-entry /C, so a Square with /C [1 0 0 null],
drawn cyan and reported cyan by props(), had no colour in the comment list;
grey and CMYK colours were dropped the same way. Non-FreeText comments now
read /C through drawn_color (the array itself followed when it is given by
reference), keeping the three-number fallback and the FreeText /DA handling.

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

* M5: the render inspector lists a comment's colour as drawn (view.render-annotation-appearances)

The inspector accepted only a three-entry /C, with a referenced entry read
as 0, so a /C [1 0 0 null], a grey or a CMYK colour listed none, unlike the
comment list and the drawing. Non-FreeText comments now read /C by the
appearance builder's rule (one entry grey, four CMYK with a non-number as 0
and a reference unreadable, finite and clamped), with the three-number
fallback reading referenced entries.

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

---------

Co-authored-by: mtlcl410 <chuanlin@niar.org.tw>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 08:29:31 +08:00
mtlcl410 31c9d91877 Merge remote-tracking branch 'upstream/main' into lane3-6491
# Conflicts:
#	ROADMAP.md
2026-11-09 02:23:29 +08:00
FredApps 8d4f2db5b1 Merge branch 'main' into fix/redact-nan-quads 2026-11-08 17:51:37 +01:00
mtlcl410andClaude Opus 5.5 fca09dd365 fix(render): the failed-decode tests revisit a size and draw both pictures on the second page (hayro-interpret patch 91)
Tests only: a JPEG 2000 failure kept for one size is still kept after drawing at another, and
the second page draws the picture decoded on the first and the one the first page's allowance
left out, through one cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 12:39:58 +08:00
mtlcl410andClaude Opus 5.5 d5b919e17f fix(render): the allowance threshold in the failed-decode test is found by a binary search over the first page (hayro-interpret patch 91)
Tests only: the search for the smallest JBIG2 allowance that draws the first picture renders the
first page alone and halves the range, about ten draws instead of up to two thousand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 12:09:47 +08:00
Chiranjeevi U adbf1dc01f M9: signed appearances show their real validation state (#79) (#2808)
* M9: redraw named-layer signature status layers

- /N drawing one Form that holds n1 and n3: n1, n3 and n4 redrawn for the state
- copies made once per original and state; originals never edited
- Lucide icons; captions in Helvetica, embedded Inter, or craft-fonts outlines

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: draw signature states in the display copy, exports and print

- marks follow the bytes shown; a changed verdict redraws after a metadata edit
- background opens pick up the trust and captions current at commit

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: status captions in the interface language

- the three captions in all 22 catalogues
- Signatures panel colours from sign::status_rgb

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: signature badge in the widget's corner

- a quarter of the shorter side, 48pt cap; outline only under 32pt
- dropped for a drawn valid state; bottom-right where the appearance draws one
- sign_list reports verdict_shown; parity and roadmap

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: keep Arabic signature captions in logical order

Pass raw RTL catalogue text to the signature appearance shaper so UAX #9 is applied exactly once. This keeps the rendered word order and exported /ActualText consistent.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M2: read /ActualText over painted paths and pictures

- A replacement that marks no glyph reads where it paints, cut to what is visible and along the way it is drawn.
- A span the page content leaves open ends before the annotations.
- OCR and scan field detection still count such a page as having no text of its own.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M8: redaction strips /ActualText around paint it clips

- A path or shading written again under a clip counts as changed, so a partly redacted word drawn as outlines leaves no name in the file.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: recognise only layer forms that draw n1 and n3

- Settle on the one form holding both first, then read its content once per call.
- Skip an /N that is its own layer form; copies keep the layer's /OC.
- A verdict counts as drawn when any visible widget shows it.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: keep the signature badge and redraw wherever a state changes

- The badge stays for every state; its mark sits bottom-right as the widget lies on the page.
- An edit that changes a verdict redraws every page; captions apply before startup files open.
- Print, the CLI's render and the repair log follow the display copy.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: restore the attribution usage the rebase dropped

- Usage lines for the three icons, Inter and the craft-fonts input; ATTRIBUTION.md rebuilt from them.
- Roadmap and localization notes for this round.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: pin signature field names on a nested form

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: bound the signature field walk

- kids share their parent's name and type instead of each copying them
- a widget stays listed under every field that claims it, as one shared copy
- a field written in place in a shared /Kids array is walked once
- caps of 20,000 fields, 100,000 entries and 4096-byte names; no states drawn past them

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: keep a document signed when the field walk stops early

- list_cached adds a signed row of unknown validity for the fields the walk didn't read
- Save As and actions then append, and Optimize, Sanitize and full rewrites are refused, as for any signed file

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M8: removing hidden content no longer strips the names of shadings

- With no regions to clip, a shading is left alone instead of wrapped and marked clipped
- clean_replacements skips scopes already marked, so deep nesting stays linear

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M2: read a picture's /ActualText over the whole picture

- draw_image's transform maps pixels, not a unit square, so the text sat on one pixel in the corner; the test now draws a 2x2 picture
- Painted grows in place instead of being copied through an Option

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: cap the bytes of names the signature field walk keeps

- A kid's name repeats its parent's, so one long name over 20,000 kids came to 80 MB; the walk now stops at 4 MiB of names and counts the rest as unread
- Cover the widget written into a shared /Kids array and the field cap among kids, which no test caught
- sig_fields is private again, and the cap docs say what happens past each cap

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: skip status-layer work that changes nothing; fix tests that could not fail

- show_status_layers and has_status_layers return before reading the form when nothing is signed, so an unsigned form no longer walks every field on each edit
- revalidate and draw_captions leave documents with no drawn state alone, and no longer copy the signature list to update it
- Scan form detection reads the page's text once; comment and badge tips share one hover rule
- Tests: drop a table row that repeated another, stop taking an expectation from the code under test, stop comparing an Arc with itself, give bare asserts messages

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: test the signature field name cap at the engine boundary

- A 4,096-byte name is read and blocks a full rewrite; at 4,097 bytes the list holds the unread-fields row and a full rewrite is still refused

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: test signature states beside fields the viewer draws

- One display copy holds both a field's drawn appearance and the signature's state.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: draw Thai and Devanagari status captions in their own script

- script_of names Thai and Devanagari, so a craft-fonts face meant for either draws the caption as outlines; without one it stays English.
- Localization, parity and the craft-fonts usage line list the new scripts.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M2: paint left out by an exclusion mixes its /ActualText replacement

- A replacement placed by outlines or pictures, some of them excluded, fails filtered extraction as one with excluded glyphs does.
- Glyphs still place a replacement that has them.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: a long-named field that can't hold a signature leaves the walk whole

- A leaf of another type passed over for its name is no signature field and has none below it, so an unsigned form stays unsigned.
- A signature field, or any field with kids, still counts as unread.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: status captions use document faces only

- Each script takes the face PdfCraft writes it into documents with, so Chinese stays English although the pinned craft-fonts' interface-only Chinese face is meant for Hani.
- The craft-fonts usage line names Devanagari: the pinned revision has a face for it, and Hindi captions are drawn in it.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: refuse a new signature field the field walk can't read back

- Signing adds a field only when the walk reads the whole form with it: past a cap the signature would be listed as unread fields, and its name could repeat an unread field's.
- A field the walk already reads is signed as before.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: refuse a new signature field with a blank name

- The walk lists no field without a name, so signing now also requires the walk to list the new field: one named only with spaces or NULs would be a signature nothing shows.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: an unnamed signed field or an unreadable form counts as unread

- A signed field with no usable /T, or an /AcroForm or /Fields of the wrong type, cuts the field walk short: the document keeps its unread-fields row and stays signed.
- Signing refuses a blank new field name before it adds the field, so the refusal still says why.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: no display copy when no signature state is drawn

- display_bytes_for makes a copy only when it added an appearance or drew a state, as the app does.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: the verdict mark stays on the page

- A widget past the crop box takes its mark in the part on the page, or none when too little of it is there.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: typed values stay drawn while a signature redraws every page

- A form edit that changes a signature's state puts its values back in unbaked after the pages are invalidated.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: the action, Save As and shading tests check their effects

- The signed action and Save As tests reopen the output for the title and the comment; the shading test checks its clip.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: values typed earlier stay drawn while a signature redraws every page

- A form edit that changes a signature's state keeps every value still waiting on its page, not only its own.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: comment and content edits keep typed values drawn too

- An edit to one page's comments or content that changes a signature's state moves no page, so the values still waiting on their pages stay pending, as after a form edit.
- The pending-value test runs a field deletion and a comment after a fill, under a certification that allows the fill; its messages say what it checks.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: the language-change test skips without catalogs

- Like the other language tests, it returns early when the build has no craft-i18n catalogs.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

* M9: signed fields behind kids without a name count

- A kid with no /T but kids of its own is walked as a field node, so a signed field under it is listed by its full name.
- An unnamed kid claiming a signature its field doesn't have cuts the walk short, so the document stays signed and keeps its unread-fields row.

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>

---------

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
2026-11-07 20:37:17 -06:00
mtlcl410andClaude Opus 5.5 4c3ad2895c Merge upstream/main into fix/tounicode-cidrange-text
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 00:29:06 +08:00
mtlcl410andClaude Opus 5.5 7bbed34d01 Merge upstream/main into fix/cjk-ucs2-text
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 00:21:51 +08:00
mtlcl410andClaude Opus 5.5 121bf5ecce test(render): a JPEG 2000 picture that doesn't decode is decoded once at each size it is drawn at (hayro-interpret patch 91)
The failure is kept per key, and a JPX decode's key includes the size it
decodes for, so the same failed picture drawn at two sizes is decoded once
at each. The test fails if the cache key leaves the target out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 19:16:16 +08:00
mtlcl410andClaude Opus 5.5 9b3991990d fix(render): a Type 3 font without /Widths is reported as a render warning (hayro-interpret patch 90)
The fallback to the glyph procedures' d0/d1 widths keeps such a page
readable, but ISO 32000-2 Table 110 requires /Widths in a Type 3 font,
so the reader should also be told the font is malformed. Selecting a
Type 3 font without /Widths (or /FirstChar, /LastChar) now reports
InterpreterWarning::Type3WidthsMissing, surfaced once per page as
RenderWarning::Type3WidthsMissing: `pdfcraft-cli render` prints it and the
automation render tools list it with the page's other warnings.

Test: raster::tests::type3_proc_widths::a_type3_font_without_widths_is_reported
(fails with the report switched off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 18:10:35 +08:00
Josias Eli Rodriguez 6e0d0fde3f redact: treat unusable /QuadPoints as absent in every reader
A quad counts only when all 8 entries are present, numeric and finite.
Anything else contributes nothing — never folded, shifted or zero-filled
into a box the document does not describe. With no usable quad the
existing /Rect fallback runs, and with neither the mark stays unreadable.

The rule lives in one helper, geom::quad_points::usable_quads, and redact,
render-inspect, annot summaries and annot appearance drawing all read
through it, so the outline shown is the area apply removes. Redact also
follows indirect numbers in /QuadPoints now, which it used to drop.
2026-11-07 03:04:51 -06:00
mtlcl410andClaude Opus 5.5 558ffd7f8a perf(render): a picture that doesn't decode is decoded once, not at every draw (hayro-interpret patch 91)
The decoded-image cache stored a failed decode as an empty entry and read
an empty entry as a miss, so a picture or stencil mask that doesn't decode
(damaged data, a JPX or JBIG2 stream its decoder refuses) was decoded again
at every draw, and at every tile, preview and zoom of a render worker that
shares the cache.

The cache now keeps a failure for its key, with the warnings the decode
reported; a later draw reports the same warnings and gets nothing, as the
decode did. A failure counts 128 bytes towards the cache's 64 MiB budget.
A failure the page's JBIG2 allowance may have caused (ContentTruncated, or
the allowance spent) isn't kept, since the next page starts afresh.
Successful decodes are cached as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-07 15:22:28 +08:00
mtlcl410andClaude Opus 5.5 30164b6973 M2: the soft-mask tests count image and mask decodes apart and check a truncated mask's size
decoded_images_serve_every_size_they_are_drawn_at asserts image and mask
decodes separately, through a new InterpreterCache::decoded_mask_decodes
(hayro-interpret patch 82), so a wrong split behind the same total fails.
The truncated-stream case of
an_averaged_down_soft_mask_follows_its_samples_and_its_drawn_size asserts
the exact mask handed over: 32 x 10 (the decoded rows), all opaque.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-07 14:45:30 +08:00
mtlcl410andClaude Opus 5.5 6d3824d066 fix(render): a Type 3 font without /Widths advances by its glyphs' d0/d1 widths (hayro-interpret patch 90)
A Type 3 font without /Widths (PDFBOX-2943 misspells the key) gave its
glyphs no advance, so the text was drawn as one blot and extracted as
characters stacked on one spot. ISO 32000-2 Table 110 requires /Widths,
and 9.6.4 has the glyph procedure state the same width with d0/d1, so
such a font now takes that width, read once per code. A font with
/Widths is unchanged; a code outside FirstChar..LastChar keeps width 0
as Table 110 says. Rendering and text extraction share the advance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-07 12:20:04 +08:00
mtlcl410andClaude Opus 5.5 f1e7777d17 M2: name both families in the non-embedded font tests' comments (review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-07 09:04:28 +08:00
mtlcl410andClaude Opus 5.5 07c5163b66 fix(render): every Adobe collection, Identity-V and /H over CFF read through their UCS2 CMap, other collections do not (hayro-interpret patch 84)
Tests for the UCS2 route of hayro-interpret patch 84: Adobe-GB1, -CNS1 and
-Korea1 fonts with Identity-H and no /ToUnicode (CFF and TrueType), each
with a CID-to-Unicode pair read from its bundled UCS2 CMap; Identity-V
and /H over a CFF program; and a Type 0 font of a non-Adobe collection,
which gets no text through this route. Each collection's case fails when
its family is pointed at another collection's UCS2 CMap, and the
non-Adobe case fails when such a family is given one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 07:28:02 +08:00
mtlcl410andClaude Opus 5.5 49f200f99d M2: a non-embedded Times New Roman draws the glyphs its codes name (hayro-interpret patch 88)
A Times New Roman the PDF doesn't embed, written as a Type 0 font with a
CIDFontType2 descendant (TimesNewRoman,Bold, TimesNewRomanPSMT and the
other faces) and no /ToUnicode, now goes through the rule of patch (76):
each CID gives, through /CIDToGIDMap (ISO 32000-2 Table 115), a glyph
index in the author's font, and the substitute draws that glyph by its
name in the standard Macintosh order of the TrueType post table. Before,
the CID was taken as one of the substitute's own glyph indexes, which
ISO 32000-2 9.7.4.2 notes "are not defined consistently from one font to
another", and the ameli.fr mutation form drew "Salarié(e) du régime
général" as "Salari²*g+f w't²i lo g'ì²p²tcn".

Times New Roman numbers its first 258 glyphs in that order: the same
form's embedded Times New Roman subsets keep their glyph indexes, and 941
of their 942 /ToUnicode entries below 258 name that order's character.
Times and Times New Roman MT Std are other fonts and keep the old path.

Fixes #6449

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-07 07:09:33 +08:00
mtlcl410andClaude Opus 5.5 1d968fea8f M2: patterned stencil and tiling cell tests count offscreen draws instead of timing them; interleaved settings and uncoloured patterns keep their own cells
hayro patch (30) gains a test hook, `offscreen_draws`: per thread, the tiling cells drawn
and the pixels of every pixmap drawn offscreen, all made by `offscreen_pixmap`. The two
wall-clock bounds become counts: 5,000 patterned stencils draw one cell and less than a page
of pixels offscreen, and 3,000 fills of one setting draw one cell. New tests interleave the
settings of two coloured patterns with cells of one size and one of them at two scales, each
showing its own pattern in place and drawing one cell per setting, and paint an uncoloured
pattern in two colours and, at half opacity, both as a fill and as a stencil mask's copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-07 07:01:42 +08:00
mtlcl410andClaude Opus 5.5 3b47f80575 test: a bf entry wins over a CID entry for the same /ToUnicode code (create.text-extraction)
A `/ToUnicode` that gives one code both a `beginbfchar` / `beginbfrange`
entry and a `begincidchar` / `begincidrange` entry extracts the bf value
(ISO 32000-2 §9.10.3); the CID number is read only for codes no bf entry
covers. The case is checked with two-byte and one-byte entries: with
two-byte entries the CID lookup already falls back to bf entries at its
one-byte step, so the one-byte case is the one that fails if the CID
entries are read first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 06:42:54 +08:00
mtlcl410andClaude Opus 5.5 427c942aad fix(render): a Type 3 glyph's kept procedure holds the page's content budget too
CodeRabbit on #6409:
- a kept procedure holds its bytes within the page's content budget while the
  glyph is painted, as a decoded one does (context::hold_content), so a page
  skips the same glyphs once the budget is spent, kept or not;
- one decoded with no more than 64 KiB of budget left, which the budget may
  have cut short, isn't kept;
- each kept procedure counts its name and 64 bytes besides its bytes against
  the cache's 32 MiB.
Tests for the 64 KiB and 32 MiB limits and for two fonts with the same glyph
name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 17:54:56 +08:00
mtlcl410andClaude Opus 5.5 05dca28601 fix(render): a Type 3 glyph shown again isn't decoded again (hayro-interpret patch 87)
A Type 3 font now keeps each glyph procedure it decoded, so a glyph drawn many
times isn't inflated again for every copy. A real file uses a one-glyph Type 3
font as a fill pattern and draws that glyph about 650,000 times on a page; it
rendered in 11.2 s and now in 5.9 s, pixel for pixel the same. A procedure over
64 KiB isn't kept, and the fonts of one InterpreterCache keep at most 32 MiB
together, as fonts live as long as the cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 17:32:29 +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 519a9b719c fix(render): ToUnicode CID entries of four-byte codes are found too (hayro-interpret patch 85)
Review on #6359: the fallback tried byte lengths 0 to 3, and hayro-cmap
keeps entries by length 1 to 4, so an entry written with a four-byte code
was never found. It tries 1 to 4 now; the test adds a map written with
four-byte codes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 07:32:12 +08:00
mtlcl410andClaude Opus 5.5 e420bc5118 fix(render): a ToUnicode written with CID ranges gives its text (hayro-interpret patch 85)
A Type 0 font's /ToUnicode written as a CID CMap (begincidchar /
begincidrange, destinations as numbers, as Word writes it) now gives each
number as the code's Unicode value for text, as glyph lookup
(map_via_unicode) already reads it and as pdftotext and PDFium extract it.
ISO 32000-2 §9.10.3 has beginbfchar / beginbfrange there; read as a bf map
only, such text extracted nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 07:13:17 +08:00
mtlcl410andClaude Opus 5.5 fc28e85fb8 fix(render): an embedded CJK font without a usable ToUnicode reads its text through its collection's UCS2 CMap (hayro-interpret patch 84)
ISO 32000-2 §9.10.2: a Type 0 font of the Adobe-GB1, -CNS1, -Japan1 or -Korea1 collection maps
code to CID to Unicode through the collection's UCS2 CMap. Only fonts that are not embedded took
that route; an embedded one without /ToUnicode, or with one that maps nothing for a code, gave no
text. /ToUnicode still comes first; the UCS2 CMap answers the codes it gives nothing for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 06:40:33 +08:00
mtlcl410andClaude Opus 5.5 cb8d211eec M2: the loca-format fix is hayro-interpret patch (83)
Patch (79) was also taken by #3969, opened earlier; this PR's number moves
past the highest any open PR of ours or main uses (82): README row, code
comments and the render tests that name it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 03:37:18 +08:00
mtlcl410andClaude Opus 5.5 9f786e785a M2: the soft-mask shrink is hayro-interpret patch (82)
Patch (77) was also taken by #4459, opened earlier; this PR's number moves
past the highest any open PR of ours or main uses (80, with 81 for #5632):
README row, code comments and the render tests that name it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-06 03:33:08 +08:00
mtlcl410andClaude Opus 5.5 abc348894c M2: a loca table with padding after short offsets stays short (hayro-interpret patch 79)
Room for numGlyphs + 1 long offsets means long, room only for short ones
means short (padding after them allowed), and a table too short for
either keeps the field as it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-05 23:25:23 +08:00
mtlcl410andClaude Opus 5.5 02f0eaa8f0 M2: a TrueType font marked with an out-of-range loca format draws its glyphs (hayro-interpret patch 79)
A font whose head.indexToLocFormat holds neither 0 nor 1 (256, the bytes
of 1 swapped) had its long loca offsets read as short ones, and every
glyph came out wrong: "LINE UP" drew as boxes. The format is now taken
from the loca table's length.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-05 23:00:04 +08:00
mtlcl410andClaude Opus 5.5 f387c036d6 M2: a soft mask sized under /W and /H keys the raster by drawn size too (hayro-interpret patch 77)
`has_larger_mask` read only /Width and /Height, while `ImageXObject::new`
also takes the abbreviations /W and /H, so a large mask sized that way
cached its image's raster without the drawn size and a later, larger draw
reused the small mask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-05 21:41:22 +08:00