* Signatures: RFC 3161 timestamp tokens (PAdES B-T) behind a transport-free TSA interface
* Signatures: document timestamps, DSS/VRI evidence embedding, trusted verification time
* Signatures: revocation evidence verification (CRL and OCSP) wired into validation
* Signatures: real-world certificate compatibility (CRLDP, tolerant extensions) and XFA-aware DocMDP classification
* Signatures: RFC 6960 encodings, delegated-responder verification, TSA trust anchoring, try_oid overflow
* Signatures: harden PR #69 validation (XFA, metadata, revocation, TSA trust, token digest)
- Remove the XFA "form fill" reclassification: it read the new revision, so
an update could add /XFA + /NeedsRendering and replace the page tree while
approval and P=2/3 certification signatures stayed Valid. Classification
(including catalog /Extensions) is back to main's.
- A changed stream tagged /Type /Metadata counts as metadata only when it is
the signed catalog's /Metadata stream or was already metadata when signed;
a relabelled page Form XObject is "other changes".
- A verified revocation stays Invalid when the certificate was also outside
its validity at signing time (was softened to Unknown).
- An embedded timestamp whose TSA does not chain to the trust store is
reported as an unverified timestamp and no longer sets the validity or
revocation time; the signer's claimed time is used, as on main.
- parse_token checks the TSTInfo messageDigest with the SignerInfo digest
algorithm, not the imprint's.
Each fix has a regression test in crates/sign/tests/pdf.rs that fails on
the PR head.
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>
* 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: 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>
---------
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>
* Forms: push buttons run their show/hide-a-field action
A push button whose Mouse Up action is a Hide action (ISO 32000-2
§12.6.4.11) did nothing when clicked: the action wasn't read, so the
button counted as having no action at all.
Buttons now read it: /T as a field name, a reference to a field or one
of its widgets (named by its fully qualified name, following /Parent
with a cycle guard), or an array of these; /H false shows instead of
hiding. Clicking hides or shows the named fields and their kids through
the same edit that a script setting field.display uses, so it is one
undo step.
A hidden widget (Hidden or NoView flag) no longer takes clicks, tab
stops, hover frames or field highlights. That also covers fields that a
script hides.
Tests: forms push_buttons_read_hide_actions (names, references, arrays,
/H, a /Parent cycle) and ui-egui hide_actions_hide_and_show_fields.
parity: edit.action-show-hide-field is shipped.
* Forms: push buttons with an action show the pointing hand
Hovering a push button showed the "not allowed" cursor, although
clicking it runs its action. Buttons that aren't read-only and have an
action now show the pointing hand; read-only buttons and buttons with
no action keep the "not allowed" cursor.
Test: hide_actions_hide_and_show_fields checks the cursor (it fails
without this change).
* Clippy: needless borrow in the hide-action test
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>
* #163: allow replacing saved signatures and render long names fully
Add saved signature and initials previews with change/remove/add controls in
Fill & Sign. Preserve placed annotations and saved values on cancellation.
Use bounded line outlines for long names and fit typed placements to 150 pt.
Co-authored-by: Codex <noreply@openai.com>
* cargo fmt after the rename
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A document could open any address with no confirmation: a link click, a
push button's URI action, a one-line button script mapped to a URI action,
or app.launchURL from JavaScript (on by default). That allowed sending
form data to an outside site, or handing file:, smb: and app-handler
addresses to the operating system.
All four now go through PdfCraftApp::request_document_url:
- engine::links::document_url accepts only http, https and mailto, up to
2048 characters, and refuses control, zero-width, bidi-override and
non-space whitespace characters, malformed schemes and web addresses
without a host.
- Accepted addresses wait in an "Open this web page?" dialog that shows
the real host (engine::links::host drops user:password@, and treats \
like / as browsers do, so https://a.example\@b.example shows a.example)
and the full address. Cancel is the highlighted button; Escape and
clicking outside cancel. While one request waits, further ones are
dropped, so a script can't stack dialogs.
- Refused addresses are reported in a notice and never opened.
PdfCraft's own links (Help, About, updates) still use open_url.
Preferences > JavaScript now says scripts need permission to open a web
page. Tests: engine unit tests for the policy and host parsing, and
crates/ui-egui/tests/document_links.rs driving the link, button, button
script, app.launchURL and Escape paths (all fail against the old code).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Field Properties > Options > Check Box Style (Button Style for radio
buttons): Check, Circle, Cross, Diamond, Square or Star, as Acrobat
offers. The choice is stored in each widget's /MK /CA with Acrobat's
ZapfDingbats codes (4 l 8 u n H), so other viewers draw the same mark,
and PrintCraft draws it as paths, needing no symbol font. Styles set
by other apps are read back too; the defaults stay a check for check
boxes and a circle for radio buttons.
- forms: CheckStyle, check_style(), FieldProps::check_style; set_props
writes /MK /CA (resolving an indirect /MK so its colours survive) and
redraws; text and choice fields refuse a style.
- engine: Document::field_check_style.
- automation: form_set_props takes check_style, form_fields reports it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- #93: field detection already ran when Prepare a form starts on a
document without fields, and as the form.detect command, but nothing in
the tool panel offered it. Prepare a form > Fields now lists Detect
form fields; on a form that already has fields it only adds new ones.
- #95: with several fields selected (Shift/Cmd-click), Delete and the
context menu's Delete removed only the right-clicked one. They now
remove every selected field as one undoable step ("Delete 3 fields").
One prepare::delete_selected serves both.
Editing the shared properties of several fields at once is still to do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine and agent tool already made squiggly underlines; the UI had no
way to. CommentTool::Squiggly joins Underline and Strikethrough in the
Highlight flyout and the Comment tool's Markup section, as the
comment.squiggly command, and existing squiggly comments edit as themselves.
Test: comments.rs::the_squiggly_tool_marks_selected_text. Parity
comment.squiggly: shipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PrintCraft asks GitHub for the latest release and, when it is newer, offers
its release page to download from. Nothing is downloaded or installed
automatically (installing in place needs signed release artifacts).
- ui-egui: updates module (version comparison, background check, dialog);
no network code: the desktop app supplies the source, and without one
(web build, tests) the command opens the releases page.
- desktop: one HTTPS request (ureq over rustls with the OS's trusted roots,
10 s timeout, 1 MiB cap). Only a page under the releases list is offered.
No bundled root list: webpki-roots/webpki-root-certs are CDLA-licensed.
- The check at start is opt-in and silent unless there is a newer version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`printcraft_engine::guard` wraps `catch_unwind` and returns an error message.
- `Session::apply`: edits run on a copy of the document, so a panic leaves it unchanged and is
reported as an error. A panic while refreshing rolls the edit back.
- `Session::open`, `save_bytes`, `save_full_bytes` and `export_data` report a panic as an
open, write or export error.
- `PrintCraftApp::execute`: a command that panics shows a notice, and the app and its documents
keep running.
- The desktop app installs a panic hook that logs every panic to stderr, with a backtrace when
`RUST_BACKTRACE` is set.
Autosave and crash recovery (`RecoveryStore`) were already in place for anything that escapes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stable clippy 1.99 denies chunks_exact/chunks_exact_mut with a constant
size (32 sites), which failed the test job on main and every open PR.
Behaviour unchanged; MSRV 1.90 has as_chunks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>