Commit Graph
78 Commits
Author SHA1 Message Date
Eduardo SoaresandBrandon Thomas 49c6e2ec54 i18n: complete Brazilian Portuguese (pt-BR) interface catalog (#604)
* i18n: complete Brazilian Portuguese (pt-BR) interface catalog

Catalog by André Cardoso (andrecards) from #288, unioned with the
strings merged after the catalog was cut. Asset hash refreshed.

* i18n: translate the pt-BR strings added since the #288 catalog and gate coverage

* i18n: pt-br review fixes (line ending, redact/sanitize/page-boxes terminology)

* i18n: credit the pt-BR translator and document full coverage

Name André Cardoso (andrecards) as the pt-br catalog's translator in
ATTRIBUTION.toml: the ~1,826-entry gist posted in issue #288, with the
labels added since and the wording review as this change's contribution.
ATTRIBUTION.md is regenerated from it.

docs/localization.md moves Brazilian Portuguese from the menus-only
clause into the full-coverage list, states the 1,997-entry count next
to the provenance (following the Ukrainian paragraph's pattern), and
lists pt-br with the languages whose coverage tests gate every
command, All tools label and tl!() literal (added with the catalog).

people.toml is unchanged: PR #165's translator has no entry there, and
its policy is that only one's own entry may be added.

* i18n: pt-br writes '>' like the other Latin catalogs (Inter has no ▸)

* i18n: pt-BR final review fixes (toast grammar, contextual entries, menu gate)

* pt-BR: add the labels main gained while this catalog was completed (98) and drop stale OCR keys

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 20:56:13 -04:00
94f9ce69c6 i18n: add Italian (it) interface translation (#494)
* i18n: add Italian (it) interface translation

- Complete it catalog (about 2,000 entries); it and it_* locales use it
- Fonts test covers the Italian catalog; ▸ written as > like fr/es/ru, since Inter has no ▸
- Docs, attribution and ROADMAP

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

* it: add the labels main gained since this catalog (83), drop stale OCR keys, and coverage tests

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 20:55:59 -04:00
4fc99e4845 Tab strip: shrink tabs before scrolling, keep Open in sight; ignore about:blank titles (#612)
* M3: tabs shrink to fit before they scroll, and Open stays in sight

With more documents open than fit, the tabs kept their full width: they ran
under the buttons on the right (#416 made them scroll) and the Open button
scrolled away with them. Now the tabs shrink first, as browsers do: narrow
tabs keep their width, wider ones share the rest equally and end their names
in "…" (the tooltip has the whole name). Only once they are at their narrowest
does the strip scroll. Open sits after the tabs, outside the scrolling area,
so it is always in sight next to the controls on the right.

Refs #397.

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

* M3: a placeholder title like about:blank leaves the file name on the tab

A browser's "Save as PDF" of a blank page or pop-up titles the document
"about:blank" and asks viewers to show the title, so its tab and window said
"about:blank". A one-word browser address (about:, blob:, data:) or a bare
"Untitled" now counts as no title, and the file name is shown. A tab that
does show a document title names its file on hover.

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

* M3: arrows step through tabs that don't fit, and tabs keep most of their name

Tabs shrank to 110 points before the strip scrolled, so with many open they
became a few characters each and nothing showed that more were hidden. Tabs
now stop at 180 points (most of a name), and once even that doesn't fit,
arrows appear either side of them: each steps about one tab, and an arrow is
dimmed at its end, so it is clear whether more tabs lie that way. The mouse
wheel still scrolls them, and the active tab is still brought into view.

New labels "Scroll tabs left" / "Scroll tabs right" are translated in every
catalog (machine-written; native review welcome).

Refs #397.

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

---------

Co-authored-by: William Max & Co. <info@williammax.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:56:45 -04:00
96854d8d92 M5: name line endings in the comment properties combo (#582)
* M5: name line endings in the comment properties combo

The menu showed PDF tokens such as ROpenArrow. It now uses translated labels, and the stored /LE name is unchanged.

* Fix main CI: move the rotated-field render test out of pdfcraft-forms (#591)

#516 added pdfcraft-render as a dev-dependency of pdfcraft-forms for one
rendering test. Both crates are layer 3, so `cargo xtask layers` (run in
CI) rejects the sideways edge and main went red. The test now lives in
crates/engine/tests/form_rotation.rs, where both crates are already
dependencies, and forms drops the dev-dependency. The parity evidence
points at the new location.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* ja: 平ら for the Butt line ending

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:45:54 -04:00
22cbf65628 Tell the user when PdfCraft draws without a GPU (#519) (#627)
* Refs #519: tell the user when PdfCraft draws without a GPU

When wgpu's adapter is a software one (WARP on Windows, llvmpipe on
Linux), show a notice at launch that PdfCraft may be slow. egui-wgpu
only logged it, so the #519 reporter had to find it in pdfcraft.log.

The OpenGL fallback isn't covered: glow reports its renderer only
through unsafe calls. The string is translated in the ten catalogs that
carry the portable-mode notice, with their ATTRIBUTION.toml hashes.

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

* zh-hant: the drawing-without-a-GPU notice

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:45:27 -04:00
cc31d11227 i18n: complete the Japanese catalog (105 missing strings) and check it like fr/de/ru/zh-hans (#587)
* i18n: complete the Japanese catalog and check it like the other languages

Japanese lacked 105 strings that other languages already have: the
open-link and open-email confirmations, DPI settings, the script sandbox
note, XFA form notes, signing, table and page-mode settings, and the
Contributors / Models statistics. Each now has a Japanese row, so the
interface no longer falls back to English for them.

`japanese_covers_ui_literals` joins the French, German, Russian and
Simplified Chinese checks: every `tl!("…")` literal in the UI source needs
a Japanese row. ATTRIBUTION.toml carries the catalog's new SHA-256.

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

* ja: the Fill & Sign flatten setting main gained meanwhile

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:45:11 -04:00
AlexisandBrandon Thomas 80e6bc9d98 M14: complete the French (fr) catalog (#579)
Add the 47 labels and messages that had no French translation (redaction
marks, undo/redo labels, file-dialog and script warnings, SHA digests,
contextual Start/Subject/Edit/Type, ...). Written from the plain meaning of
the English text, clean-room, following the existing fr.tsv vocabulary
(vouvoiement, "biffer" for redact).

Update the fr.tsv SHA-256 in ATTRIBUTION.toml and add a credit entry for
the contributor in contributors/people.toml.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:44:56 -04:00
56f85f76df M9: more signature validation algorithms (SHA-3, RIPEMD-160, Ed25519, P-521, brainpool, PSS, rsa_sha1) (#386)
* M9: more signature validation algorithms

Validation now reads what real signers write, and says when it is weak:

- Digests SHA-224, SHA-512/224, SHA-512/256, SHA3-224/256/384/512 and
  RIPEMD-160, beside SHA-1 and SHA-2.
- RSA PKCS#1 v1.5 for every digest, in `rsa_pad`: the one expected encoding
  is built and compared byte for byte, with or without the DigestInfo's NULL
  (RFC 8017 App. B.1). A signature over the bare digest, which doesn't say
  which hash it uses, is refused. The signature value must be below the
  modulus (RFC 8017 §5.2.2); s and s + n both verified before.
- RSA-PSS with the hash, MGF1 hash and salt length the signature declares.
- ECDSA as DER, non-minimal DER or raw r||s on P-256, P-384, P-521 and
  brainpoolP256r1/P384r1/P512r1 (BSI "plain" ECDSA OIDs too); brainpoolP512r1
  is hand-written on crypto-bigint because no RustCrypto crate has it.
- Ed25519 (RFC 8419), and the legacy adbe.x509.rsa_sha1 format.
- An algorithm PdfCraft can't check is Unknown, never Invalid.
- SHA-1 and RIPEMD-160 signatures validate, but the signature's details say
  the algorithm is weak (wording pinned by tests).
- Encoding irregularities that were tolerated are listed in the details
  (`verify_noting`; `verify` keeps its signature).

Found while testing, each with a regression test that failed before the fix:
a brainpoolP512r1 public key with a coordinate >= p panicked inside
crypto-bigint's `sub_mod` (the certificate comes from the PDF); it is refused
now, and the point at infinity is an invalid signature, not an error.

Vectors were made with OpenSSL (tests/data/README.md has the commands).

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

* Update comment to clarify MAC digest restrictions

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* M9: a legacy x509.rsa_sha1 signature whose key the verifier rejects is Invalid; only unsupported keys are "can't check yet"

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:44:38 -04:00
Brandon ThomasandClaude Opus 5.5 d90b3f897f Refs #103: ship the OCR models in the desktop release packages (#630)
Release builds said "Text recognition isn't installed: its models are
missing" because no packaging script or release job fetched the OCR
models: only `cargo xtask models` does, into the source tree.

- packaging/env.sh gains stage_models DEST, which runs
  `cargo xtask models DEST` (every ATTRIBUTION.toml kind = "model" file,
  SHA-256 verified, with its licence text). No model file is named, so
  the models PRs #433/#572 add ship without packaging changes.
- `cargo xtask models` also writes ATTRIBUTION.txt crediting each model
  (CC-BY-SA-4.0 requires attribution when redistributed).
- macOS: PdfCraft.app/Contents/Resources/models, before signing.
- Windows: models\ beside pdfcraft.exe in the MSI (WiX v5 <Files>
  harvest) and the portable zip; test-msi.ps1 checks the folder.
- Linux deb/rpm/tar.gz/AppImage, Flatpak bundle and FreeBSD tarball:
  share/pdfcraft/models beside bin/. Models::search_dirs now also looks
  in <exe dir>/../share/pdfcraft/models (Models::dirs_beside_exe); a
  test builds every package layout on disk and finds the models in each.
  release.yml and freebsd.yml check the packages contain them.
- The missing-models messages no longer tell end users to run cargo,
  and the 11 catalogs that keyed an old PRINTCRAFT_MODELS wording (or
  the previous one) now key the current English text; ATTRIBUTION
  hashes updated. A test checks the translations match.

The web (wasm) build, which the issue reports, still has no models; this
is the desktop half only.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:42:23 -04:00
Márk BartalosandBrandon Thomas b0548e64a7 M14: correct Hungarian Windows Store and saved-signature labels (#595)
* M14: correct Hungarian Windows Store and saved-signature labels

Follow-up to the Magyar catalog: the certificate list keeps the name Windows Store, saved signature and initials labels take the substituted noun, and the squiggly comment type names the underline.

* hu: keep "Windows-tároló" — "Windows store" is the certificate store, not Microsoft's app store

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:39:31 -04:00
445673fe1b M14: clarify Ukrainian wording and restore CI (#584)
* M14: clarify Ukrainian certificate and accessibility wording

Distinguish the certificate holder from the document topic, translate
fixed certificate types, and clarify fingerprints, keyboard access,
table sections and equal field sizes. Validate the complete catalog
with VESUM 6.8.6 and review technical-word exceptions in context.

Fixes #503
Follow-up to #509

Co-authored-by: Codex <noreply@openai.com>

* M14: restore CI after merged test changes

Keep one byte-identical copy of each appearance regression test instead
of five definitions, preserving all three test bodies and assertions.
Correct MCP resource parity evidence to the existing passing test name.

Co-authored-by: Codex <noreply@openai.com>

* ROADMAP: drop the CI follow-up sentence (main fixed the duplicated tests and MCP evidence in #605/#608)

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 18:38:18 -04:00
75b31fb04d M10: Print ▸ Properties… opens the printer driver's own settings (#482)
* M5.7: date format and date language for Fill & Sign in the engine

* M5: flatten Fill & Sign marks when saving

Saving can now bake typed text, marks and signatures into the page, from Preferences or doc_save. The option defaults off, so a normal save still leaves them editable, and other comments and form fields stay.

* M4: zoom the Organize page grid

The page grid draws its pages at 50% to 300%: toolbar buttons with a
percentage that resets to 100%, trackpad pinch or Ctrl/⌘ + wheel about
the page under the pointer, and ⌘+ / ⌘− / ⌘0 (which no longer change the
hidden page view's zoom while the grid shows).

Thumbnails are re-rendered sharper in a few steps as the grid grows,
capped so one document's thumbnails stay within 256 MB. The page under
the pointer (or the first one in view) stays where it is across a zoom.

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

* M4: zoomed grid pages are rendered at the size drawn

A zoomed page grid stretched 132 pt thumbnails, and the stepped detail
that was meant to help was capped by a memory budget, so a long document
stayed blurry at any zoom.

Pages drawn larger than their thumbnails now get a render at exactly the
size drawn while they are in view; the thumbnail stands in until it
arrives. Only pages in view keep one, so memory no longer depends on the
page count, and thumbnails go back to their one fixed size.

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

* M5.7: fill_sign_date_format tool; format and language on fill_sign_add

* M5.7: Preferences ▸ Date format for Fill & Sign dates (refs #158)

* M5.7: refuse Fill & Sign dates the PDF text font can't hold

* M5.7: Spanish labels for Preferences ▸ Date format

* M10: Print ▸ Properties… opens the printer driver's own settings

CUPS: the queue's PPD job options (with lpoptions defaults), grouped in the
Print dialog and sent with this job as lp -o key=value; installable options
and what the dialog sets itself are left out. Windows: the driver's Printing
Preferences window through printui (no unsafe FFI), saved as the printer's
per-user defaults. Headless: printer_options tool and doc_print options.

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

* View: the middle button pans instead of drawing with markup tools

egui's drag responses accept every pointer button, so on Windows and macOS a
middle-button drag reached the page tools: with Draw (ink) selected it drew a
stroke, and other tools cropped, selected or moved. Linux was unaffected
because its middle-click auto-scroll already blocks page input.

Outside Linux a middle-button drag that starts in the viewport (document or
Organize Pages) now pans 1:1 while the button is held, with a Grabbing
cursor, and page input is blocked from the press through the release. Linux
keeps its latched auto-scroll.

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

* M3: a short page gone to from the Pages panel stays current

The current page was whichever page showed most of itself. Going to a short
page (a cheque) above a tall one scrolls it to the top, where the tall page
below shows more, so the current page and the Pages panel's highlight moved
to the tall page instead of the one clicked. The page navigated to now stays
current while it is wholly on screen.

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

* M4: page operations from the Pages panel: context menu and drag to reorder

Right-clicking a thumbnail offers Extract pages, Cut, Copy and Paste after, on
the selection or on the page clicked (which becomes the selection), as the
organize grid does. Dragging thumbnails moves the selection, or the page
grabbed, to the gap it is dropped on: a bar marks the gap, the page count
follows the pointer, and the panel scrolls near its edges. The move is one
undoable step; the moved pages stay selected and the current page follows
its page. Documents that forbid assembly keep click-only thumbnails.

Parity: view.thumbnail-context-ops is shipped, with what it still lacks.

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

* Pages panel: only the primary button drags thumbnails; a drag left over after the panel closed or the document changed is dropped

* Fill & Sign dates: German, Bulgarian, Hungarian and Ukrainian month and weekday names, matching the interface languages

* uk: keep the author's wording for the flatten setting

---------

Co-authored-by: KissT <github@kisst.net>
Co-authored-by: Mark Bartalos <bartalos86@gmail.com>
Co-authored-by: samehelhawary <basio20044@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cody Vogt <Cody.Vogt@caltechsurveys.com>
Co-authored-by: William Max & Co. <info@williammax.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 14:27:02 -04:00
d592dbb9c4 Fill & Sign: date format and date language in Preferences (refs #158) (#505)
* M5.7: date format and date language for Fill & Sign in the engine

* M5: flatten Fill & Sign marks when saving

Saving can now bake typed text, marks and signatures into the page, from Preferences or doc_save. The option defaults off, so a normal save still leaves them editable, and other comments and form fields stay.

* M4: zoom the Organize page grid

The page grid draws its pages at 50% to 300%: toolbar buttons with a
percentage that resets to 100%, trackpad pinch or Ctrl/⌘ + wheel about
the page under the pointer, and ⌘+ / ⌘− / ⌘0 (which no longer change the
hidden page view's zoom while the grid shows).

Thumbnails are re-rendered sharper in a few steps as the grid grows,
capped so one document's thumbnails stay within 256 MB. The page under
the pointer (or the first one in view) stays where it is across a zoom.

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

* M4: zoomed grid pages are rendered at the size drawn

A zoomed page grid stretched 132 pt thumbnails, and the stepped detail
that was meant to help was capped by a memory budget, so a long document
stayed blurry at any zoom.

Pages drawn larger than their thumbnails now get a render at exactly the
size drawn while they are in view; the thumbnail stands in until it
arrives. Only pages in view keep one, so memory no longer depends on the
page count, and thumbnails go back to their one fixed size.

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

* M5.7: fill_sign_date_format tool; format and language on fill_sign_add

* M5.7: Preferences ▸ Date format for Fill & Sign dates (refs #158)

* M5.7: refuse Fill & Sign dates the PDF text font can't hold

* M5.7: Spanish labels for Preferences ▸ Date format

* View: the middle button pans instead of drawing with markup tools

egui's drag responses accept every pointer button, so on Windows and macOS a
middle-button drag reached the page tools: with Draw (ink) selected it drew a
stroke, and other tools cropped, selected or moved. Linux was unaffected
because its middle-click auto-scroll already blocks page input.

Outside Linux a middle-button drag that starts in the viewport (document or
Organize Pages) now pans 1:1 while the button is held, with a Grabbing
cursor, and page input is blocked from the press through the release. Linux
keeps its latched auto-scroll.

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

* M3: a short page gone to from the Pages panel stays current

The current page was whichever page showed most of itself. Going to a short
page (a cheque) above a tall one scrolls it to the top, where the tall page
below shows more, so the current page and the Pages panel's highlight moved
to the tall page instead of the one clicked. The page navigated to now stays
current while it is wholly on screen.

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

* M4: page operations from the Pages panel: context menu and drag to reorder

Right-clicking a thumbnail offers Extract pages, Cut, Copy and Paste after, on
the selection or on the page clicked (which becomes the selection), as the
organize grid does. Dragging thumbnails moves the selection, or the page
grabbed, to the gap it is dropped on: a bar marks the gap, the page count
follows the pointer, and the panel scrolls near its edges. The move is one
undoable step; the moved pages stay selected and the current page follows
its page. Documents that forbid assembly keep click-only thumbnails.

Parity: view.thumbnail-context-ops is shipped, with what it still lacks.

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

* Pages panel: only the primary button drags thumbnails; a drag left over after the panel closed or the document changed is dropped

* Fill & Sign dates: German, Bulgarian, Hungarian and Ukrainian month and weekday names, matching the interface languages

---------

Co-authored-by: Mark Bartalos <bartalos86@gmail.com>
Co-authored-by: samehelhawary <basio20044@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cody Vogt <Cody.Vogt@caltechsurveys.com>
Co-authored-by: William Max & Co. <info@williammax.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 14:26:29 -04:00
8e3782d5ca Flatten Fill & Sign marks when saving (#464)
* M5: flatten Fill & Sign marks when saving

Saving can now bake typed text, marks and signatures into the page, from Preferences or doc_save. The option defaults off, so a normal save still leaves them editable, and other comments and form fields stay.

* M4: zoom the Organize page grid

The page grid draws its pages at 50% to 300%: toolbar buttons with a
percentage that resets to 100%, trackpad pinch or Ctrl/⌘ + wheel about
the page under the pointer, and ⌘+ / ⌘− / ⌘0 (which no longer change the
hidden page view's zoom while the grid shows).

Thumbnails are re-rendered sharper in a few steps as the grid grows,
capped so one document's thumbnails stay within 256 MB. The page under
the pointer (or the first one in view) stays where it is across a zoom.

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

* M4: zoomed grid pages are rendered at the size drawn

A zoomed page grid stretched 132 pt thumbnails, and the stepped detail
that was meant to help was capped by a memory budget, so a long document
stayed blurry at any zoom.

Pages drawn larger than their thumbnails now get a render at exactly the
size drawn while they are in view; the thumbnail stands in until it
arrives. Only pages in view keep one, so memory no longer depends on the
page count, and thumbnails go back to their one fixed size.

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

* View: the middle button pans instead of drawing with markup tools

egui's drag responses accept every pointer button, so on Windows and macOS a
middle-button drag reached the page tools: with Draw (ink) selected it drew a
stroke, and other tools cropped, selected or moved. Linux was unaffected
because its middle-click auto-scroll already blocks page input.

Outside Linux a middle-button drag that starts in the viewport (document or
Organize Pages) now pans 1:1 while the button is held, with a Grabbing
cursor, and page input is blocked from the press through the release. Linux
keeps its latched auto-scroll.

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

* M3: a short page gone to from the Pages panel stays current

The current page was whichever page showed most of itself. Going to a short
page (a cheque) above a tall one scrolls it to the top, where the tall page
below shows more, so the current page and the Pages panel's highlight moved
to the tall page instead of the one clicked. The page navigated to now stays
current while it is wholly on screen.

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

* M4: page operations from the Pages panel: context menu and drag to reorder

Right-clicking a thumbnail offers Extract pages, Cut, Copy and Paste after, on
the selection or on the page clicked (which becomes the selection), as the
organize grid does. Dragging thumbnails moves the selection, or the page
grabbed, to the gap it is dropped on: a bar marks the gap, the page count
follows the pointer, and the panel scrolls near its edges. The move is one
undoable step; the moved pages stay selected and the current page follows
its page. Documents that forbid assembly keep click-only thumbnails.

Parity: view.thumbnail-context-ops is shipped, with what it still lacks.

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

* Pages panel: only the primary button drags thumbnails; a drag left over after the panel closed or the document changed is dropped

* M5: translate Fill & Sign flatten labels into Ukrainian

The coverage test failed because the new preference strings had no Ukrainian entries.

* Fix main CI: move the rotated-field render test out of pdfcraft-forms (#591)

#516 added pdfcraft-render as a dev-dependency of pdfcraft-forms for one
rendering test. Both crates are layer 3, so `cargo xtask layers` (run in
CI) rejects the sideways edge and main went red. The test now lives in
crates/engine/tests/form_rotation.rs, where both crates are already
dependencies, and forms drops the dev-dependency. The parity evidence
points at the new location.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* uk: the author's wording for the flatten setting

---------

Co-authored-by: samehelhawary <basio20044@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cody Vogt <Cody.Vogt@caltechsurveys.com>
Co-authored-by: William Max & Co. <info@williammax.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 14:25:08 -04:00
f28cbbcdd9 M4: zoom the Organize page grid (#468)
* M4: zoom the Organize page grid

The page grid draws its pages at 50% to 300%: toolbar buttons with a
percentage that resets to 100%, trackpad pinch or Ctrl/⌘ + wheel about
the page under the pointer, and ⌘+ / ⌘− / ⌘0 (which no longer change the
hidden page view's zoom while the grid shows).

Thumbnails are re-rendered sharper in a few steps as the grid grows,
capped so one document's thumbnails stay within 256 MB. The page under
the pointer (or the first one in view) stays where it is across a zoom.

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

* M4: zoomed grid pages are rendered at the size drawn

A zoomed page grid stretched 132 pt thumbnails, and the stepped detail
that was meant to help was capped by a memory budget, so a long document
stayed blurry at any zoom.

Pages drawn larger than their thumbnails now get a render at exactly the
size drawn while they are in view; the thumbnail stands in until it
arrives. Only pages in view keep one, so memory no longer depends on the
page count, and thumbnails go back to their one fixed size.

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

* View: the middle button pans instead of drawing with markup tools

egui's drag responses accept every pointer button, so on Windows and macOS a
middle-button drag reached the page tools: with Draw (ink) selected it drew a
stroke, and other tools cropped, selected or moved. Linux was unaffected
because its middle-click auto-scroll already blocks page input.

Outside Linux a middle-button drag that starts in the viewport (document or
Organize Pages) now pans 1:1 while the button is held, with a Grabbing
cursor, and page input is blocked from the press through the release. Linux
keeps its latched auto-scroll.

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

* M3: a short page gone to from the Pages panel stays current

The current page was whichever page showed most of itself. Going to a short
page (a cheque) above a tall one scrolls it to the top, where the tall page
below shows more, so the current page and the Pages panel's highlight moved
to the tall page instead of the one clicked. The page navigated to now stays
current while it is wholly on screen.

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

* M4: page operations from the Pages panel: context menu and drag to reorder

Right-clicking a thumbnail offers Extract pages, Cut, Copy and Paste after, on
the selection or on the page clicked (which becomes the selection), as the
organize grid does. Dragging thumbnails moves the selection, or the page
grabbed, to the gap it is dropped on: a bar marks the gap, the page count
follows the pointer, and the panel scrolls near its edges. The move is one
undoable step; the moved pages stay selected and the current page follows
its page. Documents that forbid assembly keep click-only thumbnails.

Parity: view.thumbnail-context-ops is shipped, with what it still lacks.

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

* Pages panel: only the primary button drags thumbnails; a drag left over after the panel closed or the document changed is dropped

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cody Vogt <Cody.Vogt@caltechsurveys.com>
Co-authored-by: William Max & Co. <info@williammax.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 14:24:45 -04:00
f1af1e2e79 Issue #125: page text the standard fonts can't draw is refused, not written as ? (#510)
Add text, headers/footers and watermarks wrote every character outside
WinAnsiEncoding as `?` and reported success. They now refuse it with a
specific error ("the standard fonts can't draw "中" (U+4E2D) ...") before
changing anything, as the maintainer asked on #327. Form fields and
text-box comments are unchanged (they keep the value).

- fonts: win_ansi_encodable / first_non_win_ansi, defined through
  win_ansi itself so a fuller table widens them too; the edited-text
  Type 3 decision uses the same check (same character set as before).
- edit: one drawable() check, fed the lines exactly as each tool draws
  them, so a CR that would become `?` is refused and CRLF in headers is not.
- ui: the Add-text editor keeps such text open with an inline warning.
  Done, a click elsewhere or a double-click on another item don't take
  or replace it; it counts as unsaved work; Save stops and shows it; a
  deleted page takes its draft with it.
- Arabic (#403): one public check, first_undrawable, follows the drawing:
  Arabic shaped with the craft-fonts face is drawable, and only what's left
  to the standard font counts. Without the face, #403 still decides for the
  Arabic letters (new items refused with what's missing, existing ones stay
  movable), but nothing else in the item may become `?`. The editor uses
  the same check, cached per draft.
- #403's odd_arabic_text_never_panics: its U+2029/U+0085 input, which was
  drawn as `?`, now expects a refusal naming the character.
- The warning is translated (fr, ru, zh-hans, zh-hant, es, ja, bg, de); the
  catalogs' SHA-256 entries in ATTRIBUTION.toml are updated.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 13:20:47 -04:00
Márk BartalosandBrandon Thomas 3af93c7585 M5: draw all ten PDF line-ending styles (#520)
* M5: draw all ten PDF line-ending styles

Imported lines, polylines and callouts kept Square, Circle, Diamond, Butt, Slash and reverse arrows in /LE but would not redraw them. Those styles now draw, and comment_add and Comment properties can set them.

* hu, uk: Line ending

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 13:15:44 -04:00
Márk BartalosandBrandon Thomas c9d0619226 Forms: rotate field widgets with /MK /R (#516)
* M6: rotate form field widgets with /MK /R

Filling or restyling a rotated field redrew its text upright. Appearances now follow /MK /R, and Field Properties and form_set_props can set the rotation.

* Forms: a rotation refused as too small is checked before any other property is written; no indexing by widget number

* hu, uk: Rotation:

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 13:13:12 -04:00
e6df6b1384 i18n: add complete Ukrainian interface translation (#509)
* M14: add complete Ukrainian interface translation

Add the uk registry entry and original catalog, Ukrainian integer plurals,
calendar month inflection, attribution and localization documentation.
Fit Home recommendation cards and the search hint to translated text.
Cover locale detection, persistence, catalogs, bundled glyphs and UI control.

Fixes #503
Related to #348

Co-authored-by: Codex <codex@openai.com>

* uk: add the recent-files labels, labels that reach tl!() through a variable, and 'Inserted text'; fix 'мають мати'

* uk: Home / End are key-cap names

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 12:55:46 -04:00
f626db25cc M14: refine German labels and prevent translated UI text overlap (#515)
* M14: refine German labels and keep translated text within UI bounds

Co-authored-by: Codex <codex@openai.com>

* Tool names inside notices stay lowercase in English; German keeps its capitalised nouns

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 12:39:57 -04:00
Márk BartalosandBrandon Thomas 95120862fc M14: Hungarian interface catalog (hu) (#545)
* M14: Hungarian interface catalog (hu)

Magyar joins the interface languages as a complete catalog, so Preferences and hu locales show commands, dialogs, panels and notices in Hungarian. Wording uses informal "te" and nouns for commands; the strings were written from the meaning of the English labels.

* Small grammar correction

* hu: restore trailing spaces in run-on labels; add the recent-files labels and labels that reach tl!() through a variable

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 12:28:06 -04:00
9ed70e185c de: translate labels that reach tl!() through variables; fix name collisions and wrong meanings (#541)
* de: translate labels that reach tl!() through variables; fix name collisions and wrong meanings

- 32 labels the German catalog was missing because they are looked up
  through a variable (Fill & Sign buttons, link prompts, notices, history
  labels, checker and certificate rows): the UI showed mixed text such as
  "Add Unterschrift" or "A link in diesem Dokument ..."
- Keep the trailing spaces of the digital-ID list parts ("Ausgestellt
  von:Example CA, läuft ab:2031-..." ran together)
- Name collisions: Add a text box / Add a text field, Paste pages / Insert pages
- Wrong meanings: Social Security Numbers (US format only), Clear form
  (resets to defaults), radio-button options called "Schaltfläche", the
  Mark as Confidential description, the first measuring point, Mouse Enter/Exit
- Small grammar fixes; du style and "das PDF" unchanged

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

* de: drop a leftover merge marker

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 12:23:15 -04:00
pauliusiv123-rgb 28804d55d7 Issue #430: clear the recent-files list, or remove one file from it (#557) 2026-10-09 11:53:34 -04:00
7c358099ff M14: German interface catalog (de) (#471)
* M14: German interface catalog (de)

Deutsch (`de`) joins the interface languages as a complete catalog of
2,035 entries: every command, All tools entry, dialog, panel and notice.
It is selected from Preferences, and Auto picks it for `de`, `de_DE`,
`de_AT`, `de_CH` and other German locales. Wording uses the informal
"du" and the infinitive for commands; translations are clean-room, written
from the meaning of the English labels.

- de.tsv registered in LANGUAGES (plural rule one|other)
- tests: registration and locale mapping, plurals, coverage of every
  command, All tools label and tl! literal, history labels keeping user
  values; the Latin UI faces cover umlauts, ß and German quotes; the
  control-channel language switch includes `de`
- ATTRIBUTION.toml/.md entry (kind = "translation"), docs/localization.md,
  ROADMAP and parity notes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* M14: register German as the 12th language; translate the reopen-session preference (de, bg)

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 09:26:15 -04:00
Brandon Thomas 37542ad9db Merge remote-tracking branch 'origin/main' into dmg-background
# Conflicts:
#	ATTRIBUTION.md
2026-10-09 09:21:35 -04:00
DimitarandClaude Opus 5.5 79eadc2aa8 M14: Bulgarian (Български) interface language (#466)
A complete clean-room catalog (2,035 entries: every command, All tools
label and tl! literal), registered as `bg` with one/other plurals so Auto
picks it on Bulgarian systems. Tests mirror the Russian ones (registration,
plurals, persistence, command and literal coverage), and Bulgarian joins
the Latin/Cyrillic font-coverage test.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:16:16 -04:00
pauliusiv123-rgb 3de2be8a68 Issue #442: reopen the files that were open when PdfCraft last closed (#462) 2026-10-09 09:16:10 -04:00
Xus BadiaandClaude Opus 5.5 084f7636ea macOS DMG: branded window background and icon layout
The DMG opened as a plain Finder window. It now shows the app icon's figure on
the PdfCraft colour, with PdfCraft.app and the Applications link side by side.
Same approach as PhotoCraft's and VectorCraft's.

- packaging/macos/dmg/: background.svg (source; text outlined, no paper grain,
  the figure an <image> of assets/app-icon), background.tiff (1x + 2x HiDPI,
  16-colour palette, Deflate, sRGB; ~95 KB) and dmg-layout.DS_Store (Finder
  view settings: window, icon size 128, positions, background).
- generate.py writes the TIFF and the layout on any OS, byte-for-byte
  reproducible, with nothing from the machine that ran it.
- package.sh copies them into the staging folder; the image is still built with
  hdiutil makehybrid. Nothing extra runs in the signing job.
- The volume name drops the version: the layout finds the background through
  an alias that includes the volume name. The DMG file name keeps the version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:38:19 +02:00
0afe0b7094 Keyboard shortcut to enter page number to jump to (#393)
* added view.read_mode command

* added view.focus_page_input

* Go to page…: Ctrl+Shift+N / ⇧⌘N (Acrobat's), no indexing, rustfmt, catalog entries

Shift+Y alone (the first proposal) would have typed a capital Y in text fields
on Windows and Linux.

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-08 21:37:22 -04:00
31f3631868 Home: pin folders and list their newest PDFs (#383)
* Home: pin folders and list their newest PDFs

File > Pin folder to Home... (or "Pin a folder..." on Home) pins up to 8
folders. Each lists its newest PDFs with how long ago and size, opens one
with a click, and can be unpinned. Folders are read on a worker thread and
listed again every few seconds while Home shows, so new scans appear by
themselves; an unreadable folder (moved, offline synced drive) stays pinned
and says so. A folder is described by where it is ("iCloud Drive",
"OneDrive - Contoso", "Documents > Work") instead of its full path.

Cloud drives work through their local synced folder; nothing goes through a
provider API. Settings keep absolute paths only, deduplicated and capped.

Tests: crates/ui-egui/tests/folders.rs (8) and folders_ui unit tests (4).
Command label translated in every catalog; Home strings in fr, es, ja,
zh-hans and zh-hant; catalog hashes updated in ATTRIBUTION.toml.

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

* Add my contributor name (amitabhthakur)

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

* Home pinned folders: Russian strings; regenerate ATTRIBUTION.md

The Russian catalog (#375) landed while this branch was open: its coverage
tests need the new command label and Home strings. ATTRIBUTION.md is
regenerated (cargo xtask assets --write), which also adds the ru.tsv row
that #375 left out.

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-08 19:59:24 -04:00
4995da0653 i18n: add Russian (ru) interface translation (#375)
* i18n: add Russian (ru) interface translation

- Add ru.tsv catalog (~1950 entries, clean-room translations following PhotoCraft terminology: Слой, Образцы, Обработка контуров, Монтажная область, Обводка, Заливка)
- Register ru in LANGUAGES with plural_russian (one/few/many with 11-14 exception)
- Add Russian tests: registration, command/catalogue coverage, UI literals, history/diagnostics, plural forms
- Add ATTRIBUTION.toml entry for ru.tsv
- Update docs/localization.md, ROADMAP.md with Russian language
- All i18n tests pass, cargo fmt and clippy clean

* ru: translate labels added on main since the catalog was written; Russian in the Latin-font coverage test

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

* ru: '>' in the JavaScript-off hint, as the other catalogs (Inter has no ▸)

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-08 19:24:51 -04:00
2a35a1a76f M11: Optimize PDF and Reduce File Size run in the background with a progress bar (#370)
* M11: Optimize PDF and Reduce File Size run in the background with a progress bar

Advanced optimization and Reduce File Size did all their work on the UI
thread, so on a large file the window stopped repainting until they were
done and looked as if it had crashed.

- pdfcraft-optimize: optimize_with_progress reports each image and the
  clean-up; returning false stops with OptimizeError::Cancelled.
- engine: Session::optimize_job captures a copy of the document and the
  choices; OptimizeJob::run reports each OptimizeStage (removing user data,
  images, cleaning up, merging, writing), stops on request with
  EditError::Cancelled and turns a panic into an error so a worker always
  reports back. optimized_bytes is the job run in place.
- ui: both commands run the job on a worker thread (inline in tests and on
  the web), like OCR and Export. A card at the bottom centre shows the
  stage ("Optimizing... image 14 of 40"), a bar that eases towards the
  fraction with a light sweeping across it, the percentage and Cancel;
  notices stack above it. A second run is refused while one is going. The
  control channel's state includes the progress.

The headless doc_optimize and doc_reduce tools stay synchronous. New
strings are translated for es, fr, ja, zh-hans, zh-hant.

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

* Clippy: as_chunks in the signature image background test (follow-up to #355)

#360 switched the image test to as_chunks; #355 added another chunks_exact(4)
in the background test, which clippy's chunks_exact_to_as_chunks rejects.

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

* Optimize: report a failed worker spawn instead of waiting forever

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-08 18:46:34 -04:00
f88a59f762 M10.2: Create PDF from multiple files, edited as pages in the Organize grid (#361)
* M10.2: create PDF from multiple files

Create a PDF ▸ Multiple files takes PDFs, images and .txt files, lets the
user reorder them, and makes one combined PDF (a bookmark and an optional
page range per file) or one PDF per file in a chosen folder, never
overwriting what is there.

- create: source_kind() tells PDFs, images and text apart (moved from the shell)
- engine: Session::convert_to_pdf, shared by Open, the dialog and the tool
- automation: doc_create_multiple (combine or separate)
- ui: Multiple files dialog reusing the Combine files list; translations
- parity entry and ROADMAP updated; catalog hashes refreshed

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

* M10.2: multiple files open in the page grid; insert files at any gap

Create a PDF ▸ Multiple files now opens the picked files as one unsaved
document in the Organize page grid instead of a file-list dialog: pages
are removed and reordered there, and Save pages writes what is shown.

- grid: a "+" at every gap inserts files right there; Save pages button
- insert from file: several files in order, images and text converted,
  inserted pages selected
- the file-list dialog is removed; one PDF per file stays in the
  doc_create_multiple tool
- translations, catalog hashes, parity notes and ROADMAP updated

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

* M4: insert files by dropping them on the page grid

On desktop, PDFs, images and .txt files dropped on the Organize page grid
are inserted at the gap under the pointer, in order, and selected; a
dropped file still opens as a document everywhere else.

winit does not report the pointer while files are dragged over the
window on every platform, so a drop waits (up to a second) for the
pointer to show up and otherwise appends at the end.

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

* M4: files dragged over the page grid follow the pointer (macOS)

winit sends no pointer moves while files are dragged over the window, so
egui kept the place the pointer entered at: the insertion bar froze there
and the files were inserted there. During a file drag the pointer is now
read from AppKit (safe objc2 calls, crates already in the tree) and fed
to egui, so the bar and a "+" badge follow the pointer and the files go
where they are released.

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-08 17:42:13 -04:00
MarekSpodBiedryandBrandon Thomas 2f42c743b9 Issue #157: the Windows portable zip keeps settings, logs and recovery beside pdfcraft.exe (#325)
* Issue #157: the Windows portable zip keeps settings, logs and recovery beside pdfcraft.exe

The portable zip had no portable.txt, and the app had no portable mode: it always wrote %APPDATA%\PdfCraft and %LOCALAPPDATA%\PdfCraft. Now portable.txt (or PdfCraft.portable) beside the executable puts the settings, log files, crash recovery and new digital IDs in PdfCraftData next to it, as PhotoCraft does, and the zip ships portable.txt. An unwritable PdfCraftData falls back to the per-user folders and says so.

* Issue #157: update ATTRIBUTION.toml SHA-256 for the translation catalogs this PR changes

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-08 17:29:59 -04:00
05c8ab3b3e M14: Telugu (తెలుగు) interface language (#349)
* M14: Telugu interface font

- The interface takes `Telu` faces from craft-fonts after the CJK and Arabic ones, on the web too
- Test: Telugu text has glyphs in every family and its conjuncts are shaped

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

* M14: Telugu interface language

- Complete te catalog: 1,984 entries plus whole-phrase Undo/Redo; te and te_IN locales use it
- Undo and Redo labels use a catalog phrase when the language has one (Telugu puts the verb last)
- Tests, docs, attribution, parity and ROADMAP

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

* Add my contributor name (chiranjeevi-max)

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

* M14: catalog contexts for labels with two meanings

- tl_ctx! gives the signature pad's Type, the Action Wizard's Start and the comment menu's Edit their own context; Telugu translates them, other languages keep their current translation
- Preferences' All tools goes through the catalog in every language

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

* M14: Telugu for the look-alike web address warnings

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

* M14: Telugu for the image signature notices

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

* Telugu for Open Recent (#323)

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

---------

Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:00:25 -04:00
dbe4b2dddb Issue #290: File ▸ Open Recent lists the recent files (#323)
* Issue #290: File ▸ Open Recent lists the recent files

The Home view kept a recent-files list but the File menu had no way to
reach it; Acrobat shows File ▸ Open Recent and the request asked for
exactly the menu wiring.

A new command file.open_recent renders as a submenu of the live list
right under Open… (disabled while the list is empty); each entry opens
its file or focuses the tab already showing it, which is now shared
with the Home view's list through the new open_recent() instead of a
second copy of that check. The command palette runs it as "open the
most recent file", or says there are none. Translations added to every
catalog (Japanese, Czech, Spanish, both Chinese and Brazilian
Portuguese are test-covered).

Test: commands.rs::the_open_recent_menu_lists_files_and_opens_one
(submenu lists a seeded recent file and clicking it opens a new active
tab on that path) and commands.rs::the_open_recent_menu_is_disabled
while_the_list_is_empty. Both failed before the change (no such menu).

Co-authored-by: Mawfyy <mawfy12@gmail.com>

* Build: cap cargo jobs and lighten the dev profile

On a 7.1 GiB machine the default one-job-per-core (12) with every
dependency at opt-level 2 plus full debuginfo swap-thrashes the box
frozen mid-build. jobs = 4 bounds peak memory to a few GiB and matches
GitHub runners (4 vCPUs), so CI is unaffected; dependencies drop to
opt-level 1 with debug = false, which halves their build cost and
keeps them fast enough for a usable debug app. Own crates keep their
opt-level and full debuginfo.

Verified: a from-scratch rebuild of all dependencies peaked at ~2 GiB
used with the machine responsive throughout; the perf.rs frame-budget
tests still pass at the lower dependency optimization.

Co-authored-by: Mawfyy <mawfy12@gmail.com>

* Keep the build configuration as on main; French for Open Recent

The jobs and dependency opt-level changes are a separate build-policy decision.
French is coverage-checked since #291; catalog checksums refreshed.

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

* French for Open Recent again, and catalog checksums refreshed

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

---------

Co-authored-by: Mawfyy <mawfy12@gmail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:46:22 -04:00
Devon Llewellyn e17833a64e Add image signatures with placement preview and resize controls (#335)
* M5.7: import images as Fill & Sign signatures and initials

* M5.7: preview image signatures and select after placement
2026-10-08 16:36:26 -04:00
bb8fcd006e Security: refuse mailto attachments, show link hosts in punycode (follow-up to #104) (#337)
* Security: refuse email links that attach or insert a local file

Follow-up to #104. A document's mailto: link could ask the email app to
attach a file from the user's computer (Evolution, KMail and Claws Mail
honour attach=, Evolution and KMail also attachment=) or read one into
the body (Claws Mail's insert=).

document_url now refuses a mailto: address in which any word starting
with attach or insert (any case) is followed by =, wherever it stands
and after each layer of percent-decoding. It also refuses text that
some email tools decode before reading the address: RFC 2047 encoded
words, which KMail decodes across the whole address, and backslash
escapes, which xdg-email's echo decodes. The link is refused rather
than rewritten: PdfCraft doesn't change what a document asks to open,
and the email apps' parsers disagree with one another.

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

* Security: show a link's host as the browser receives it, in punycode

Follow-up to #104. The link prompt's host now comes from
links::display_host, which reads it with url::Url: the webbrowser crate
hands the OS that crate's serialisation, and the web build's browser
parses by the same WHATWG standard. International hosts are shown in
punycode with a note, a label mixing writing systems (UTS #39 resolved
script sets) gets a red warning, numeric hosts such as 3232235777 show
as 192.168.1.1, and a host the browser would reject is refused as
malformed. The Unicode form isn't repeated in the prompt, so a
whole-script lookalike can't read as the real site.

url, idna and icu_properties become direct dependencies of
pdfcraft-engine; all three were already in the graph.

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

* Security: refuse document addresses the URL parser rejects

Follow-up to #104. egui opens links through the webbrowser crate, which
parses the address with url::Url and, when that fails, treats it as a
local file path and opens file:///<cwd>/<address> in the browser.
document_url accepted addresses that don't parse, such as
mailto://a^b/x or https://a^b/ (a forbidden host code point), so a
document could still get the browser to open a local path.

document_url now also requires url::Url to accept the address with the
same scheme, which is exactly the parse webbrowser does first.

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-08 16:07:25 -04:00
9140b44792 M4: Combine files in its own tab, with a sortable table and unlocking of protected files (#321)
* M4: Combine files in its own tab, with a sortable table and unlocking of protected files

Combine files was a modal dialog with a cramped list and up/down buttons on
every row. It is now a tab next to Home and the documents, showing a table
of the files: File name | Pages | Size | Modified | Warnings.

- Columns sort when their heading is clicked (anywhere in the cell; again to
  reverse; names in natural order), resize from their edges and move by
  dragging the heading. Order and widths are kept in the settings; a
  right-click on a heading resets them.
- Rows select like a file manager (click, Ctrl/Cmd+click, Shift+click,
  Shift+arrows, Ctrl/Cmd+A). One toolbar moves, removes and unlocks the
  selection; Alt+arrows and dragging move it too. Every change to the list
  undoes and redoes with Cmd/Ctrl+Z (also toolbar buttons).
- Add files, Add folder, Add folder and subfolders (a split button), Add open
  documents, or drop files and folders on the tab.
- Problems show before Combine: password-protected files, security settings
  that withhold page assembly, page ranges (checked as typed, with the pages
  they take), duplicates. Files that open but are protected say that the
  combined file won't keep their security; the result is never encrypted.
- Unlock... asks for a file's open or permissions password (one password can
  be tried on several files). Passwords stay in memory, are never printed,
  and reach the engine through combine_unlocked; doc_combine takes them as
  `passwords`. The engine decides whether a password works, since the
  inspector rejects some it accepts (RC4 owner passwords).

organize.combine-sort is shipped and organize.combine-options partial
(include open files). Strings are translated for es, fr, ja, zh-hans, zh-hant.

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

* Credit Marlon Konrad (mlkonrad) in the About window

Adds my own entry to contributors/people.toml, as docs/contributors.md asks.

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-08 15:37:10 -04:00
7458f7d47d M14: complete Simplified Chinese About credits and multiline guidance (#311)
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-08 13:39:40 -04:00
edce549a32 M9: sign with the Windows certificate store (#179) (#304)
List Current User Personal CNG identities alongside file digital IDs and sign
without exporting their private keys through safe rustls-cng wrappers.
Expose Windows store references through automation and the desktop dialogs,
with RSA and ECDSA validation, certificate cleanup, and UI regression coverage.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-08 13:02:52 -04:00
a9209c3235 M10: select pages in the Pages panel and print the selection (#296)
* M10: select pages in the Pages panel and print the selection

The Pages panel could only jump to a page. It now picks several:
Ctrl/Cmd-click toggles a page (the first one keeps the current page
selected too), Shift-click takes a range from the last clicked page, a
plain click goes to the page and starts over, Escape clears. Picking does
not turn the page. It is the selection the Organize grid and the page
commands already use; the click rules moved to DocView::click_page and
both places call it.

Print opens on a new "Selected pages" choice when pages are picked and
prints them in page order. They are passed as positions
(print::select_listed), not as a typed range, which would read numbers as
page labels first. ui.state reports active.selected_pages.

Windows still has no printer output, so there the result is a PDF of the
selected pages. No context menu or drag in the panel.

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

* Restore ROADMAP.md, emptied by accident in this PR

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-08 12:00:35 -04:00
22752dc915 Run set-layer-visibility actions from links and push buttons (#292)
* Layers: run set-layer-visibility actions, keeping radio-button groups

The engine can apply a set-layer-visibility action (SetOCGState,
ISO 32000-2 §12.6.4.13) to a document's layers: each ON, OFF or Toggle
in order, naming the layer by its optional content group, so a toggle
sees the state the changes before it left. With /PreserveRB (the
default), a layer turned on turns off the other layers of its
radio-button groups, now read from /OCProperties /D /RBGroups; turning
one off leaves them alone. The renderer is rebuilt once per action, not
once per layer.

Like the Layers panel, this changes the view, not the document.

Tests: engine set_layer_actions_toggle_in_order_and_keep_radio_groups
and set_layer_actions_change_what_renders (pixels before and after),
render radio_button_layer_groups_are_read.

* Links: run set-layer-visibility actions

Clicking a link whose action is SetOCGState showed "SetOCGState actions
run in the JavaScript engine (M6)" and did nothing. Links now read the
action (/State, also when indirect, and /PreserveRB) and clicking runs
it; the Layers panel follows. Hovering shows "Set layer visibility"
(translated in the ja, es, zh-hans and zh-hant catalogs), and doc_info
describes the action in its link list.

Tests: render links_read_set_layer_actions, automation
doc_info_describes_set_layer_links and ui-egui
links_set_layer_visibility.

* Forms: push buttons run set-layer-visibility actions

A push button whose mouse-up action is SetOCGState counted as having no
action, so clicking it did nothing. Buttons now read the action as
links do, and clicking runs it.

Tests: forms push_buttons_read_set_layer_actions and ui-egui
push_buttons_set_layer_visibility.
parity: edit.action-set-ocg-state is partial. The notes say what is
missing: creating the action, bookmarks and other triggers, /Next
chains, JavaScript ocg.state, and the Layers panel keeping radio-button
groups itself.

* French: Set layer visibility

The French catalog is coverage-checked since #291.

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-08 11:27:10 -04:00
cd8c753527 M14: French interface catalog (#291)
Add Français (fr) as a complete UI catalog and select it for every
French locale. Preferences lists it, and Auto follows fr, fr_FR,
fr_CA and fr_BE. About credits labels now go through the catalog.

Coverage tests check commands, All tools labels and every tl! literal.
The bundled Latin faces cover the catalog.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: iMhack <iMhack87@users.noreply.github.com>
2026-10-08 11:16:30 -04:00
4db49900c6 Add Spanish (es) interface catalog (#251)
Registers Español in the language list, with a full catalog covering
commands, dialogs, panels and notices, plus tests and docs.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-08 07:17:26 -04:00
Chiranjeevi U 8407c87ef9 #214: wheel paging, Page display menu, default layout and zoom (#241)
- The wheel turns pages in single-page view: one per notch, one per swipe.
- The rail's Page display menu offers Acrobat's view choices.
- Preferences: default page display and default zoom.
- Fit page and Fit height no longer jump across mixed page sizes.
2026-10-08 06:35:51 -04:00
6974c6550d Measure: add distance, perimeter and area tools (#208)
* M12: add calibrated 2D measurement tools

* M12: harden measurement tools for landing (#208)

Bounded snap targets (endpoints only for accepted segments, page-wide cap),
lenient extraction (deep q nesting and undecodable streams are skipped and
reported), per-page snap cache, measure::list reports unsupported entries
instead of failing, closed polygons with a repeated vertex, printable unit
labels (m²), cached panel listing, WinAnsi captions, indirect /VP kept,
measure.export skipped in the command test like other pickers, missing
translations, honest parity (scale and distance partial) and ROADMAP.

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-08 00:38:57 -04:00
才倾梦华VandBrandon Thomas 77893e8ab3 M14: complete Simplified Chinese translation catalog (#209)
* M14: complete Simplified Chinese translation catalog

* M14: translate new XFA notices and counts

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 23:45:02 -04:00
355de9038e i18n: Simplified Chinese UI language option (#112)
* i18n: Czech interface language

Adds Czech (Čeština, code `cs`) next to English and Japanese:
Preferences ▸ Interface language, `ui.set language cs`, `ui.state`
and persisted preferences all accept it.

- i18n: `Language::Cs`; `tr()` picks the language's table with a match
  (English has none), panic-free. The CZECH table (83 entries) covers
  every label passed to `.tr("…")` (main menu, View zoom/theme/side-panel
  entries, Preferences dialog), every registered File/Edit/Pages/View/Help
  command shown by `registry_menu`, and every key of the Japanese table.
  Written clean-room from the English meaning, not from any Acrobat
  localisation.
- Tests: Czech covers every registered menu title and label, every
  `.tr("…")` literal found by scanning the crate's sources, and the
  Japanese keys; nothing is left identical except an explicit KEEP_AS_IS
  list ("OK"); ellipses match the source; keys are unique per language;
  `cs` round-trips through set_option, ui.set/ui.state and persistence.
- theme: the font setup is split into `font_definitions()`, and a test
  checks with skrifa (dev-dependency, already in the tree via egui) that
  Inter Regular/Medium/SemiBold and JetBrains Mono draw all Czech letters
  and every Czech translation, so nothing falls back to Shippori Mincho.
- The `language` error lists `cs`; docs/localization.md, the parity note
  for misc.prefs-language and the ROADMAP log mention Czech.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* M14: translation catalogs, same system as PhotoCraft

Move the interface translations to PhotoCraft's i18n system: one TSV
catalog per language (crates/ui-egui/src/i18n/ja.tsv), the tl! macro,
entries keyed by command id, context or plural forms, and catalog tests
(format, duplicates, placeholders, command ids).

The 49 existing Japanese strings move over unchanged, plus "Auto". The
language preference now defaults to "auto" and follows the system
language (English when it has no catalog); "en" and "ja" still persist
and unknown values are rejected.

Update docs/localization.md, ROADMAP.md and the misc.prefs-language
parity entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* M14: add Traditional Chinese (Taiwan) UI localization

Co-Authored-By: Codex <noreply@openai.com>

* M14: translate shortcuts and expose language preferences in Edit menu

Add command-comma and verify language switching through the actual selector.

Co-authored-by: Codex <noreply@openai.com>

* M14: expand Traditional Chinese dialogs, panels and diagnostics

Preserve opaque values in translated templates and keep application diagnostics at the UI boundary. Extend native control and template regression coverage.

Co-authored-by: Codex <noreply@openai.com>

* M14: finish Traditional Chinese signature prompts and history labels

Translate generated action labels without changing filenames or field names. Complete checkbox styles, resolution labels and native picker filters.

Co-authored-by: Codex <noreply@openai.com>

* M14: complete Japanese dialogs, panels, shortcuts and diagnostics

Expand Japanese from 49 to 1830 interface entries, expose language preferences in the Edit menu, preserve opaque template and history values, and localize known application diagnostics only at the UI boundary. Add control, persistence, template and desktop/web glyph regressions.

Co-authored-by: Codex <noreply@openai.com>

* i18n: Simplified Chinese UI language option

Adds Language::Zh alongside en/ja (--language zh, preferences switch,
persisted): full Chinese translation table covering every user-visible
label, generic Hans font fallback plumbing, and a build.rs skip-guard
plus test so Noto CJK is never embedded (AGENTS.md 1.1). Pairs with
storytold/craft-fonts#5 (ChillRoundF Regular, OFL-1.1 Adobe-free
chain), which supplies the Hans glyphs. Also fixes a startup deadlock
from translating inside lazy widget_info closures.

* i18n: add Brazilian Portuguese (pt-br) UI translation

Adds Language::PtBr (code pt-br, endonym Português (Brasil)) with serde rename so it persists as "pt-br", registers it in Language::ALL (Preferences ▸ Interface language and ui.set/ui.state) and translates the same 49 menu/command keys as the Japanese table. Extends the i18n tests (table parity, codes, persistence) and updates docs/localization.md.

* M14: strict catalog validation, one-pass fmt, translation assets

Fold in the infrastructure parts of #175:
- `fmt` fills placeholders in one pass, so inserted values (a file name
  containing `{n}`) are never substituted again.
- Catalog validation rejects unknown escapes, unknown `@` contexts,
  placeholder, ellipsis and plural-form mismatches and duplicates. The
  bundled catalogs must have no errors (tests); at run time a bad line is
  still only skipped and logged, so the rest of the catalog loads.
- `.tsv` files count as assets in `cargo xtask assets`, with a
  `translation` kind that may never come from Adobe; ja.tsv gets its
  ATTRIBUTION.toml entry.
- Control-channel test: switching the language keeps documents, their
  dirty state and command ids.

Also make the drawing language thread-local, as in PhotoCraft, so
parallel kittest harnesses can't change each other's language.

Co-Authored-By: 才倾梦华V <185041488+CQMHV@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M14: Brazilian Portuguese as a TSV catalog

Move #165's Portuguese table into crates/ui-egui/src/i18n/pt-br.tsv
(same 49 strings, plus Auto) and register it in LANGUAGES with
PhotoCraft's Portuguese plural rule (0 and 1 singular). Any Portuguese
locale (pt_BR, pt_PT, pt) follows the Brazilian catalog, as in
PhotoCraft. The catalog is an attributed asset (ATTRIBUTION.toml), and
the docs and parity notes list the new code.

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

* M14: Czech as a TSV catalog

Move #68's Czech table into crates/ui-egui/src/i18n/cs.tsv (PrintCraft
labels renamed PdfCraft, plus Auto) and register it with PhotoCraft's
Czech plural rule (one / few / other). #68's tests become catalog
tests: Czech covers every registered menu title and menu command label,
keeps ellipses, uses no three-dot ellipsis or stray whitespace, and only
leaves "OK" untranslated; the code persists and `cz` is rejected. The
source-scanning `.tr("…")` test is dropped: strings now go through tl!
and the catalog tests check consistency.

The font-coverage test moves to tests/fonts.rs and covers both Latin
catalogs (cs, pt-br) against Inter and JetBrains Mono. It keeps skrifa
as a dev-dependency (the version pdfcraft-fonts already uses): egui's
has_glyphs can't answer it, since a family with only the primary face
also uses that face as its replacement face.

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

* M14: route every UI string through the catalog; Japanese as ja.tsv

Port #141's wiring to the catalog API from land/106:
- `crate::i18n::tr(ui, s)` becomes `tl!(s)`, `tr_fmt`/`tr_template`
  become `i18n::fmt(tl!(..), ..)`, and `Language::get`/`store` (the
  egui temp-data copy of the language) are gone: the drawing language
  is the per-thread `i18n::current()`, set from the preference at the
  start of each frame. Worker threads (export, OCR, batch actions) take
  the UI's language with them for their summaries.
- ja.rs (1,830 pairs) becomes ja.tsv, merged with the existing entries
  and renamed PrintCraft -> PdfCraft. The 29 diagnostic templates of
  errors.rs are dropped.
- `Language::diagnostic` and errors.rs are dropped: matching engine and
  OS messages against English templates breaks silently whenever the
  wording changes. Error details are shown as they are, inside the
  translated frame ("{label} failed: {e}").
- History labels (`command_label`/`action_label`) move into i18n and keep
  captured file and field names; registry menus prefer `@id` entries.
- Preferences: Auto plus every registered language, relabelled at once.
- Tests: Japanese covers every command and All tools entry; Japanese
  labels have glyphs with the desktop faces and with the web subset
  (with craft-fonts); history labels; #141's control tests, with the
  error-detail expectation updated.

Co-Authored-By: shuiyue-cmyk <291597411+shuiyue-cmyk@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M14: Preferences in the Edit menu, Cmd/Ctrl-comma

Split out of #141: register app.preferences in the Edit menu with the
platform's usual shortcut (Command-comma on macOS, Ctrl-comma
elsewhere), so the interface language can be found and changed with no
document open. Shortcut keys may now be punctuation (egui's key name
","). #141's control test drives the menu item, the language selector
and the shortcut in Japanese and English.

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

* M14: Traditional Chinese as zh-hant.tsv

Reduce #140 to its translations: zh_tw.rs becomes
crates/ui-egui/src/i18n/zh-hant.tsv (PrintCraft renamed PdfCraft; the
29 errors.rs diagnostic templates dropped, as for Japanese) and one
registry row, code `zh-hant` (Taiwan vocabulary). Locales zh_TW, zh_HK,
zh_MO and zh-Hant-* resolve to it. #140's own copy of the UI wiring and
its errors.rs diagnostics are not taken: land/141 carries the wiring.
Tests: registration, locale mapping, every command label translated.
The catalog is an attributed asset; docs, ROADMAP and parity list it.

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

* M14: Simplified Chinese as zh-hans.tsv

Reduce #112's translations to crates/ui-egui/src/i18n/zh-hans.tsv
(PrintCraft renamed PdfCraft) and one registry row, code `zh-hans`
(#112 used `zh`; `zh`, `zh_CN`, `zh_SG` and `zh-Hans` locales still
resolve to it through the system-language match). #112's own copy of
the UI wiring is superseded by land/141. Tests: registration, locale
mapping, menu coverage. The catalog is an attributed asset; docs,
ROADMAP and parity list it.

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

* M14: CJK font order follows the UI language

From #112: `ui_cjk_fonts(prefer_hans)` orders the craft-fonts CJK faces
for the interface, and `theme::font_definitions_for` installs them. In
Simplified Chinese the `Hans` faces come first; otherwise the Japanese
ones (the previous behaviour). egui draws each character with the first
face that has it, so with Japanese first a Simplified-only character
(欢) would land in another face and sit on a different baseline. The
fonts are reinstalled when the language crosses that line (same family
names, so named fonts stay valid while the new set loads).

Changes from #112: within each group BIZ UDPGothic still leads, so a
serif listed first in the manifest (Shippori Mincho) never becomes the
Chinese-mode UI fallback; the unused `document_chinese_font` is left
out; and the crates/fonts/build.rs special case that skipped
"Noto Sans CJK SC" (and its test assertion and README line) is not
taken. Without CRAFT_FONTS_DIR, or without a `Hans` face, the order
logic yields only what exists: Chinese characters then show replacement
boxes and nothing panics (tests/fonts.rs, the fonts crate test, and the
control test switching ja -> zh-hans -> en).

Also document `--language` on the app's command line.

Co-Authored-By: shuiyue-cmyk <291597411+shuiyue-cmyk@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Petr Leo Compel <petrleocompel@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Simon Leblanc <contact@leblanc-simon.eu>
Co-authored-by: sakamoto824 <sakamoto824@gmail.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: shuiyue-cmyk <291597411+shuiyue-cmyk@users.noreply.github.com>
Co-authored-by: RicSchonfelder <ricschonfelder@icloud.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: 才倾梦华V <185041488+CQMHV@users.noreply.github.com>
2026-10-07 22:17:00 -04:00
ab4c87926a M14: expand Traditional Chinese UI, dialogs and language preferences (#140)
* i18n: Czech interface language

Adds Czech (Čeština, code `cs`) next to English and Japanese:
Preferences ▸ Interface language, `ui.set language cs`, `ui.state`
and persisted preferences all accept it.

- i18n: `Language::Cs`; `tr()` picks the language's table with a match
  (English has none), panic-free. The CZECH table (83 entries) covers
  every label passed to `.tr("…")` (main menu, View zoom/theme/side-panel
  entries, Preferences dialog), every registered File/Edit/Pages/View/Help
  command shown by `registry_menu`, and every key of the Japanese table.
  Written clean-room from the English meaning, not from any Acrobat
  localisation.
- Tests: Czech covers every registered menu title and label, every
  `.tr("…")` literal found by scanning the crate's sources, and the
  Japanese keys; nothing is left identical except an explicit KEEP_AS_IS
  list ("OK"); ellipses match the source; keys are unique per language;
  `cs` round-trips through set_option, ui.set/ui.state and persistence.
- theme: the font setup is split into `font_definitions()`, and a test
  checks with skrifa (dev-dependency, already in the tree via egui) that
  Inter Regular/Medium/SemiBold and JetBrains Mono draw all Czech letters
  and every Czech translation, so nothing falls back to Shippori Mincho.
- The `language` error lists `cs`; docs/localization.md, the parity note
  for misc.prefs-language and the ROADMAP log mention Czech.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* M14: translation catalogs, same system as PhotoCraft

Move the interface translations to PhotoCraft's i18n system: one TSV
catalog per language (crates/ui-egui/src/i18n/ja.tsv), the tl! macro,
entries keyed by command id, context or plural forms, and catalog tests
(format, duplicates, placeholders, command ids).

The 49 existing Japanese strings move over unchanged, plus "Auto". The
language preference now defaults to "auto" and follows the system
language (English when it has no catalog); "en" and "ja" still persist
and unknown values are rejected.

Update docs/localization.md, ROADMAP.md and the misc.prefs-language
parity entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* M14: add Traditional Chinese (Taiwan) UI localization

Co-Authored-By: Codex <noreply@openai.com>

* M14: translate shortcuts and expose language preferences in Edit menu

Add command-comma and verify language switching through the actual selector.

Co-authored-by: Codex <noreply@openai.com>

* M14: expand Traditional Chinese dialogs, panels and diagnostics

Preserve opaque values in translated templates and keep application diagnostics at the UI boundary. Extend native control and template regression coverage.

Co-authored-by: Codex <noreply@openai.com>

* M14: finish Traditional Chinese signature prompts and history labels

Translate generated action labels without changing filenames or field names. Complete checkbox styles, resolution labels and native picker filters.

Co-authored-by: Codex <noreply@openai.com>

* M14: complete Japanese dialogs, panels, shortcuts and diagnostics

Expand Japanese from 49 to 1830 interface entries, expose language preferences in the Edit menu, preserve opaque template and history values, and localize known application diagnostics only at the UI boundary. Add control, persistence, template and desktop/web glyph regressions.

Co-authored-by: Codex <noreply@openai.com>

* i18n: add Brazilian Portuguese (pt-br) UI translation

Adds Language::PtBr (code pt-br, endonym Português (Brasil)) with serde rename so it persists as "pt-br", registers it in Language::ALL (Preferences ▸ Interface language and ui.set/ui.state) and translates the same 49 menu/command keys as the Japanese table. Extends the i18n tests (table parity, codes, persistence) and updates docs/localization.md.

* M14: strict catalog validation, one-pass fmt, translation assets

Fold in the infrastructure parts of #175:
- `fmt` fills placeholders in one pass, so inserted values (a file name
  containing `{n}`) are never substituted again.
- Catalog validation rejects unknown escapes, unknown `@` contexts,
  placeholder, ellipsis and plural-form mismatches and duplicates. The
  bundled catalogs must have no errors (tests); at run time a bad line is
  still only skipped and logged, so the rest of the catalog loads.
- `.tsv` files count as assets in `cargo xtask assets`, with a
  `translation` kind that may never come from Adobe; ja.tsv gets its
  ATTRIBUTION.toml entry.
- Control-channel test: switching the language keeps documents, their
  dirty state and command ids.

Also make the drawing language thread-local, as in PhotoCraft, so
parallel kittest harnesses can't change each other's language.

Co-Authored-By: 才倾梦华V <185041488+CQMHV@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M14: Brazilian Portuguese as a TSV catalog

Move #165's Portuguese table into crates/ui-egui/src/i18n/pt-br.tsv
(same 49 strings, plus Auto) and register it in LANGUAGES with
PhotoCraft's Portuguese plural rule (0 and 1 singular). Any Portuguese
locale (pt_BR, pt_PT, pt) follows the Brazilian catalog, as in
PhotoCraft. The catalog is an attributed asset (ATTRIBUTION.toml), and
the docs and parity notes list the new code.

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

* M14: Czech as a TSV catalog

Move #68's Czech table into crates/ui-egui/src/i18n/cs.tsv (PrintCraft
labels renamed PdfCraft, plus Auto) and register it with PhotoCraft's
Czech plural rule (one / few / other). #68's tests become catalog
tests: Czech covers every registered menu title and menu command label,
keeps ellipses, uses no three-dot ellipsis or stray whitespace, and only
leaves "OK" untranslated; the code persists and `cz` is rejected. The
source-scanning `.tr("…")` test is dropped: strings now go through tl!
and the catalog tests check consistency.

The font-coverage test moves to tests/fonts.rs and covers both Latin
catalogs (cs, pt-br) against Inter and JetBrains Mono. It keeps skrifa
as a dev-dependency (the version pdfcraft-fonts already uses): egui's
has_glyphs can't answer it, since a family with only the primary face
also uses that face as its replacement face.

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

* M14: route every UI string through the catalog; Japanese as ja.tsv

Port #141's wiring to the catalog API from land/106:
- `crate::i18n::tr(ui, s)` becomes `tl!(s)`, `tr_fmt`/`tr_template`
  become `i18n::fmt(tl!(..), ..)`, and `Language::get`/`store` (the
  egui temp-data copy of the language) are gone: the drawing language
  is the per-thread `i18n::current()`, set from the preference at the
  start of each frame. Worker threads (export, OCR, batch actions) take
  the UI's language with them for their summaries.
- ja.rs (1,830 pairs) becomes ja.tsv, merged with the existing entries
  and renamed PrintCraft -> PdfCraft. The 29 diagnostic templates of
  errors.rs are dropped.
- `Language::diagnostic` and errors.rs are dropped: matching engine and
  OS messages against English templates breaks silently whenever the
  wording changes. Error details are shown as they are, inside the
  translated frame ("{label} failed: {e}").
- History labels (`command_label`/`action_label`) move into i18n and keep
  captured file and field names; registry menus prefer `@id` entries.
- Preferences: Auto plus every registered language, relabelled at once.
- Tests: Japanese covers every command and All tools entry; Japanese
  labels have glyphs with the desktop faces and with the web subset
  (with craft-fonts); history labels; #141's control tests, with the
  error-detail expectation updated.

Co-Authored-By: shuiyue-cmyk <291597411+shuiyue-cmyk@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M14: Preferences in the Edit menu, Cmd/Ctrl-comma

Split out of #141: register app.preferences in the Edit menu with the
platform's usual shortcut (Command-comma on macOS, Ctrl-comma
elsewhere), so the interface language can be found and changed with no
document open. Shortcut keys may now be punctuation (egui's key name
","). #141's control test drives the menu item, the language selector
and the shortcut in Japanese and English.

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

* M14: Traditional Chinese as zh-hant.tsv

Reduce #140 to its translations: zh_tw.rs becomes
crates/ui-egui/src/i18n/zh-hant.tsv (PrintCraft renamed PdfCraft; the
29 errors.rs diagnostic templates dropped, as for Japanese) and one
registry row, code `zh-hant` (Taiwan vocabulary). Locales zh_TW, zh_HK,
zh_MO and zh-Hant-* resolve to it. #140's own copy of the UI wiring and
its errors.rs diagnostics are not taken: land/141 carries the wiring.
Tests: registration, locale mapping, every command label translated.
The catalog is an attributed asset; docs, ROADMAP and parity list it.

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

---------

Co-authored-by: Petr Leo Compel <petrleocompel@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Simon Leblanc <contact@leblanc-simon.eu>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: RicSchonfelder <ricschonfelder@icloud.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: 才倾梦华V <185041488+CQMHV@users.noreply.github.com>
Co-authored-by: shuiyue-cmyk <291597411+shuiyue-cmyk@users.noreply.github.com>
2026-10-07 22:16:34 -04:00