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>
* 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>
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>
* 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>
- Pdf::new_with_password reads the table the last startxref names first: a wrong password is
refused through it, and the rebuilt table is taken only when it is encrypted exactly when
that table is.
- Tests: an encrypted file with a cut-off update needs its password; data after %%EOF holding
an object header reads as the document model reads it (both take its page).
- vendor/README.md lists the split tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
Tests only: a complete update, trailing data without objects and a short cut-off update each
have their own test, and the trailing-data case asserts the model's page count as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
* 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>
The renderer (hayro-syntax (44)) counted the sections its chain reached, and
a hybrid section's /XRefStm stream (ISO 32000-2 §7.5.8.4) counted as one, so
a hybrid update without /Prev was rebuilt by cos but not by the renderer. It
now asks what cos asks: whether the newest section names an earlier one
through /Prev. The /XRefStm stream belongs to the same update.
Tests: a synthetic hybrid fixture (pdfcraft-testkit), with and without
/Prev, in cos and render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
An update whose cross-reference section has no /Prev (ISO 32000-2 §7.5.6,
§7.5.8.2) cuts the earlier revisions, and the page tree with them, off the
chain. The signed PDFBOX-2720 and PDFBOX-2942 files have this shape: the
document model found no page tree and the renderer showed 2 blank pages
found by scanning for page dictionaries.
When the chain reaches only the newest section, the file holds another
startxref and the catalog's /Pages can't be read, both readers now rebuild
the table from the file's objects, as for a damaged file:
- cos: the existing reconstruction (newest in-file definition wins), a
repair-log line saying why, and `Document::rebuilt_unlinked_update`.
- hayro-syntax (44): `root_xref` repairs the table before the page tree is
read; `Pdf::rebuilt_unlinked_update` says so.
- render: an open warning, and the lazy structure reader accepts such a
rebuilt file, so links, fields and bookmarks are read too.
A save rewrites the file in full (main's rule for reconstructed files).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
- hayro-syntax patch 44: a /MediaBox without area counts as missing, so
the page is sized, placed and clipped by the same Letter region and its
content is drawn (MuPDF and PDFium both draw it on 612 x 792).
- pdfcraft-model: Page::crop falls back to Letter for such a box too.
- vendor/README.md: the raw-crop summary names the Letter fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
A page with no /MediaBox, its own or inherited, rendered as A4, while
PdfCraft's page model and inspector already sized it as US Letter and
other readers draw it at 612 x 792 pt (PDFBOX-4001-reduced.pdf).
ISO 32000-2 §7.7.3.3 Table 31 requires the box and gives no default;
follow the readers. `base_dimensions`' fallback for a visible region
without area uses the same Letter size, so a /MediaBox [0 0 0 0] page
is Letter too. Vendored hayro-syntax patch 44. The model test pins the
size it already gave.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCHD1R3rywQBPCHXZA
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.
Through /Indexed, samples past the decoded data read as black, so an
all-black image could not tell a decoder that stopped after one row
from one that decoded all four. A second Group 4 image striped black,
white, black, white (horizontal-mode rows, ITU-T T.6 §2.2.3.3) is now
rendered through both colour spaces with every /Rows value, and each
row is checked for its own colour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
When a file's last incremental update stops after its cross-reference rows,
with no trailer or startxref, the last startxref in the file is the older
revision's. pdfcraft-cos finds no startxref in the last 4 KiB and rebuilds
from object headers (latest objects win); vendored hayro-syntax read the older
table, so the view showed the older revision (PDFBOX-3934: 12 pages, the model
13). ISO 32000-2 §7.5.6: a later section supersedes earlier ones; repair is
unspecified and PDFium, MuPDF, Poppler and qpdf take the latest objects.
hayro-syntax patch 43: when the last startxref is more than 4 KiB from the end
and N G obj headers follow it, Pdf::new_with_password rebuilds first; the older
table is read only when the rebuilt one has no page. Trailing data without
object headers and a startxref near the end read as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
A CCITTFaxDecode image whose /DecodeParms /Rows exceeds the image's
/Height (PDFBOX-771: /Rows 1084975104 on 2,592 columns) asked the decoder
for /Rows rows; the size check refused that, and every page drew blank.
The image is /Height rows (ISO 32000-2 Table 87), so the decoder is asked
for those; /Rows still counts for a stream decoded without an image's
height. pdfcraft-cos already decodes /Height rows; a test pins it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
A writer declared /Predictor 15 on an image and its soft mask without
applying it, so the inflated data is exactly /Height rows of the row length
with no filter-type byte before each (ISO 32000-2 §7.4.4.4 requires one per
PNG-predicted row). Read with the predictor, every row's first sample was
taken for a filter type, the image came out a row short and shifted, and the
page drew it in fragments; image export said its data was shorter than its
size. PDFium, MuPDF and Poppler read such data as the samples it is.
Only that exact length takes the new path, and only for the last Flate
filter of an image whose height is known; anything else is predicted as
before. The renderer (vendored hayro-syntax, patch 40) reports it with
warn!, like its other lenient filter decodes; pdfcraft-filters returns it as
tolerated damage (partial in tolerant decoding, an error in strict), through
the new decode_image_tolerant_within that pdfcraft-cos uses for images.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
The previous commit was made on main's newer head with the older tree, so
it undid #977 and other changes from main. This commit puts main's files
back: the PR changes only vendor/hayro-syntax/src/page.rs, vendor/README.md
and crates/render/src/text/tests.rs again.
Review: a page's own box without area now gives way to an inherited one
with area before the default (A4 for a media box, the media box for a crop
box); the test adds a page whose own /MediaBox [0 0 0 0] inherits a larger
box with text past A4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
A /MediaBox without area ([0 0 0 0]) is read as a missing one (the
default page), and a /CropBox without area, or with none in common with
the media box, as a missing one (the media box, ISO 32000-2 §14.11.2,
Table 33). Kept, the page's visible region had no area: nothing was drawn
and no text was extracted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
The text layer asked the router whether the press's owner kept its drag,
link following asked it which link held the press, and the Link tool
started a new link from a press any link held, since the router kept a
declined drag's capture.
- `PageRouter::drag_started`: a drag the owner declines (text markup;
with the Link tool, a link) is nobody's once it starts, on the page it
began on. The Link tool draws a new link only from a drag nobody
holds.
- `PageInput` carries the press (`PagePress`) and whether its drag is the
page's text's (`text_drag`): the text layer and link following read
them, and `PageRouter::owner` goes. Only the page view reads the
router now.
- Image pick acts on a press nobody holds: a link's click follows the
link only, and picks no picture under it (the user's ruling).
- The I-beam drops over a link only where a press would go to it, the
top hit under the pointer (the user's ruling): the tools that select a
link's text show it there.
- `PageText::glyph_at` is the one test for the text under a point, for
the text layer and Redact's boxes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>