Commit Graph
389 Commits
Author SHA1 Message Date
Mex a9d7774bbc Merge branch 'main' into fix/xml-form-text-nodes 2026-10-17 13:37:34 +02:00
skylar taylor-barrick 924c4da4ac Merge pull request #2698 from mtlcl410/fix/audit-one-word-2662
M8: the redaction audit finds a cover over one word of a long line (#2662)
2026-10-16 16:05:39 -06:00
Chiranjeevi Uandskylar taylor-barrick 5fdd820bdb fix(ui): validated signature display, snapshot tab reuse (#1895)
* 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>
2026-10-15 19:16:48 -06:00
mtlcl410andClaude Opus 5.5 9dfc91e0e8 M8: the redaction audit finds a cover over one word of a long line (#2662)
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
2026-10-16 06:25:35 +08:00
Mex 14c3d951e7 M6: preserve complete plain XML form values 2026-10-15 23:04:14 +02:00
mtlcl410andClaude Opus 5.5 1bc3a31814 fix(parity): merge the keys concurrent merges left twice in acrobat-features.toml (#2419)
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>
2026-10-15 16:50:26 +02:00
cdf44d7999 Comments panel: page groups fold from their chevron (#1614)
* 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>
2026-10-15 04:03:49 -04:00
ab45a1855f Keep annotations readable under reading colours (#1548) (#1557)
* 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>
2026-10-15 04:03:25 -04:00
3805be7669 M14: store XDG preferences in configuration directory (#1395) (#1536)
Generated with Codex for @DWAA1660.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:52:12 -04:00
57c8de09ab M6: preserve XFDF field text around XML comments (#1647)
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>
2026-10-15 03:47:41 -04:00
b7976ef40e M12: show structure tag content (#1496)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:38:35 -04:00
a6124bcdfe M12: find untagged accessibility content (#1463)
* 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>
2026-10-15 03:33:19 -04:00
afa50999ef M12: tag unstructured AcroForm widgets (#1451)
* 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>
2026-10-15 03:24:55 -04:00
953ac1483f M14: correct localization and installer parity statuses (gap 24) (#1211)
* 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>
2026-10-15 03:01:44 -04:00
db34952a94 M6: expose dropdown and list-box field alignment (#1208) (#1263)
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>
2026-10-15 03:01:17 -04:00
6b185cdc5b Add permission-aware selected-text Copy to Edit and context menus (#1244)
* M3: add selected-text Copy to page and Edit menus (#1225)

Register selected page-text Copy with the existing document permission gate and expose it in Edit and the ordinary page context menu. Route floating Copy through the same gate while preserving focused-input and Organize clipboard ownership.

Four regressions fail behaviorally on the exact base; five candidate regressions pass. All eight cargo xtask ci stages pass: 2575 tests passed, 12 ignored, and 31 WASM crate checks. Native synthetic checks verify Unicode clipboard output, disabled no-selection/restricted actions, and permissions-password unlock using presented Xvfb/Vulkan software-rendered frames. Physical display and hardware GPU coverage are not claimed; the optional cargo-deny audit was unavailable.

Generated with Codex for FredApps.

* Tests: scope GoToR case mutation to Unix for Windows Clippy

Keep the common directory/non-PDF cases immutable. Shadow them in the Unix-only block so FIFO coverage can append its case without an unused-mut warning on Windows. No assertion or platform coverage is removed.

This repairs the exact hosted Windows Clippy failure inherited from upstream. Source review and whitespace checks passed; no local build, test or formatter was run during the pause. Automatically triggered hosted CI will validate this head separately.

Generated with Codex for FredApps.

* Tests: scope document link target mutation to Unix for Windows Clippy

Preserve directory coverage on all native platforms and device coverage on Unix. Static diff, whitespace and conflict checks passed; no local build, test or formatter was run during the pause.

Generated with Codex for FredApps.

* Tests: escape GoToR fixture file paths as PDF literal strings

Windows absolute paths were interpreted as PDF escape sequences, introducing control characters and failing the hosted local GoToR root test. Escape backslashes and parentheses in the fixture helper without changing product behavior or assertions. Static diff and whitespace checks passed; hosted CI will validate the repair. No local builds, tests or formatters ran.

Generated with Codex for FredApps.

* Tests: use ordinary absolute paths in the GoToR fixture

Build the absolute target from the original sandbox base rather than its canonicalized Windows verbatim path. Assert absolute-path form and canonical identity with the sandbox target. Preserve production UNC/device/verbatim rejection and existing containment and destination assertions. Static diff and whitespace checks passed; no local build, test or formatter ran.

Generated with Codex for FredApps.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:00:45 -04:00
e08f87f138 M5: Make Properties Default in Comment Properties (#2031)
* M5: Make Properties Default in Comment Properties

Acrobat's Comment Properties dialog has a Make Properties Default box
beside Locked. Ticking it and pressing OK makes the edited colour,
opacity, line width (and, for shapes, fill) the default for new comments
of that kind, e.g. new strikethroughs take the chosen colour.

The dialog applies the edited look, so the box also works when only the
default changes. Opacity and width go through the clamping setters: the
dialog allows 0% opacity, which would make an invisible default.

The box is translated in all 21 catalogues and the comment.default-properties
parity entry names the new test.

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>
2026-10-15 02:59:01 -04:00
Earl Co ad6728bf6c fix(cli): render comments the app renders when /AP is missing (#2371) (#2382) 2026-10-15 02:58:04 -04:00
cortenburg28-archandClaude 00442a0e9b fix(ui): a shape just drawn resizes from its handles without the Select tool (#2362)
Drawing a rectangle or oval selects it and paints its eight handles, but only the Select tool
answered them: with the shape tool still on, dragging a handle drew a second shape on top of the
first. The handles were a promise the tool did not keep, and anyone marking up a drawing had to
switch tools after every shape to fix its size.

A shape tool now hands a press on the selected comment's handles to the same select_input the
Select tool uses, as the Fill & Sign tools already do for their marks. Only the handles: a drag
anywhere else, over the shape included, still draws, so drawing one shape inside another keeps
working, and a polygon being clicked out keeps its clicks. The Eraser is left out. The handle
test is shared with `grabs` rather than copied.

Lines, arrows and polygons are unchanged: they have no resize handles with the Select tool
either.

Fixes storytold/pdfcraft#2338


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

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-15 02:57:54 -04:00
02a572909d feat(automation): accessibility_tags lists tags a page at a time, with filters (#2335)
accessibility_tags returned every structure tag at once. A 20-page tagged
guide printed by Chromium has 400 to 630 tags, 70,000 to 110,000
characters of JSON, more than an MCP client takes in one tool result.

It now pages like bookmark_list: offset, limit (default 200, at most
1000) and next, with count the number of tags that match. types, pages
and max_depth keep only some tags (the headings, one page, the top
levels).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:56:25 -04:00
Krzysztof Cieślik 331813e5de fix(ui): stop dropping cached tiles when zooming away and back (#2196) (#2323)
- Retain tiles of on-screen pages at both eviction sites in
  crates/ui-egui/src/canvas.rs, so a pan or zoom that moves the viewport no
  longer discards tiles that were rendered sharp moments earlier.
- Stop dropping a zoom's tiles when zooming out far enough shows the page
  whole; that made returning to a visited zoom a cold miss, which re-blurred
  the page and re-interpreted it once per tile.
- Bound older-zoom retention by bytes (TILE_BYTES) as well as by count
  (OLD_TILES): a tile of a large scan can be 4096 squared, about 67 MiB, so a
  count alone ran past the tile allowance.
- Add returning_to_a_visited_zoom_reuses_its_cached_tiles, which fails on the
  reverted fix, and cite both view tests in view.deep-zoom-tiles; the entry
  stays partial because the 8192 px-per-side cap is untouched.
2026-10-15 02:54:44 -04:00
89f0fa2bd5 M14: Italian catalog: complete the missing entries (#2320)
* M14: Italian catalog: complete the missing entries

it.tsv lacked 265 keys that the other catalogs carry. The 201 the code
looks up are added in one dated section at the end of the catalog: the
sentences the accessibility checker, preflight, signature validation and
page-range parser word (their WORDINGS), the certificate rows and calendar
weekday and month labels (their tl_ctx contexts), digital-ID, docking and
print messages, and five plurals (one|other). Italian now has 2,528
entries.

The new rows follow the wording the catalog already uses: history labels
say "Modifica ..." like "Change {key}", and the Creator and Producer keys
that label fills in say "Applicazione" and "Produttore PDF", as the
Document Properties dialog names those fields. The Optimize PDF summary
says "eliminato" like its Discard options, the checker and preflight
sentences say "ha rilevato" and "Viene usato", the trust messages say
"nel tuo elenco di certificati attendibili", the password and certificate
rows say "non corrispondono" and "rilasciato" like the existing "The
passwords don't match." and "Issued by" rows, and the print hint quotes
the "Disattivato" option as the dialog shows it.

The other 64 are left out because no non-test code looks them up:
- 10 tooltip texts with a fixed macOS shortcut ("Zoom in (⌘+)"); the code
  fills the shortcut in through "{key}" rows Italian already has;
- 9 older wordings the code no longer uses: the five Combine folder
  messages, "Save (M4)", " on page {label}", the owner-only security
  message and the old "Text recognition isn't installed" message (the
  code now says "its model files are missing");
- 45 generic labels and plurals only the Lithuanian catalog has. The code
  says "{n} file|{n} files" as two plain strings, "1 file" and
  "{n} files", which Italian has; "{n} comment|..." and "{n} result|..."
  are used nowhere.

The entries are AI-assisted translations written from the meaning of the
English (clean-room; no proprietary localisation consulted), and keep
their placeholders and trailing ellipsis. The catalog's SHA-256 in
ATTRIBUTION.toml is updated by hand, as the other catalog changes do.
localization-parity.md, the ROADMAP progress log, the M14 rows of
docs/roadmap.md and docs/localization.md record the new count, and
parity/acrobat-features.toml now says Italian translates the engine's
sentences too.

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

* Refresh catalog hashes after merging main

---------

Co-authored-by: kcobelli-pixel <257471167+kcobelli-pixel@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:54:32 -04:00
Adrián Iglesias ÁlvarezandBrandon Thomas 76c66b8e9c feat(ui): Shift on a corner handle keeps proportions for comments, stamps, and form fields (#2252)
Holding Shift while dragging a corner handle now preserves the annotation's
current proportions, unless the annotation has its own fixed aspect ratio
(image signatures). This matches Acrobat behaviour for comments, stamps,
and form fields.

- Added  helper to compute the effective ratio from fixed
  aspect, current proportions with Shift held, or none.
- Updated  in comments.rs and  in prepare.rs to
  pass the Shift modifier.
- Updated signature_drag.rs to read and pass Shift.
- Added tests for the new Shift behaviour in comments.rs and prepare.rs.
- Updated docs/ui-parity.md and parity/acrobat-features.toml.

Fixes #2199

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:51:55 -04:00
b850cbb5c9 M4: lay out Organize pages as Acrobat's panel (#2248)
* M4: lay out Organize pages as Acrobat's panel

Organize pages gets Acrobat's panel: Pages selected (typed, or by kind
from its menu: all, even, odd, landscape, portrait); page options as a
row of icon buttons (rotate both ways, delete, extract) with Insert (a
blank page, pages from a file, duplicates), Replace, Split and Bates
numbering; page setup (boxes, crop, transitions, templates, printing);
and page labels and properties. Items PdfCraft doesn't have yet are
listed muted and say they are planned.

The panel shows whenever the grid does, however Organize was opened (the
tool, the command, --organize), and its Close ends Organize. The grid's
own toolbar keeps the selection count, moving pages, their size, Save
pages and Close; the page actions moved into the panel with the same
labels, and queue on the view as before. Two new strings are translated
in every catalogue.

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

* M4: Pages selected shows the current page when nothing is selected

The empty field showed the example "1, 12, 16-18" in grey, which read as a
selection. It now shows the page the actions apply to, as Acrobat does.

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

* chore(i18n): refresh the catalogue hashes in ATTRIBUTION.toml

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

* M4: leaving Organize goes back to the workspace's panel

Opening a page from the grid (or the grid's Close, or the panel's Close)
always showed All tools, so Organize opened from the Edit panel dropped
the user out of Edit. It now goes back to the workspace's own panel: Edit
under the Edit tab, Convert's and E-Sign's under theirs, All tools
otherwise. Back still goes to All tools.

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

* M4: box the Organize panel's page edit (clippy large_enum_variant)

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

* M4: parity notes for the Organize pages panel

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

* Refresh catalog hashes after merging main

---------

Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:51:32 -04:00
ae9d6246e7 M7: the Edit tab turns on Edit text & images, as in Acrobat (#2241)
* M7: the Edit tab turns on Edit text & images, as in Acrobat

Entering Edit boxes every paragraph and image on the page at once, so text
is editable with one click instead of first finding Edit text & images in
the tool list. The floating toolbar gets an Edit text & images button while
Edit is open or the tool is in use. Leaving Edit (or turning the tool off
there) applies a paragraph still open in the editor, as clicking away from
it does; left open, its page kept showing the paragraph's text removed.

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

* M7: parity notes for the Edit tab turning on Edit text & images

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

* M7: the panel-reveal test looks for the Edit panel's own row

With Edit text & images on, its name is on the floating toolbar as well as in the panel, so
the label alone matched twice and no longer showed that the Tools tab was revealed.

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

---------

Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:50:45 -04:00
3de8d56784 M3: Save asks before writing over a changed file, one tab per file, locked autosaves (#2166) (#2218)
* M3: Save asks before writing over a changed file, one tab per file, locked autosaves (#2166)

Save compares the file on disk with the version the document came from
(its open, reload or last save) and asks before writing over another
one, with Save as... to keep it; the changed-on-disk notice says what
Reload and Save each do. A file that is open already comes to the front
instead of opening twice. A running instance holds an exclusive lock
file in the recovery folder and names it in its snapshots, so a second
instance neither offers nor discards what the first is autosaving.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* M3: the pending save exists only where files do (#2166)

The web build has no file to compare against, so the struct and field
are desktop-only; the wasm check gained no warning.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:49:40 -04:00
Marco ArnulfoandBrandon Thomas c4e95d3af3 Forms: fields that share a name take the same value (fixes #2214) (#2217)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:49:17 -04:00
3f9cd3b87d M1: every PDF leaves the engine checked, and every writer uses one staging-file writer (#2166) (#2182)
New files from Combine, Extract, Split and Create, signed, timestamped and
LTV-embedded files and the OCR batch output reopen with their pages before
anyone writes them, as saves already did. The desktop app, the automation
tools (with or without --root) and the CLI's edit, combine, extract and
split write through pdfcraft_platform::staging::write_atomically, so a
failed write or --out naming the input never leaves a half-written file.
Special files such as /dev/stdout are still written as given.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:48:09 -04:00
c1146d0d8d feat(ui): improve back and forward page navigation (#2080) (#2151)
Co-authored-by: srt8hellcat2-ui <225485253+srt8hellcat2-ui@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:47:33 -04:00
b369a50b68 fix: Space Audit lines up its columns and counts form fonts as fonts (#2118)
* fix(optimize): count what form fields and comments draw with by kind

The space audit claimed the AcroForm and annotations before any page
resources, so the fonts in /DR and in widget and comment appearance
streams were counted as Forms or Comments. A fillable form with an
embedded field font showed mostly "Forms" and almost no "Fonts".

Resources are now sorted by kind first, wherever they are used: page
resources, the form's default resources and every annotation
appearance (one stream or a dictionary of states). A form XObject's
other entries now count as X Object Forms instead of overhead.

Sorting nested forms' resources recursed once per form, so a long
chain of nested forms overflowed the stack. The recursion is now
bounded at 64 levels; deeper forms are still counted as X Object
Forms through the iterative walk.

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

* fix(ui): line up the Space Audit columns and say what it shows

The percentages were drawn at the dialog's far edge, away from their
header and outside the striped rows: egui gives a grid's last column
the rest of the window, and the right-to-left layout used to
right-align the numbers filled all of it. Every cell is now laid out
first and given exactly its column's width; numeric columns and
their headers are right-aligned, and the description column takes
the rest, so the stripes span the dialog.

A caption under the title says the table is what takes up the file's
space now (not what optimizing would save), translated in every
catalogue. The parity notes no longer call the space audit missing.

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>
2026-10-15 02:45:58 -04:00
716a957186 fix(sign): a signed page's resources are page content whatever keys a later revision writes (#1787) (#2115)
A rewritten resources dictionary, font, XObject or graphics state of a
signed page changes what the page shows. It was judged by the keys its
new version carried, so adding /FT or /Subtype /Widget made it a
permitted form fill. The roles now come from the signed revision's own
object graph, walked from each page's resources with bounds; a category
dictionary that only gained a name is judged as before.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:45:35 -04:00
a7fe7a20c4 M7: Hebrew in Add text (#128) (#2109)
Add text and page_add_text draw Hebrew with the craft-fonts Hebr face
(Noto Sans Hebrew) through the shaped path Arabic and Thai already use:

- pdfcraft-fonts: ShapedScript::Hebrew, shape_hebrew and hebrew_has.
  Grapheme clusters keep points and cantillation marks on their letter.
- pdfcraft-edit: Hebrew paragraphs are laid out with UAX #9 like Arabic;
  spaces and punctuation between Hebrew letters take the Hebrew face.
  first_undrawable refuses Hebrew the face can't draw (or without the
  face) instead of writing "?"; existing items stay movable without it.
- Type 3 glyphs with ToUnicode and per-line ActualText, so extraction,
  search and the editor get typing order back.

Regular style only, as for Arabic and Thai.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:45:12 -04:00
712ae9c36a M3: a document that asks to open in Full Screen mode asks first (/PageMode /FullScreen) (#2086)
* M3: a document that asks to open in Full Screen mode asks first (/PageMode /FullScreen)

The Initial View option "Open in Full Screen mode" was written and read
but never acted on when opening. Now the app asks, as Acrobat's alert
does: Full Screen enters it, Not now leaves the window as it is. A
document cannot take the screen on its own.

Refs #379, #1459.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:43:17 -04:00
c0f76fc73a M4: set page transitions and display times (/Trans, /Dur) through the engine and the tools (#2083)
* M4: set page transitions and display times (/Trans, /Dur) through the engine and the tools

organize::set_transition writes or removes a page's /Trans dictionary
(only the entries the style reads and that differ from the defaults) and
/Dur, with every value checked before any page changes; Edit::SetTransition
is one undo step ("Set page transitions", translated in every catalogue);
page_transition_set is the tool. The counterpart of reading them (#2074);
playing them and the Organize dialog stay planned.

Refs #379.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:42:28 -04:00
mtlcl410andClaude Fable 5.1 8772db0a6e M4: read page transitions and display times (/Trans, /Dur) into DocInfo (#2074)
Each page's presentation settings of ISO 32000-2 s12.4.4 are read with the
spec's defaults for what is missing or out of range: the transition style,
duration, dimension, motion, direction, Fly scale and backdrop, and the
/Dur display time. doc_info lists them; pdfcraft-cli info counts them.
This is the data full screen needs to play transitions and auto-advance;
playing and editing them stay planned.

Refs #379, #2070.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-15 02:42:04 -04:00
Lucas Sampaio DiasandBrandon Thomas aa050612c5 M3: preserve Ctrl+Alt+Q input during digital ID creation (#1980) (#2055)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:41:32 -04:00
bbc99126db i18n(es): translate the new print and Add text strings, Type on the signature pad, tú register (#2049)
* i18n(es): translate the new print and Add text strings, Type on the signature pad, tú register

Seven tl! literals reached the Spanish UI in English: the PDF page size,
Match page at 100%, PDF scale and the two scale hints in the print dialog,
and the Done / Discard buttons of Add text. The signature pad's Type
button read "Tipo" (the noun) because Spanish had no `signature pad`
entry; it now reads "Escribir", beside Dibujar, Imagen and Escanear.

Four rows used the formal usted register the catalog header rules out
(Elija, Cámbielo, the signature-photo hint, the no-signature notice);
they now use tú like the rest.

Spanish gains `spanish_covers_ui_literals`, the check Japanese, Italian,
French, German and others already have, and its command test checks
menu names too, so a new string can't ship in English by accident.
`contextual_labels_fall_back_to_the_plain_translation` used Spanish's
missing `signature pad` entry as its example; it now uses a context no
catalog defines, so it keeps testing the fallback as catalogs fill in.

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>
2026-10-15 02:41:10 -04:00
15d82b5b9c fix(render): a Multiply highlight inside a transparency group keeps the text (#1968) (#2027)
Highlight appearances written by other editors are a transparency group whose
content is filled with `/BM /Multiply`. Every `/Group` form got a layer of its
own, so the Multiply blended with that layer's transparent backdrop and the
layer then covered the page as an opaque block.

Vendored hayro-interpret patch (28): a group that is neither isolated nor
knockout composites its elements with the backdrop (ISO 32000-2 §11.4.7), so
when it is entered at full opacity with no soft mask and Normal blending there
is nothing for a layer to add, and its content is drawn straight onto the page.
`/I` and `/K` are read from the group dictionary. An isolated group keeps its
blank backdrop; a knockout group or one entered at partial opacity, under a
soft mask or with a blend mode keeps its layer.

Test: `raster::tests::a_multiply_highlight_inside_a_transparency_group_keeps_the_text`
(the appearance drawn as an annotation and from page content, with `/I false
/K false` spelled out and with no group at all; an isolated group as a control;
a group entered at half opacity still composited as one). It fails before the
fix with the text pixel yellow instead of black. parity: view.render-transparency
cites it and says what is still approximated.


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

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:39:16 -04:00
8da1d3efce Redaction Tool Properties: the mark's appearance, and Make properties default (#2008)
* Redaction Tool Properties: the mark's appearance, and Make properties default

Acrobat's third group in the Redaction Tool Properties dialog: the mark's
outline colour (/C), a fill colour shown while the pointer is over a mark (a
viewing preference, never written), and the mark's opacity (/CA). Make
properties default: ticked, OK keeps the values for the next run (saved with
the settings as redact_prefs); unticked, they hold for this run only and the
saved defaults stay; Cancel puts back what the dialog opened with. The dialog
is laid out in two columns so it fits a laptop screen. Rows in every
language, their checksums in ATTRIBUTION.toml, parity
protect.redaction-properties shipped.

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

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:38:22 -04:00
6c22c78a72 Edit: crop an existing page image with its handles (#2002)
* Edit: crop an existing page image with its handles

ImageChange::Crop(rect) keeps the part of a page image inside rect (user space)
with a clipping path placed just inside the scope that holds the image (or a
new q … Q around its Do); a clipping path already at the scope's start, the
picture's earlier crop, is replaced, so a crop can grow back. The pixels are
never scaled, and a later move or resize wraps the scope, so the crop moves
with the picture. ImageEdit::Crop (undo label "Crop image"), image_edit
action crop. In the window, right-click ▸ Crop Image puts the selected
picture in crop mode: eight handles trim (the inside pans), the part that goes
is dimmed, Enter or Done applies, Esc or Cancel leaves; Remove Crop shows the
whole picture again. Rows in every language, their checksums in
ATTRIBUTION.toml, parity edit.image-crop shipped.

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

* Image crop: Bulgarian hint without a character the monospace UI font lacks

JetBrains Mono has no 'ѝ', so primary_ui_fonts_cover_latin_and_cyrillic_catalogs
failed; the hint is reworded ("with its handles" -> "with the handles").

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

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:37:56 -04:00
bb2b03f263 View: Single Page wheel turns to the next page at the edge of a zoomed-in page (#1924) (#1986)
* View: in Single Page view the wheel turns the page at the edge of a zoomed-in page (#1924)

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

* ROADMAP: log edge paging (#1924)

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

* View: no jump past the last/first page, a turned page starts at its edge (#1924)

step_page is a no-op at either end (it re-aligned the view via goto), the page-view
ScrollArea is not animated (its zero-length offset target overwrote the turn's jump), and
the rest of a gesture that turned the page no longer pans the new page.

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:37:07 -04:00
75c9a9c27b Comments: font size for text boxes in Comment Properties (#1908) (#1984)
* Comments: font size for text boxes in Comment Properties (#1908)

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>
2026-10-15 02:36:35 -04:00
5fffd4cacd Export to Word: place pictures on positioned pages as DrawingML anchors and bound the pictures (#1936) (#1981)
A VML rectangle with an image did not show the picture in Word. Pictures on
positioned pages are now wp:anchor drawings behind the text, sharing the
picture XML with inline pictures. At most 1000 pictures / 512 MiB are embedded;
empty images are skipped.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:35:50 -04:00
Valentin KleinandClaude Sonnet 5.5 76037a77ac View: show page size when hovering the bottom-left of the page view (#1855) (#1979)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:35:26 -04:00
bc5f6ca8ee M5: undo the last stroke in the signature Draw pad (#1963) (#1978)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:35:15 -04:00
a4b7054919 Issue #1910: Organize opens on the current page (#1975)
Every way into the page grid (toolbar, tool panel, page.organize, --organize,
Create multiple) goes through DocView::open_organize, which drops a selection
left from earlier that does not include the page being read and scrolls that
page's cell into view, computed from the row so virtualized rows need not be
laid out. Grid clicks navigate the document as Pages-panel clicks do, so the
viewer no longer resets the current page from its scroll position when the
grid closes.

Regression: crates/ui-egui/tests/editing.rs::organize_opens_on_the_current_page
(middle page, last page, reopening after a selection elsewhere).

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:34:52 -04:00
Valentin KleinandClaude Sonnet 5.5 b870ad32e9 CLI: run --script --report prints one JSON document for the run (#1891) (#1974)
Per step: number, tool, status (ok/error/skipped) and result or error.
Without the flag the output is unchanged; exit codes are unchanged.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 02:34:30 -04:00
8e803b6290 Print: print as image (Advanced), with grayscale pictures (#1965)
* Print: print as image (Advanced), with grayscale pictures

print::Settings.raster (dpi 72-600, grayscale): Session::print_pdf renders each
distinct page used once the way the viewer shows it (comments left out when the
print leaves them out), composes it over white, grey by Rec. 601 luma when
grayscale is on, makes an image-only document of the pictures and imposes it on
the paper like the pages themselves. The Print dialog's new Advanced section
(folded unless on) offers it at 150, 300 or 600 dpi; doc_print takes as_image
and dpi. Rows in every language; parity print.print-as-image shipped,
print.grayscale shipped (the pictures are grey, not only the printer asked).

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

* ATTRIBUTION.toml: the language files' checksums after the new rows

cargo xtask assets checks each catalog's sha256; the rows this branch adds
changed the files without updating it.

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

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <338149330+regendorf-ux@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:34:09 -04:00
Shawn SkaggsandClaude Opus 5.5 57256f9a4c M6: auto-size text fields shrink and wrap typed text on screen, as on the page (#1964)
Auto-size fields (`0 Tf`, often inherited from the AcroForm /DA) fit their
value to the box in the page appearance, but the in-place editor and the
preview shown before the page re-renders ignored it: they used a fixed guess
from the box height, never shrank to the width and never wrapped multiline
text, so long values ran past the box and were clipped.

The fit now lives in one place, `appearance::auto_text_size_by` (re-exported
as `field_auto_text_size_by`). The page appearance measures with Helvetica;
the screen measures with the interface font it draws in, which is wider and
breaks lines differently, so the text stays inside the box on screen too. The
editor wraps at the same scaled width the size was fitted to. Explicit sizes
are unchanged and still clip, as in Acrobat.

Tests: forms auto_text_size_fits_single_and_multiline_text (incl. NaN/inf/
negative boxes and a NaN measurer); ui-egui
auto_size_fields_shrink_and_wrap_typed_text_to_fit_their_box (fails with the
old on-screen sizing restored).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 02:33:45 -04:00
Zeyu LiandBrandon Thomas f52c92882d M9: signature labels follow the interface language, and one the font can't draw is refused, never written as ? (#1951)
* M9: signature labels follow the interface language, and one the font can't draw is refused, never written as ?

The labels of a visible signature ("Digitally signed by", "Reason: ", ...) were written in WinAnsi unchecked, and every caller passed the English defaults. Now:

- sign: a drawn label is checked as the details are, and one Helvetica can't draw is refused (SignError::Unsupported) naming the label and the character. AppearanceLabels::fields and ::first_undrawable say which labels a caller can use.
- ui-egui: signing stamps the labels in the interface language when Helvetica can draw all of them, otherwise all in English (never a mix) with a note under the preview. The preview uses the same labels, the stamp's line layout and the signing time.
- automation: sign_document takes optional labels and appearance; a label the font can't draw, an unknown key or one without a visible signature is InvalidArgs.
- Catalogs: the five labels and the note in every language; refresh hashes.

Follows up the note on #1112.

* Refresh catalog hashes after merging main

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:32:38 -04:00