* fix(ui): validated signature display, snapshot tab reuse
Signature widgets with layered appearances baked the unverified state
into the page, so a valid signature kept reading 'Signature Not Verified'
while the banner and panel said valid. Validated verdicts are now drawn
into the icon and caption layers in the display copy only: the signed
bytes are untouched, saving keeps the original appearance, and files
without those layers or without a valid verdict render exactly as before.
Other valid signatures still get the verdict mark over their widget,
sharing one style with the Signatures panel, and hide it again once the
appearance itself shows the verdict.
View signed version and View revision opened a new tab on every click,
and every tab showed the document title, so repeats piled up identical
tabs. Repeats now reuse the snapshot tab, snapshot tabs read like their
source plus the suffix in the tab strip, window title and Combine picker,
and carry a notice bar naming the revision with a Close that asks about
saving first.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Address review comments on validated appearances and trust refresh
Count every field when checking shared appearance streams, so a
caption shared with an unvalidated field is never rewritten for the
valid one alone.
Pick the icon slot by drawing instead of length, and only when a
single layer qualifies. Bare rectangles never count as the icon, and
ambiguous or missing slots fall back to the overlay badge.
Revalidate and invalidate together through one helper, so trusting a
certificate always drops rendered pages. Trust alone still marks
nothing dirty.
Tests: mixed-verdict sharing, longer decorative borders, competing
and doubled layers, capture-before preservation, trust invalidation
without zooming, and signed Session round trips with rendering,
saving and passwords.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Prove the icon slot geometrically and cover real layered signatures
A lone line drawing is still not the icon: frames and borders span the
box while the status mark lives in a corner of it. The icon now has to
sit strictly inside the appearance frame and cover at most half of it;
anything else falls back to the overlay badge. Widgets without a
measurable frame veto the rewrite the same way.
Tests: parsed extents and inhabitant checks as units, both reported
border repros plus a frameless widget, and real layered signatures
built with cms::sign_detached through Session open, trust, rendering,
save and reopen, plain and password-protected.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Identify the icon by shape instead of geometry
A lone line drawing is still not the icon, but size and position were
the wrong signals: the real status mark is large, and geometry blocked
it. Frames, borders and panels are rectangles by nature while the mark
is not, so a drawing made only of axis-aligned rectangles is decoration
however it is stroked. Singularity, shared-stream and template checks
are unchanged; anything ambiguous still falls back to the overlay badge.
Tests: rectangularity as a unit, nested line-drawn frames, and a
triangular fixture mark in place of the rectangular one.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Budget the appearance walk and keep the marks with the bytes
The walk that finds a signature's icon and caption capped depth,
layers and fields, but not the work: a dictionary whose entries all
point at the next one is visited once per entry per level, so a small
hostile file never finishes opening. The walk now counts its visits
and gives up, and a field whose tree cannot be read to the end stops
the whole pass, because a layer we never read may be the one two
fields share and a rewritten shared layer puts one field's verdict on
another field's page. The sharing count is exact again with that: two
widgets of one field pointing at the same appearance claim it once,
or the field vetoes its own rewrite.
A mark used to outlive the bytes it described. A failed display write
left the page showing the file's own caption while the verdict still
claimed its state was drawn; a metadata edit dropped the mark while
the page kept drawing the state, so the badge landed on top of it. The
write fallback now drops the marks and logs the loss, and metadata
carries them over.
Tests: a self-referential appearance tree, which never finished before
the budget, two widgets sharing one appearance, a metadata edit that
keeps its mark, a save that goes through the writer instead of
short-circuiting on the opened file, and the snapshot bar's Close
button pressed for real.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* Cite the new regressions in parity, restore main's installer note
The appearance entry now cites the bounded walk and the save through
the writer, and says what a tree that cannot be read to the end does.
The installer entry carried main's older text, which still claims
signed installers are planned; main's newer note is taken verbatim.
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
* M9: preserve rounded decoration beside raster status glyphs
* M9: place appearance progress beside signature history
* M9: retain appearances with ambiguous curved layers
* M9: keep signature verdicts in viewer overlays
Defer generic appearance rewriting: caption text and a unique drawing do not establish an icon role. Preserve original artwork for saves, image and text exports, and both print modes while retaining snapshot reuse and trust refresh. Cover chamfered borders and mixed signer/caption streams through signed Session round trips.
---------
Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
* fix(render): sampled functions index a flat sample array (vendored hayro-interpret patch 31)
A Type 0 sampled function kept its samples in a HashMap keyed by each point of
its /Size grid (two SmallVecs per key, SipHash), and the renderer builds a
shading's functions again every time it is painted, so every paint hashed one
insert per sample. A 2,097,152-sample ramp painted 30 times took 38 s; the
pdf.js corpus's bug1721218 (InDesign) spent most of a 1.5 s page on it.
The samples now stay in stream order in one Vec, and a grid point is found by
strides (first input fastest). The interpolation keeps the same recursion and
arithmetic, so results are bit for bit the same, and a point the stream ended
before still gives no value. The repeated-paint page draws in well under a
second. The maximum sample value no longer computes 2^32 in a u32 (a panic with
overflow checks on at 32 bits per sample).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoEx9Nj8KC4WKDcf5YaokQ
* fix(render): one decode of an image serves every size it is drawn at (vendored hayro-interpret patch 32)
The decoded-image cache (patch 24) keyed each decode by the size the image is
drawn at, but only the JPX filter decodes a reduced resolution for a size:
every other filter decodes the whole image. So a JPEG or Flate scan missed the
cache at every zoom step, was decoded again, and was kept once per zoom level.
The key now carries the target size only when JPX decodes the image, its
/SMask or its /Mask (read from /Filter or /F, erring towards keeping the
target). `InterpreterCache::decoded_image_decodes` counts the cache's decodes;
the new test draws a Flate image at five sizes and sees one decode, and a
copy whose soft mask is JPX-encoded still decodes per size. Both assertions
fail with the old keying.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoEx9Nj8KC4WKDcf5YaokQ
* fix(render): patch meshes copy their vertices' colours less (vendored hayro-interpret patch 33)
A Coons or tensor-product patch is drawn as 722 triangles over a 20 x 20 grid
of points, and every vertex's colours (a SmallVec) were produced or copied up
to five times: each grid cell interpolated its four corners itself (1,444
interpolations a patch for 400 points), cloned all six vertices of its two
triangles, and the mesh sampler cloned each vertex again to move the triangle
into device space. Profiling page 8 of the annual report in #1584 put a
quarter of its time in those clones.
Each grid point's colours are now interpolated once, a cell's second triangle
takes the vertices its first copied, and the sampler moves a patch's freshly
made triangles instead of copying them (`TriangleSampler::sample_owned`; a
free-form triangle mesh, which the shading keeps, is still copied). The same
inputs give the same values: rendering the first three pages of every pdf.js
corpus file is byte for byte the same. Release build, Linux: report page 8
773 -> 696 ms, personwithdog.pdf 149 -> 129 ms, issue13520.pdf 104 -> 87 ms.
The new test pins the colours near each corner, at the centre and between them
for a Coons and a tensor-product patch (taken before the change); swapping two
vertices' grid points makes it fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoEx9Nj8KC4WKDcf5YaokQ
* docs: roadmap entries for faster sampled functions, image decodes and patch meshes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MoEx9Nj8KC4WKDcf5YaokQ
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A cover counted only when it hid 40% of the line's words or box, so a
black box over one number in a long line, and an unapplied redaction
mark over it, audited clean while the number was still extractable. A
cover that hides half of any one word now counts as well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
`CLAUDE.md` asks for a progress-log line and a status line at the end of a session.
- The Annex D.2 alias codes, in both tables (`13e3154a`).
- Reading order in the text oracle (`50cbf5fa`), with gap 12's revision history.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
`cargo xtask text-oracle` reported one number: word F1 against `pdftotext`. F1 is a bag of
words. It catches a character read wrong, a glyph not read at all, a font we cannot map -- and
it is blind to order, so a page emitted back to front scores a perfect 1.000. Reading order is
most of what text extraction has left to get wrong: a column read into the middle of its
neighbour (#2387), a figure's labels threaded through a paragraph (#2586), a form's value parted
from its label.
The hole is not hypothetical. Reviewing #2431, a change that moves text and alters nothing else,
**word F1 called 26 of the 29 files whose output changed identical**, while scoring the word
sequence separated 18 improvements from 6 regressions and named the shape of the regressions
(forms: `prefilled_f1040.pdf` 0.672 -> 0.481, a checkbox's number parted from its label). The
harness could not see the only thing that change does.
So a second score per file: the longest common subsequence of the two word sequences, as a share
of the longer one. It shows up in the first three corpus files -- `Test-plusminus.pdf` scores F1
1.000 and reading order 0.875, the same forty words in a title block that we read down the
column and `pdftotext` reads across.
Both sides are cut to the first 3,000 words, because the comparison is quadratic and three pages
is plenty of signal; the run says how many files that bit, so the cap cannot quietly become the
measurement. It reports; it does not gate, and like the pixel score it is a *disagreement* with
Poppler rather than a verdict -- in the #2431 measurement the same split scored as a win on one
file and a loss on another purely because `pdftotext` makes it in one place and not the other.
The function's doc comment, `crates/oracle/README.md` and `docs/gaps.md` gap 12 all say so.
`words` now takes the sequence rather than re-splitting the text, so both scores read the same
tokens. `docs/file-format-parity.md` claimed plain-text export was good "(word-F1 0.98)" in a row
whose own column is reading order; it now says the two are scored separately.
Verified: `cargo xtask text-oracle --limit 60` end to end; `cargo test -p xtask` 49 passed; fmt
and clippy clean. The unit test was watched failing with the score blinded to order (sorting both
sides made it 1.000 where it must be below 0.7). The corpus-wide first reading is not in this
commit: the machine is shared with a parallel build and the full sweep was stopped twice for
memory.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
#2305 fixed WinAnsi 0xAD in `crates/fonts`, and closed with two loose ends it named: the
user-visible extraction path goes through hayro, not that table, so it still reported the
invisible character; and the same Annex D.2 note makes 0xA0 the `space` glyph. This finishes
both, and makes the two tables in the product agree about the same byte.
WinAnsiEncoding gives codes 0xA0 and 0xAD the glyph names `space` and `hyphen` -- the names it
also gives 0x20 and 0x2D -- and MacRomanEncoding gives 0xCA `space`. The tables called them
`nbspace` and `sfthyphen`, whose Adobe Glyph List values U+00A0 and U+00AD are invisible
formatting characters, so extraction hid ink the reader can see: "well-known" drawn with the
0xAD byte matched no search and no redaction pattern, and a non-breaking space did not match the
space a reader typed.
Evidence. Two sources transcribe the table the same way (an Annex D implementation's docs, and
the WinAnsi.enc encoding vector R ships, which gives `/space` at octal 240 and `/hyphen` at
255), and a black-box check settles the rest: on a synthetic fixture pdftotext reads the
base-encoded codes as `a-b a b`, and keeps U+00AD for a code an explicit `/Differences` names
`sfthyphen`. We now do both. Over the 989-file pdf.js corpus the soft hyphens we extract fall
from 117 to 13 and the non-breaking spaces from 30 to 26; everything left comes from `/ToUnicode`
maps, which say what they mean and are not touched.
Scope. The base table only, in the text direction only.
- A `/Differences` entry naming `sfthyphen` or `nbspace` is a deliberate statement about one
code and keeps its glyph list value, as pdftotext does.
- Glyph lookup is untouched, so a font program's own `sfthyphen` program is still found first.
In hayro that is why the alias sits in the Unicode path rather than in the generated encoding
table: `code_to_ps_name` is shared with lookup.
- Writing is unaffected: `encode` takes the lowest code showing a character, so ' ' and '-'
still write 0x20 and 0x2D, and a caller holding a literal U+00A0 still writes 0xA0.
Tests. `fonts` pins both tables and the whole decode/encode path including the `/Differences`
case; `render` pins the extraction a reader actually gets, over WinAnsi, MacRoman and
`/Differences`. Each was watched failing with each cell and each match arm reverted in turn, so
no assertion is passing by accident. fonts 70, render 296+4, redact 49, a11y 43 and edit 156 are
green, with fmt, clippy, `xtask layers` (37 crates) and `xtask assets` clean.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Fix main UI regressions in paging, fonts and test expectations
* Wait for asynchronous dropped-document preparation in regression
---------
Co-authored-by: Codex <codex@openai.com>
the Revert prompt to zh-hant.tsv; merged together, the catalog held both
copies and `bundled_catalogs_are_consistent` (and the tests that load
zh-hant as a reference) failed on main. Keep #1750's wording (字型大小,
「」 quotes, as the rest of the catalog) and drop the other copy.
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Nine features carried `evidence`, `notes` (and once `tools`) twice, so the
file was no longer valid TOML and `cargo xtask parity` stopped with
"duplicate key". Each pair is merged into one: evidence and tools as the
union of both lists, notes keeping every fact either side added.
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
signing_with_translated_labels_through_tools (from #1951) creates and uses
a digital ID with the password "secret1"; #2201 ("harden .p12 digital IDs")
now requires at least 8 characters. Each passed alone; together the test
fails on main, and so does every PR's test job. The password is "secret12"
in both places.
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* M3: release Ctrl zoom for pixel-only Wayland wheel events
Use the reported scroll source to avoid unterminated finger gestures for wheel, wheel-tilt and continuous input. Exercise the production policy through egui, including modifier release and preserved finger momentum.
Assisted-by: OpenAI Codex
* M3: keep Cargo lockfile dependency ordering canonical
Record the dependency ordering produced by the completed workspace and wasm gates.
Assisted-by: OpenAI Codex
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Comments panel: page groups fold from their chevron (fixes#1554)
* Comments panel: translate the fold-all buttons in every catalog
"Collapse all pages" and "Expand all pages" were missing from the 21
catalogs, so the covers_ui_literals tests failed; the catalogs' hashes
are re-pinned in ATTRIBUTION.toml.
* Add the Vietnamese entries for Collapse/Expand all pages; refresh catalog hashes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(render): keep annotations readable under reading colours (#1548)
Reading colours remapped annotation paints as line art: the yellow
highlight fill is luminance-light, so it was mapped toward the dark
background, and Multiply then darkened the remapped-white words under
it into invisibility.
Two changes in hayro:
- The /Annots loop in interpret_page is bracketed by a new
begin_annotations/end_annotations Device signal (patch 26), and the
renderer's reading-colours mapping leaves annotation paints alone.
Reading colours replace document colours, never the colours a
comment was given — the Acrobat model.
- The page background is painted inside the page transparency group
(patch 27), as the initial backdrop, so a Multiply highlight blends
with the background too. With the background outside the group the
band kept its own flat colour and the words vanished into it. White
pages are unaffected: multiplying with white is the identity.
Regression test reading_colors_keep_highlight_annotations_readable
covers both annotation paths (/AP and the no-/AP /QuadPoints
fallback) under WHITE_ON_BLACK; parity entry a11y.replace-page-colors
updated with the behaviour and evidence.
Co-Authored-By: GLM <noreply@z.ai>
* contributors: credit @edurbs as Eduardo Soares
The contributor's own entry, chosen by them (real and display name).
Co-Authored-By: GLM <noreply@z.ai>
* #1557: record the vendored hayro patches in vendor/README.md
Main already uses hayro-interpret patches (26)-(28), so the annotation hooks become
hayro-interpret (29) and the renderer's parts hayro (17) and (18).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: GLM <noreply@z.ai>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Constrain the existing normalization to XML1.0 name characters while preserving
valid tags and original PDF names through xfdf:original. Verify Unicode names,
prefixes and normalization collisions with saved-PDF round trips.
Co-authored-by: MMexicann <MMexicann@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M6: preserve carriage returns in exported form CSV
Quote CR in names and values so reimport cannot discard a trailing CR as a
record terminator. Verify eleven combinations through saved-PDF round trips.
Co-Authored-By: Codex <noreply@openai.com>
* Keep main's ROADMAP status line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: MMexicann <MMexicann@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M14: discover installed UI fallback fonts through Fontconfig
Find existing fallback faces in configured directories, including Nix store paths. Bound and cache discovery while preserving candidate names, glyph checks and the screenshot opt-out.
Assisted-by: OpenAI Codex
* M14: keep the drop-dispatch regression deterministic
Select inline document preparation in the existing drop-dispatch test, matching the validation fix used by both Wayland branches. Full workspace, strict Clippy and wasm gates pass.
Assisted-by: OpenAI Codex
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M1: edit finds the pages a damaged file's readers find (fixes#1656)
* M1: rebuild a lost page tree only when the reader shows the same pages, and refuse edits to signed rebuilt files (#1656)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Complete the Arabic (العربية) catalog: every missing row translated
ar.tsv covered 2,325 of the interface rows; the newer catalogs (lt, cs,
es and friends) had since grown to a 2,578-row union. This adds the 253
rows Arabic was missing, in the same clean-room Modern Standard Arabic
and Acrobat-style vocabulary the file already uses:
- digital-signature verification detail: every ECDSA/RSA/BER/DER reading
note, timestamp-token and revocation sentence, the certificate
inspector's sixteen fields (المُصدر، الرقم التسلسلي، صالح من/إلى…)
and the key-usage purposes (عدم الجحود، تشفير المفاتيح…)
- the accessibility checker's full vocabulary (وسوم، بنية المستند،
النص البديل، خلايا الرأس…) and its report grades
- the eight newer @plural rows with all six Arabic forms (صور محسّنة،
بكسلات، نتائج…) and the calendar's weekday abbreviations
- PDF/A output intents, combine/organize status lines, panel restore
errors, and the small chrome (اختيار…، تنظيم، تقرير مفصل…).
Same catalog, same lookups — Arabic now covers the union of every
language's rows (2,578).
* Refresh the ar.tsv sha256 in ATTRIBUTION.toml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: hasan-sdaif <306196098+hasan-sdaif@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M6: make list boxes directly selectable and scroll to selection (#1524)
- In-place selection: List boxes are rendered directly on the page and
selectable in place. Clicking an option row selects it immediately;
Ctrl/Cmd-clicking toggles items in multi-select lists, and Shift-clicking
selects ranges, without opening a popup menu.
- Appearance and scroll (/TI): When options are selected, /TI (top index)
is automatically adjusted so the selected items are scrolled into view
within the widget's visible rows, and option rows and selection highlights
are drawn into the appearance stream.
- Unit and UI tests: Added tests in pdfcraft-forms verifying /TI scrolling
and appearance generation for multi-select lists, and UI tests in
pdfcraft-ui-egui covering in-place click and Ctrl-click toggling.
Also ensured test harness in reload.rs explicitly sets language to en.
Fixes#1524
* List boxes: keep the engine's page appearance, consume arrow keys
Drop the overpaint of every fillable list box with an egui mock (it ignored the
PDF's fonts, colours and /MK /R; the engine redraws the appearance with the new
/TI), and consume ArrowUp/ArrowDown in the list overlay so they don't also
scroll the page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M7: grow text boxes and edit existing text
Text boxes now grow to the page edge and vertically as needed, retaining their size when saved. Clicking native or added text in either text tool edits the existing item instead of layering a duplicate over it.\n\nCo-authored-by: OpenAI gpt-6.1-sol <noreply@openai.com>
* #1539: build against main's Document::added() accessor and draft-aware prepare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Join the text nodes in each field value so XML comments and processing instructions do not truncate imported text. Cover data extraction and form import through PDF save/reopen.
Co-authored-by: storybored379 <storybored379@users.noreply.github.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M7: add Cut, Redact, Create Link and Recognize Text to the picture menu; keep the menu open
The right-click menu of a picture in Edit text & images gains Cut Image,
Redact Image (a redaction mark over the picture), Create Link… and Recognize
Text… (for the picture's page), with translations in every catalog.
The menu was drawn only while the pointer was over the picture, so an item
outside it, which is most of a small picture's menu and Delete once the menu
reaches below the page, could not be clicked. An open menu is now drawn
wherever the pointer is.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Update the catalog checksums in ATTRIBUTION.toml
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Fix the merged test file and checksums
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Keep added text selectable over form fields
Generated with Codex for @FredApps.
* Fix the content.rs test harness merge: harness_bytes and harness_with_step_dt share one builder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Print: 4 x 6 in label paper and a Label layout for shipping labels
Thermal label printers (e.g. VEVOR Y486) could not be used for eBay/carrier
labels: the paper list had no 4 x 6 in size, the chosen paper was never sent
to CUPS (the queue's default was used), and labels laid out sideways on a
Letter/A4 sheet printed small or rotated.
- PAPERS gains "4 x 6 in label (101.6 x 152.4 mm)"; cups_media() sends the
chosen sheet to the spooler as `-o media=` (Letter, Legal, Tabloid, A3-A5
and w288h432 for the label), so the driver feeds the size the PDF was
laid out on.
- New Layout::Label: each page is cut to its printed area (a low-resolution
render, as Fit Visible does), turned a quarter turn when it is sideways in
the direction that makes its text upright (text_reads_up reads the text
and form matrices), and fitted uniformly with a small margin. Vector
content is kept, so barcodes stay sharp and undistorted. "Turn upside
down" flips it 180 degrees. One label per sheet.
- Choosing the label paper selects the Label layout, and vice versa.
- Strings translated in every bundled catalog.
Tests: label geometry (crop, quarter turns, no stretching, margins),
cups_media, text direction; print, ui-egui and automation unit tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GFXSksdMaJ3YnheY7Q6sTx
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Alessio Boschi <alessio.boschi@dinova.one>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* feat(ui): preference to hide author-only highlight hover previews (#1432)
* Keep the Identity heading, translate the new hover preference, move its persist key out of json!
The heading rename dropped the existing Identity translation in every catalog; the
new checkbox label now has an entry in every catalog. The json! object in persist()
is at serde_json's recursion limit, so the new key is set after it like redact_prefs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* cargo fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M12: find untagged accessibility content
* i18n: translate untagged content checker (rebased)
* fix(i18n): refresh untagged content translation hashes
* fix(tests): restore full test modules for find-untagged content
* fix(i18n): preserve existing translations after rebase
* ci: rerun shared timeout test
* chore(deps): refresh rust for the advisory gate
Use the current lockfile, including the rustls security update, so the dependency check can run on this branch.
* Refresh catalog hashes after merging main
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M12: tag unstructured AcroForm widgets
* i18n: translate form tag command
* i18n: repair form-field label merges
* fix(i18n): refresh translation catalog hashes
* fix(i18n): preserve main translations after rebase
* Merge main; tag a widget listed on several pages once, docs say existing trees are extended
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge main; add the Vietnamese catalog entry for Tag form fields
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Defer secondary PDF indexes until first use
* Merge main into #1434; read Document::added through the new method in content_ui
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Use checked sample lengths and row padding before the EI delimiter heuristic.
Cover valid binary terminator-like samples, masks, Indexed, aliases, invalid
layouts and saved classic/packed PDFs.
Co-authored-by: MMexicann <MMexicann@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
While a comment is being drawn, the pointer in a band along any edge of the
viewport (or just past it) scrolls the view toward that edge, so a point
outside what is shown can be reached. Polygon, cloud and connected-lines
drawings take back their last point with Backspace, Delete or Undo; removing
the only point cancels the drawing.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(macos): Cmd+Q asks about unsaved changes (#1136)
winit's default app menu sends Quit (Cmd+Q) straight to
-[NSApplication terminate:], so PdfCraft quit without asking and
unsaved edits were lost; the close button and Dock > Quit already ask.
Install craft-quit's app menu (storytold/craft-libs#21) from the app
creator. Its Quit sends ViewportCommand::Close, which guard_quit
handles like the close button. If the menu can't be built, winit's
menu stays and a warning is logged.
craft-quit is pinned to the head of craft-libs#21 until #5 and #21
are merged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(macos): closing the window quits through terminate: (#1136)
On Touch Bar Macs the close button crashed PdfCraft (exit 133,
-[_NSTouchBarFinderObservation invalidate]): eframe drops the window
inside AppKit's run loop. craft_quit::on_close_request, right after
guard_quit in `logic`, cancels an allowed close and quits through
-[NSApplication terminate:] instead. A close that guard_quit holds for
the unsaved-changes prompt is left alone. Elsewhere it does nothing.
craft-quit moves to the workspace dependencies, pinned to craft-libs
main (f58bc1a, with #5 and #21 merged) instead of #21's head.
The on_close_request change and its Touch Bar test are from jmacek.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Automation: check the read and write roots on every path a tool reads or writes
* CLI: add --automation-read-root and --automation-write-root to mcp
* Docs: document --automation-read-root and --automation-write-root for mcp
* #1448: count main's doc_batch helpers as resolved by their caller
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Adding a word to a multi-line paragraph merged or reflowed its lines.
Two causes:
- Chrome/Skia subset fonts leave glyphs of the /DW width out of /W.
has_glyph treated those CIDs as missing, so words containing them
measured short, run calibration inflated, unchanged rows rewrapped
and new letters fell back to Helvetica. A composite subset with an
explicit /DW now counts a ToUnicode-mapped CID absent from /W as
present.
- Text drawn turned on the page (landscape pages drawn a quarter turn
round) was joined into lines and rows along the page's x axis, so the
whole paragraph became one line. Lines, rows and blocks are now found
along each text's own baseline (identical values for upright text), a
turned paragraph is rewritten in its own text space, and that rewrite
keeps explicit line breaks. Formatting part of turned text is refused
instead of being misplaced.
Claude-Session: https://claude.ai/code/session_01VFaMKaTDYNrroco63W74EQ
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Keep selected form values and page content through later edits (#1319, #1305, #1324, #1306)
An XFA radio item whose text contains a space was written back as an appearance-state name, so the next layout selected nothing. Replace Pages filled the gap between a split selection. Marking a figure decorative replaced every content stream on the page. Control clicks and drags accepted coordinates that overflow to infinity.
* Move the decorative-figure regression out of pdfcraft-edit
edit and a11y are both layer 4, so a dev-dependency from edit to a11y fails the layer check. The same regression now runs in the engine, which already depends on both.
* Drop pdfcraft-a11y from the edit crate's lockfile entry
* Run a tool after a background open of the file it asked for
A tool chosen with no document opened the picked file on the worker, but only ran when that open finished inside the picker callback. Remember the command on the pending open, and let the command test wait for the worker.
* Wait for background opens in the command tests
Opening a picked file or a recent file finishes on the worker, so a fixed handful of frames can assert before the tab exists.
* Compare batch copies to the PDF that was written
A new text PDF stamps the current second, so a later pdf() call is a different file. The numbered copy and the source must match the bytes that were saved.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M10: Windows print jobs show per-sheet progress; printers list faster
Follow-up to #944, carrying the two parts main doesn't have yet.
- The progress card follows a job being spooled and counts its sheets
as they reach the spooler ("Sending sheet 2 of 3 to Office…"), using
craft-print's new submit_with_progress. It replaces the "Sending to…"
toast, leaves another long job's card alone, and goes when the job
ends; the spooler's answer is still a notice.
- craft-print bumped to storytold/craft-libs#27, which also lists
Windows printers through .NET's PrinterSettings (the API jobs spool
with) instead of a CIM query of Win32_Printer.
- "Sending sheet {s} of {n} to {printer}…" in all 20 catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refresh catalog hashes after merging main
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M7: share Windows local-link fixture portability fixes (#1265)
Use the identical test-only prerequisite already proposed in #1263: Unix-only mutable vectors, ordinary Windows drive paths and escaped PDF literal strings. This is shared CI support for the separate xref overflow fix; production link handling is unchanged.
Generated with Codex.
* M1: return an error for exhausted xref object numbers (#1337)
Check the next-object addition and return CosError at the xref offset rather than overflowing or wrapping to object zero. Classic-table and stream regressions reproduced the original panic; ordinary, zero-offset and repaired-range controls preserve catalog access and allocation.
Validation: 2732 passed, 0 failed, 13 ignored; strict all-target Clippy, fmt, 31 WASM crates, layering, assets, deny and parity passed. The rebuilt CLI opened and rendered a clean synthetic page.
Generated with Codex.
* M1: place overflow notes beside stable milestone entries (#1337)
Keep the tested code unchanged while avoiding progress-note conflicts with newly landed main updates. Formatting and diff checks passed.
Generated with Codex.
* Merge main: adapt exhausted-xref repair to the compact xref index
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M14: correct parity checklist statuses (gap 24)
* M14: gap 24 parity fix-ups: localization reasons, stale Czech claim, split table
- misc.localization notes: drop the claim that Czech and Brazilian Portuguese
lack dialog coverage (cs.tsv 2,314 and pt-br.tsv 2,255 entries after #1066);
give the real reasons it is partial (Chinese tofu #826/#688, no Telugu release
font, Arabic not mirrored, English fallback). The language is settable through
the UI control channel, only not through an automation tool; 97 shipped
features have no tool, so that alone is not why it is partial.
- target-app-parity.md: header said localization "shipped"; it is partial.
- Mainstream/specialist split recomputed from the checklist so the rows add up
to the All row (508 + 304 = 812), with the slice rule written down.
- ROADMAP.md progress-log entry in the house format, noting that the breadth
move also counts earlier merges' checklist changes.
cargo xtask parity passes; the three cited i18n tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAUEUD5qJBoJd9rR3jyegt
* Merge main; keep only the parity status changes, drop stale percentages
Restore main's ROADMAP/docs numbers and header lines, keep the two
misc.localization/misc.installers status changes (22 catalogs now), fix the
gap 24 text and add a progress log line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Reuse the existing alignment picker for choice properties, keeping existing /Q and choice values until changed. Cover cancel, undo/redo, save/reopen and retained options/flags through the real UI.
Generated with Codex.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>