Commit Graph
68 Commits
Author SHA1 Message Date
jonathanwxh-cellandjonathanwxh-cell ab502255cb edit: preserve gap-drawn spaces and indents in subset fonts via /ActualText (#4229) (#4514)
Co-authored-by: jonathanwxh-cell <jonathanwxh-cell@users.noreply.github.com>
2026-11-09 18:13:34 +08:00
mtlcl410 7eea609c5d M3: copied text keeps one line per baseline, reading order for turned lines, letters for ligatures (#4467) (#4468) 2026-11-09 18:11:40 +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
bflatastic 08c0a59212 Merge main into merge/redact-forms-misc
# Conflicts:
#	ROADMAP.md
#	crates/render/src/text/tests.rs
2026-11-09 09:56:59 +07: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
Luigi Pinna 8c7ea6377e test(content): the #2456 tests use a filtered inline image (#2483) 2026-11-09 10:13:54 +08:00
bflatastic 9e6854869b Merge origin/main into merge/render-interpret-patches
# Conflicts:
#	crates/render/src/raster/tests.rs
#	crates/render/src/text/tests.rs
#	parity/acrobat-features.toml
#	vendor/README.md
2026-11-09 09:03:56 +07:00
bflatastic fbff06bd83 Merge main into merge/redact-forms-misc
# Conflicts:
#	crates/engine/src/tests.rs
#	crates/render/src/inspect/tests.rs
#	crates/render/src/text/tests.rs
2026-11-09 09:03:20 +07:00
bflatastic 45b95e3bd1 Merge #6597: M3: copied and found text keeps combining marks after their letter and right-to-left word breaks between words (view.copy-text, view.find)
# Conflicts:
#	parity/acrobat-features.toml
2026-11-09 08:20:39 +07:00
bflatastic e2f65dbf5a Merge #6572: M3: Find matches words on a vertical line (view.find)
# Conflicts:
#	vendor/README.md
2026-11-09 08:13:59 +07:00
bflatastic 5da76a640c Merge #6543: fix(render): a numbered glyph name at its own code reads as that code's character (hayro-interpret patch 100)
# Conflicts:
#	vendor/README.md
#	vendor/hayro-interpret/src/font/mod.rs
#	vendor/hayro-interpret/src/font/type3.rs
2026-11-09 08:13:01 +07:00
bflatastic e4cb041bdc Merge #6521: fix(render): glyph names read as text only in the glyph list's uni and u forms (hayro-interpret patch 92)
# Conflicts:
#	vendor/README.md
2026-11-09 08:10:37 +07:00
bflatastic 16312e1a10 Merge #6534: M2: a page without a /MediaBox is US Letter (view.render-pages)
# Conflicts:
#	vendor/README.md
#	vendor/hayro-syntax/src/page.rs
2026-11-09 08:07:49 +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 9d8e21de25 Merge #6355: fix(render): a page whose MediaBox or CropBox encloses no area shows its content (hayro-syntax patch 38)
# Conflicts:
#	vendor/README.md
2026-11-09 08:04:18 +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
mtlcl410andClaude Opus 5.5 d983cf6fcd M3: code 32 is the first code a numbered name may name (create.text-extraction)
numbered_glyph_names_read_as_their_own_code gains a font with `/C32` at 32
beside `/C70` at 70 and `/C0071` at 71. The font still reads, and code 32
gives a space, so a guard that also rejected code 32 would now fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 07:43:14 +08:00
mtlcl410andClaude Opus 5.5 8c892e6529 M3: a numbered-name counter that starts below 32 is not read as codes (create.text-extraction)
A subset that counts its glyphs in order of first use (`/Differences [1 /C1
/C2 ...]`) names its own codes, so numbered_names_name_their_codes held and
`/C65` (the 65th glyph used) read as `A`. A numbered name at a C0 control
code, which no text shows, now gives the counter away and the font reads
nothing. The check sits in the one function both the Type 1/CFF and the
Type 3 paths call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 07:23:00 +08:00
mtlcl410andClaude Opus 5.5 7ba96e24d7 M3: a Thai mark with its own advance doesn't open a word gap (view.copy-text)
A combining mark whose font gives it a real advance (a synthetic or
broken /Widths entry, as in the grouped Thai body edit test) moves the
pen, so the next letter starts at the mark's right edge. The cluster
ended at the base's right edge, so that advance read as a word gap and
a space opened after every cluster with a mark. The cluster now ends at
the mark when the glyph drawn after it starts at the mark's box edge;
a zero-advance mark, whose box draw_glyph widens and the next letter
overlaps, still leaves the gap measured from its base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 06:08:25 +08:00
mtlcl410andClaude Opus 5.5 684928f0a8 Merge upstream/main into feat/copied-text-letter-order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-09 05:29:50 +08:00
mtlcl410 2fb01992af Merge upstream/main into fix/numbered-glyph-name-code 2026-11-08 23:30:23 +08:00
mtlcl410andClaude Opus 5.5 aab3682629 M3: right-to-left punctuation doesn't decide a line's direction, and an alphabetic mark gives a taken-back neutral no direction (view.copy-text)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 14:19:30 +08:00
mtlcl410andClaude Opus 5.5 8c1977f051 M3: a ligature counts each of its letters toward a line's direction, and an unattached mark has no direction of its own (view.copy-text)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 13:55:51 +08:00
mtlcl410andClaude Opus 5.5 70b5755a7c M1: the numbered glyph name notes name the public readers only (create.text-extraction)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 13:38:41 +08:00
mtlcl410andClaude Opus 5.5 37d0f37110 M3: nonspacing marks count toward neither direction of a line (view.copy-text)
reverse_rtl's paragraph direction and keep_taken_back_blanks' line direction both leave glyphs
made only of nonspacing marks out of their counts (is_nonspacing_mark), so a few Hebrew letters
carrying many points don't turn a mostly Latin line right to left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 13:09:56 +08:00
mtlcl410andClaude Opus 5.5 ee4b044293 M3: a mark takes the class of the letter it rides on, and the paragraph direction counts letters before marks (view.copy-text)
- resolve_marks applies UAX #9 W1 only to a mark riding on the glyph before it (its anchor), so a
  Hebrew mark placed after a Latin letter keeps its own class and stays with its word.
- reverse_rtl takes the paragraph direction before marks take their letter's class, as
  keep_taken_back_blanks counts the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 12:31:11 +08:00
mtlcl410andClaude Opus 5.5 c4cc9e0c37 M3: a combining mark takes its letter's direction, a line's edge has the line's direction, and overprinting runs are placed by their clusters (view.copy-text)
- reverse_rtl applies UAX #9 W1: a nonspacing mark takes the class of the letter it rides on,
  so a Latin accent at a word end on a right-to-left line stays after its letter.
- keep_taken_back_blanks reads a line's edge as the line's direction (UAX #9 X10) instead of
  right-to-left, so a neutral at the edge of a mostly left-to-right line keeps its place.
- Rows of overprinting runs are ordered by where their clusters start, so a riding mark that
  reaches left of its letter cannot put its run first.
- The two-taken-back-spaces test checks the untrimmed text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 11:56:38 +08:00
mtlcl410andClaude Opus 5.5 65b7324c02 M3: vertical Latin widths come from /DW and are looked up by CID, and close columns stay apart (view.find)
Tests only: a CID missing from /W is /DW wide across the column (ISO 32000-2 §9.7.4.3), widths
are looked up by CID through an embedded vertical CMap, and two columns of /DW-wide letters set
13.5 pt apart at 20 pt read as two lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 11:31:45 +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 94203a2b78 M3: a taken-back space carries the marks on either side of it and reads numbers as right-to-left context (view.copy-text)
The letter after a blank and the marks drawn between them are gathered from
both sides of the blank in x order, so a tanwin pulled back over the space
with its alef starting at the space's own start still moves the space before
the word (PDFBOX-5487). Numbers beside a taken-back neutral count as
right-to-left, as in the bidi pass (UAX #9 N1), so '15)' followed by a full
stop keeps the stop after the number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 08:46:53 +08:00
mtlcl410andClaude Opus 5.5 fc8a4d6974 M3: a taken-back space moves only inside right-to-left text, past marks drawn before its letter (view.copy-text)
A neutral taken back over a space keeps the space before it only when the
nearest strong glyphs on both sides are right-to-left (or the line edge), so a
parenthesis opening Latin text is placed by x as on a line without Hebrew.
Combining marks drawn between the space and its letter no longer stop the
move, and the space, the marks and the letter keep drawing order. The pass
now runs after zero-width marks are merged, since that merge relies on x
order. Adds tests for the pdf.js #11656 content stream and for two
taken-back spaces on one line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 08:26:02 +08:00
mtlcl410andClaude Opus 5.5 3cbf001256 M3: merge the combining-mark clusters into the copied-text letter order branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 08:08:18 +08:00
mtlcl410andClaude Opus 5.5 118691e9b7 M3: pin the vertical glyph box under horizontal scaling (view.find)
A `50 Tz` variant of the tracked vertical Latin test: the box across the
column is w0 x size x Th, centred on the column (ISO 32000-2 §9.4.4 and
§9.7.4.3), and the column still reads and finds as one line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 01:38:54 +08:00
mtlcl410 1e100a11df Merge upstream/main into fix/find-vertical-line 2026-11-08 01:28:24 +08:00
mtlcl410andClaude Opus 5.5 cfbe3c9923 Merge upstream/main into fix/rtl-overlapped-space-order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 00:52:01 +08:00
mtlcl410 866e94e9b4 Merge remote-tracking branch 'upstream/main' into HEAD
# Conflicts:
#	crates/render/src/text/tests.rs
2026-11-08 00:43:32 +08:00
mtlcl410andClaude Opus 5.5 152d696fb0 M3: a space taken back under the next letter stays a word break, so right-to-left words are not split (view.copy-text)
A right-to-left show string in reverse order starts with the space that ends
its word (ISO 32000-2 §14.8.2.5.3), and a word processor pulls the next letter
back over it with a TJ adjustment a little larger than the space's width. The
letter then starts a hair left of the space, so sorting a line by x put the
space inside the word: copied and found text read "המדינ ה" for "המדינה" and
"כולם ?" for "כולם?" (pdf.js #11656), and Find missed both.

A blank glyph has no ink, so its box cannot place it against a glyph that
covers it. On a line with right-to-left text, when the glyph drawn right after
a blank is a right-to-left letter or a neutral, starts at or left of the
blank's start and covers at least half of it, the blank keeps its drawing
place before that glyph (§14.8.2.6.2: the space is a word break in the
character stream). Left-to-right text, a glyph that only touches the blank, and
a blank inside a letter-and-mark cluster keep their x order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 00:42:38 +08: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 879999e12b Merge upstream/main into fix/empty-page-boxes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 00:26:09 +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 d0da7938f2 M3: Find matches words on a vertical line (view.find)
A glyph of a vertical-writing Type 0 font now spans its width w0 from
/W or /DW across the column (ISO 32000-2 9.7.4.3), instead of its outline's
width. Uneven outline widths made a narrow letter's line allowance smaller
than the spacing of widely tracked text, so a column of Latin capitals with
-40 Tc read as "A\nB\nCD\nE" and Find found nothing.

hayro-interpret patch 102: OutlineGlyph::vertical_glyph_width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-08 00:01:03 +08:00
mtlcl410andClaude Opus 5.5 f1bd39e578 M3: a stacked, raised or squeezed combining mark stays with its letter (view.copy-text, view.find)
A second mark stacked on a letter now rides on the letter as well as on the
mark before it; a mark drawn up to an em above or below its letter stays on
the letter's line, and the glyph after it continues from the letter; marks in
a zero-width run are ordered by their letter; and a word gap after a mark is
measured from the letter's advance, not the mark's offset start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 18:32:56 +08:00
mtlcl410andClaude Opus 5.5 f75451380c M1: numbered Type 3 glyph names above 126 read as their codes too (create.text-extraction)
A Type 3 font already reads codes 32-126 by the code when its glyph names
say nothing. Above 126 a name that only numbers its glyph (`C174` at 174,
`GE9` at 0xE9) now reads as the code's Latin-1 character, under the same
font-level guard as a Type 1 font: every numbered name in the encoding
names its own code, so `/C1` at 233 turns the font back to nothing.

Test: text::tests::numbered_type3_glyph_names_read_as_their_own_code
(fails with the fallback switched off, and with the guard switched off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 17:50:16 +08:00
mtlcl410andClaude Opus 5.5 218fbcf1b5 M1: numbered glyph names read as their codes only in fonts where every one does (create.text-extraction)
The previous commit read a glyph name that only numbers its glyph (`C70`,
`MT72`, `G4E`) as its code's Latin-1 character whenever the number, in
decimal or hexadecimal, was that code. In fonts whose names number
something else (`/Differences [1 /C70 /C105 ...]`, a subset's order), a
name can match its code by coincidence: `/MT40` at 0x40 read `@` where the
page draws `(` (PDFBOX-1078), `/C39` at 0x39 read `9` (PDFBOX-1783).

The rule now applies only in a Type 1 font where every numbered name in
its encoding names its own code (`numbered_names_name_their_codes`,
worked out once per font). `/C1` at 233 beside `/C70` at 70, the review
counterexample on #3239, turns the whole font back to nothing, and
#3239's test font (`c251` at 97, `C1` at 233 beside `C39` at 39) reads
exactly as #3239 expected again. Codes 128-255 read as Latin-1
(`C174` = `®`), as pdftotext, PDFium, MuPDF and Acrobat read them.

Vendor ledger: hayro-interpret patch 99.

Test: text::tests::numbered_glyph_names_read_as_their_own_code (fails
with the guard switched off, and with the fallback switched off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 17:36:40 +08:00
mtlcl410andClaude Opus 5.5 5307510f66 M3: copied and found text keeps each combining mark after its letter (view.copy-text, view.find)
Text drawn one glyph per show operation, with zero-advance Thai tone marks
starting a hair past their base's advance, came out with the mark after the
next letter ("ห้อง" read "หอ้ง") because a line's glyphs were sorted by x
alone. A combining mark (Mn/Me) of a left-to-right script drawn right after
the glyph it sits over now rides on that glyph: placement sorts clusters, so
the mark keeps its content-stream place. Word gaps are measured between
clusters, and the tight-cluster rule now guards only loose combining signs,
so a Thai or Indic word gap with no space glyph reads as a space.

Fixes #6517

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
2026-11-07 17:08:09 +08:00
mtlcl410andClaude Opus 5.5 4a213e06b3 M3: glyph names read as text only in the glyph list specification's uni and u forms
`uni` takes groups of exactly four uppercase hex digits and `u` four to six,
outside the surrogate range, so `uni65`, `u65`, `uni20ac` and `uni+065` read
as nothing instead of `e` and the euro sign. hayro-interpret patch 92 reads a
glyph's text by `agl_unicode_from_name` and keeps upstream's lenient reading
for finding glyphs in a font, so drawing is unchanged; pdfcraft-fonts'
`glyph_text` stops accepting lowercase digits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-07 15:39:37 +08:00
mtlcl410andClaude Opus 5.5 ae79de8deb test(render): check a page whose box has no area on the render path too, and the inherited-box fallbacks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
2026-11-07 12:45:56 +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