Every remaining rfd::FileDialog (26 call sites) ran -[NSSavePanel runModal]
inside winit's event handler, which aborts the app when the window gets an
event while the panel is open. They all go through pickers::ask now: an
rfd::AsyncFileDialog, with the follow-up run as a closure on a later frame.
- One picker at a time, held until its closure starts, so a closure that
asks again (merge data, run action) gets its picker; refusals are told.
- Closures run inside pdfcraft_engine::guard, as commands do.
- Targets drop a pick if the document is no longer active or was edited.
- Save-on-close closes only once written; CloseRequest::Tab holds a DocId.
- Extract+delete deletes only after writing, and only if unedited.
- Signing happens once the path is chosen, on an unchanged document.
- Browse… results land only in the same showing of the dialog.
- Sign, print-to-PDF and attachment saves write atomically.
Co-authored-by: Hal Goff <hal@hg.studio>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The internal path dependencies carry an explicit version requirement, so they move to 0.3.0 too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the continuous (default) and two-page views, → / ← and Page Down /
Page Up only did something with ⌘ held, and ↓ / ↑ never did: egui's
ScrollArea has no keyboard scrolling of its own, so without a modifier
the keyboard could not move through the document at all.
As in Acrobat: → / ← now go to the next / previous page (a spread in
two-page view) in every layout; ↓ / ↑ scroll a line (40 pt, a desktop
mouse-wheel line) and Page Down / Page Up scroll a screen (less one
line) where the pages scroll. ⌘ + those keys and single-page view keep
their page steps. The scroll is queued on the DocView and applied by the
page view's ScrollArea in the same frame, next to the middle-button
auto-scroll.
The shortcuts list shows ← / → beside ⌘← / ⌘→; the view.page-navigation
parity entry cites the new test.
Test: viewer.rs::arrow_and_page_keys_move_through_a_scrolling_document
(continuous at Fit Width: → / ← turn pages; ↓ and Page Down take
several presses to reach page 2, ↓ more than Page Down, and ↑ / Page Up
come back; → in two-page view and Page Down in single-page view). It
failed at the first → before the fix.
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The page view's ScrollArea had no id of its own, so egui derived it from
the parent Ui, which is the same for every document. All tabs therefore
shared one scroll offset: switching to another document showed it at
wherever the previous one had been scrolled to (clamped to its length),
and switching back showed the first document at the other one's offset,
usually the top.
Salt the ScrollArea with the document's id so egui keeps one offset per
tab.
Test: viewer.rs::each_document_keeps_its_own_scroll_position scrolls
a.pdf to page 4, switches to b.pdf and scrolls it to page 2, then
switches back and forth: before the fix b.pdf opened on page 2 instead
of its own top. Cited as evidence on view.document-tabs.
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`split --out-dir DIR` wrote each part with `std::fs::write` and never
created DIR, so a folder that did not exist yet failed on the first part
with the part's path and a raw OS error ("The system cannot find the path
specified. (os error 3)") — easy to read as the split itself failing.
The folder is now created with `create_dir_all` once the parts have been
computed, before any is written; a folder that can't be created is
reported as `split: --out-dir DIR: <error>`, naming the folder rather
than the first part.
Tests (apps/pdfcraft-cli/tests/split.rs): a nested folder that does not
exist is created and holds every part listed on stdout; an `--out-dir`
that is an existing file fails with a message naming the option and
leaves the file as it was.
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
`render --out p1.png` wrote a netpbm PAM stream (P7) whatever the name,
so a `.png` could not be opened by any PNG reader. The CLI already links
the engine, whose export module encodes PNG, JPEG and TIFF, so the output
format now follows the extension: `.png`, `.jpg`/`.jpeg` (quality 90),
`.tif`/`.tiff`, and `.pam` for the raw stream as before. Any other name
is refused up front, before the page is rendered, with the accepted list.
`export::encode_image` wraps the three encoders behind `ImageFormat` so
the CLI and `Exporter::image` share one match.
Tests (apps/pdfcraft-cli/tests/render.rs): a `.png` gets the PNG
signature and an IHDR with the rendered size; `.JPG` and `.tif` get
JPEG/TIFF magic; `.pam` still gets the P7 header and the right number of
bytes; `.bmp` fails with the message and writes nothing.
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
`comment_add` converted a note's or attachment's `at` (the icon's top-left
as displayed) as a single point through `PageInfo::view_to_user`, and the
engine's `rect_for` extends the icon square from `at` toward +x/-y in user
space. On an unrotated page the displayed and user-space top-left are the
same corner; under `/Rotate` they are different corners of the square, so
the icon landed one icon width (20 pt) away from the requested point.
`icon_anchor` converts the whole displayed square to user space and takes
its user-space top-left, so the `/Rect` covers the requested square under
any rotation. `PageInfo::view_rect_to_user` is the render-side counterpart
of `view_rect_to_quad`, and `NOTE_SIZE` is re-exported from the engine.
Test: `note_icons_anchor_at_the_requested_corner_on_rotated_pages` adds a
note and an attachment on pages rotated 0/90/180/270 and asserts the
`comment_list` rectangles; before the fix the 90° page reports
[52, 72, 72, 92] for a note asked for at [72, 72].
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
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>
eprintln! panics when standard error is a broken pipe (a GUI launch on
Windows), and a panic inside the panic hook aborts. The "no log file"
message goes through log::warn! (standard error only by then); the panic
hook's fallback writes to standard error with write_fmt and ignores the
error. Follow-up to #239, same fix as storytold/vectorcraft#465.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The schema documented printing/changes/copy/accessibility and their
defaults unconditionally, but the engine (like Acrobat) only enforces
/P behind an owner password: open_password alone encrypts the document
and leaves everything allowed, even when a caller passed copy: false
or changes: "none" explicitly, and the call reported success.
State the condition in the tool and property descriptions, and turn an
explicit restriction without permissions_password into InvalidArgs that
names the argument and what is needed, instead of writing an
unrestricted file. open_password alone is unchanged.
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
doc_info (and the MCP document-info resource built on it) serialised
annotation and link rectangles straight from the PDF /Rect, in user
space, while every other tool reports and accepts rectangles with the
origin at the top-left of the displayed page. Feeding a doc_info rect
to link_edit therefore moved the link, and on rotated pages the two
conventions disagreed in more than a y flip.
Convert both through the page's user_to_view, the same conversion
comment_list and link_list use, so the three agree. Pages that an
annotation cannot be matched to keep the raw rect.
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
- 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.
The desktop app installed no logger, so every log::warn!/log::error! in
the workspace was discarded: a failed autosave, a page that would not
render, a render worker that could not start. A launch from a desktop
menu has no terminal either, so there was nothing to attach to a bug
report.
apps/pdfcraft/src/logging.rs (no new dependency besides log):
- standard error plus <settings dir>/logs/pdfcraft.log, next to app.ron
(Linux $XDG_DATA_HOME/pdfcraft); each launch rotates the previous log
to pdfcraft.1.log and .2.log, so the log of a run that crashed
survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
(bounded) and written once the file is attached
- default levels: info for pdfcraft*, warn for everything else;
RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
error as the only sink
The logger is installed first in main, so the panic hook's report is
recorded too; the start-up messages in main go through it. The file is
opened after argument parsing (--version leaves no file) and after the
PrintCraft folder migration, which a fresh settings folder would block.
Documented in docs/development.md (Logs, environment variables).
`PageRenderer::new` keeps a failed open as `None`, so `page_count()` is 0 and
`text` without `--page` iterated nothing, printed nothing and exited 0 on a
password-protected file. When the renderer has no pages, ask `inspect` why
and fail with its message (the same wording `info` gives); a document that
inspects fine but still renders no pages is reported as unparseable. A
genuinely empty document still succeeds.
Fixes#132
Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M12: run XFA forms' JavaScript: calculations, validation, rows, visibility (#60)
Laid-out XFA forms had their fields but none of their behaviour: totals
didn't compute, "Add row" buttons did nothing, and subforms a check box
should reveal stayed hidden.
The js crate gains the XFA scripting object model as native boa objects
over a snapshot of the form tree: xfa.form navigation by name, SOM
resolution (absolute, relative with the containing-subform search,
descendants, indexes and wildcards), rawValue/presence/access, instance
managers (_name, addInstance, removeInstance, setInstances, count),
xfa.host (messageBox, resetData, print, setFocus, gotoURL), xfa.layout
page numbers, xfa.event, app, console and util, plus unqualified sibling
names as XFA resolves them. Scripts run with `this` bound to the node and
yield their completion value; everything they change or ask for comes
back as effects.
The xfa crate builds the live form tree (instances from the data, values,
presence and access with overrides) and the event table from the
template, keeps presence and access overrides on the AcroForm
(/PCXfaOverrides) so undo and reopening preserve them, adds and removes
row instances in the datasets packet through the existing merge
pipeline, and lays the form out again on demand.
The engine runs initialize and calculate scripts on open (an appended
revision, nothing dirty), exit, validate and calculate after a field
changes (a failing validate shows its message and keeps the value, as
Acrobat does), and click scripts for buttons through run_javascript, so
the app's buttons and the js_run tool work unchanged. Values a script
sets go to the fields and the data at once; rows and visibility changes
re-lay the form out. All of it is one undo step. JavaScript off turns it
off. FormCalc scripts are recognised and reported, not run.
Bounds: script length and nesting as before, loop and recursion limits,
at most 1000 instances added per run, 2000 scripts and 50 re-layouts per
event; a calculate script that adds rows, or a click script looping on
addInstance/setInstances, ends quickly with a bounded form (tested).
Checked on IRCC's IMM 5669: opens with its page-number scripts, the Print
button's xfa.host.print runs through js_run, fill and save keep the
original bytes.
* M12: Save and SaveAs named actions run the file commands (#60)
XFA forms' Save buttons are app.execMenuItem("SaveAs"), which the layout
turns into a SaveAs named action; the shell answered it with "the SaveAs
action isn't supported yet". Named Save and SaveAs now run file.save and
file.save_as, like Named Print opens the print dialog.
The buttons test covers Save against a file on disk (SaveAs opens the
native dialog, which a headless test must not).
* M12: bound XFA form scripts against hostile forms (#60)
Review fixes for the XFA scripting PR (#230):
- Effects: values, presence and access set again on the same object merge
(last wins, behind order-dependent effects); 10k effects, 100 message boxes
and 1000 console lines per script and per event, reported when hit. An
event's data writes are kept in memory and written as one rewrite of the
datasets packet; later writes in the same edit replace that stream in place.
- form_tree stops at 200k objects and says so; forms without scripts build no
live form and run nothing.
- Only click scripts may print, save, open links or move the focus; others are
noted in the console. Open-time messages and errors show right after open.
- Lower loop limits for initialize/calculate/validate, a time budget per event
after which remaining scripts are skipped and reported, and a per-script time
limit after which a runaway script is abandoned and the document's scripts
turned off.
- What open-time scripts change is noted in the XFA warnings.
- A document's Named /Save no longer writes the file on click (SaveAs opens
the Save As dialog, as a script's execMenuItem does).
- XFA click scripts are no longer written as /S /JavaScript actions: the
button carries /PCXfaClick and runs by SOM path. Private keys documented.
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>
* Split: keep only the resources each part draws (#204)
In a book whose pages share one resource dictionary, every split part carried every XObject the source listed: a part saved out at the source's size although its pages drew a fraction of the images. extract_pages now walks each part's content (form XObjects included) and drops the /XObject entries no page names; the garbage-collecting rewrite on save then drops the objects themselves. Tests cover a one-page part that must lose the other page's image and a part that needs both.
* Split/Extract pruning never drops an XObject something might still draw
A resources dictionary is left untouched when one of its pages has content
that can't be decoded, when a form nests past the depth limit, or when it
lists a Type 3 font without its own /Resources (its glyphs draw through
the page's). Regression tests fail without this.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Mohammed Munzir <Mohammed.Munzir@iga.gov.bh>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A misspelled option such as `--rotat` fell through the argument loop's
`_ => {}`, so `pdfcraft-cli edit` wrote a byte-identical copy of the input
and exited 0: an apparently successful edit that did nothing.
Every argument is now parsed before the file is opened. An unknown `--option`
fails with the list of accepted ones, an option without its value is refused,
and a second positional argument is reported. The documented options behave
as before (`--out`, `--password`, `--full` and the edits), and nothing is
written for a rejected command line.
Co-authored-by: William Stock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Release CI: macOS job on macos-15-xlarge
Custom macOS larger runners in a runner group do not pick up jobs; the
built-in M2 Pro label does. Only release.yml uses it, and the
actions_macos_xl budget caps the monthly spend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 90d60b0c98)
* CI: tell actionlint about the org's macos-release runner label (#202)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ddea3c72e0)
* About: contributor and model credits compiled in (grab bag + table, self-submitted names) (#196)
* About: contributors compiled in from contributors.json (grab bag + table, sortable, name toggle)
Adds About ▸ Contributors and About ▸ Models tabs per the craftrules standard
(standards/contributors.md): contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as
static tables; nothing is read at run time. Each contributor line: @username, opt-in
display/real name, PRs, commits, lines +/−/Δ, binary assets +/−, first/last commit.
Sortable by every column; alphabetical ignores case and the leading @.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section
build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: regenerate (verified people.toml entries)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 41e214c318)
* M13: exit cleanly when stdout pipe closes (#203)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
(cherry picked from commit 1cf0d319e4)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Ayan <108534604+excited-raccoon@users.noreply.github.com>
a_panicking_worker_clears_showing_and_queues_nothing failed on main (Linux CI and
Windows ARM64): panic unwinding on a loaded runner can take longer than the
1 s polling window. Tests now join the worker thread, so they can't time out.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M3: unify toolbar and menu theme preferences
Co-authored-by: Codex <noreply@openai.com>
* M3: use the current egui style API in theme regression
Co-authored-by: Codex <noreply@openai.com>
---------
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(i18n): follow Windows display language for Chinese UI
M14: resolve Auto through the Windows UI culture after locale environment
variables. Reuse Chinese region/script mapping and cover the query and
fallback behavior.
Refs #215
Co-authored-by: Codex <noreply@openai.com>
* Windows Auto language: ask Windows directly instead of starting PowerShell
sys-locale (1Password, MIT OR Apache-2.0) reads GetUserPreferredUILanguages, so
Auto no longer launches a hidden powershell.exe at startup (slow, and the kind of
behaviour endpoint security tools flag). All preferred display languages are
tried in order.
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>
* 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>
* M14: complete Simplified Chinese translation catalog
* M14: translate new XFA notices and counts
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M12: lay dynamic XFA forms out from their template (#60)
A dynamic XFA form is a PDF shell around an XML template: one placeholder
page ("requires Adobe Reader") and /NeedsRendering true. Every viewer
without an XFA engine shows the placeholder, and so did PrintCraft.
New crate printcraft-xfa (L3, already reserved in the layering table). It
reads the XDP packets, parses the template (XFA 3.3: subforms, areas,
fields, draws, exclusion groups, page sets, fonts, paragraphs, borders,
captions, rich text, inline JPEGs, occurrences, breaks, overflow leaders,
column widths), lays it out (positioned, tb, lr-tb, table/row; containers
without a width size to their content; flow across content areas and page
masters; header rows repeat after a break; hidden takes no space; page
number and count fields resolve) and writes real pages and AcroForm
widgets into the document: text, date (AFDate format and keystroke
actions from the picture clause), check boxes, radio groups, push buttons
with reset/print/save/URI actions. Standard-14 fonts stand in for Arial,
Courier New and friends; nothing is embedded.
The engine lays a dynamic form out when it opens it. The original bytes
stay: the layout is one appended revision, the widgets get appearances
from the forms crate, and the XFA packets remain so Adobe's viewers keep
rendering the form their way. A marker on the AcroForm (/PCXfaLayout)
stops a saved form from being laid out twice; each field keeps its SOM
path (/PCSom) for the data round trip to come. doc_info, the CLI's info
and the notice bar report the layout.
Checked on IRCC's IMM 5669 (Schedule A, Designer 6.2, 464 KB template,
AES-encrypted, usage rights): four pages and 308 fields in 27 ms, the
same page structure as the form, filled through form_fill, saved in
place with the original bytes intact and reopened with the values.
Not done, and said so in the parity notes: the datasets packet is neither
read nor written, so Adobe's viewers don't show values filled in here;
no FormCalc or XFA JavaScript (rows don't grow, nothing calculates);
choice lists are text fields; signature, image and barcode fields are
blank; metrics are approximate and fidelity is eyeballed, not measured.
Tests: crates/xfa (parsing, sizing, pagination with repeated headers,
PDF writing, hostile input), the engine (open, fill, incremental save,
reopen without a second layout), the automation tools (open, info,
render, fill, save, reopen) and the UI notice (kittest).
* M12: XFA forms read and write their datasets packet; static forms too (#60)
With the layout alone, a form filled in PrintCraft opened blank in Adobe
Reader, and one filled in Acrobat opened blank here: Adobe's viewers keep
the values in the XFA datasets packet, not in the AcroForm fields.
The xfa crate's new data module reads the datasets packet (normal binding:
a field's node is named after it under its named ancestor subforms,
repeated instances as repeated siblings) and writes it back from the
fields. Laying a form out now takes each field's value from its data node
(dates in ISO form shown in the field's pattern, check boxes and radio
groups by their on values) and gives a repeating subform or table row as
many instances as the data has, so rows another viewer added appear.
Every laid-out field carries its SOM path and its on/off items, so the
packet can be rebuilt from the AcroForm at any time.
The engine writes the datasets after every form edit (one revision with
the field change), and on open applies the datasets to the fields of a
static XFA form, or of a dynamic one laid out earlier, when another
viewer changed them since. The static-form notice now says the two are
kept in step instead of warning that Acrobat may disagree.
Checked on IRCC's IMM 5669: fill, save in place (original bytes intact),
and the file holds <IMM_5669><page1><familyName>SINGH</familyName>…, the
structure Acrobat writes for this form; reopening shows the values.
Not done: explicit bind refs, global binding and data descriptions are
not followed; no import or export of standalone XML or XDP data files.
* M12: XFA layout memoizes container sizes, bounding measurement work (#60)
A container without a width measured its content for its width and again for its
height at every level, so nested width-less subforms took 2^depth measurements, all
synchronously in Session::open. Container sizes are now cached per (node, width,
depth), the root and page-area items keep stable addresses instead of being cloned
per measurement, and a measurement budget turns anything left into TooLarge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M12: rustfmt the merged XFA notice (#60)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M12: XFA datasets are merged into, not replaced; capped; UTF-16 kept; rewrites recorded (#60)
- write_datasets merged nothing: it replaced <xfa:data> with the AcroForm-bound values
alone, dropping unbound, namespaced and deep data. It now edits the existing packet
text in place: a bound node's text is replaced only when it differs, missing nodes are
added below the deepest existing one, and everything else (unbound data, other
namespaces, attributes, comments, layout) stays byte for byte. A packet of an XFA array
that relies on the envelope's prefixes is read inside a wrapper declaring them.
- What can't be written (a node holding structured content, too-deep paths or absurd
SOM indices, conflicting fields, an unreadable or non-UTF packet) comes back as
DatasetsWrite.warnings; the engine keeps them in Document.xfa_warnings and
info.warnings (surviving re-reads), and doc_info reports xfa_warnings.
- Building data counted same-name siblings for every one it added (quadratic in an
untrusted index) with no total cap. Children are now indexed by name, indices above
10,000 and paths deeper than 64 are refused, and one write creates at most 20,000 nodes.
- Packets are decoded exactly (UTF-8, UTF-16 LE/BE with or without BOM) and written back
in the encoding they came in; anything else is reported and left untouched instead of
being rewritten from a lossy UTF-8 reading.
- A static XFA form whose fields are rewritten from the datasets on open now says so in
info.warnings, naming the fields.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
An unwrapped title carried a long file name past the viewport: egui's Modal
sizes to its content, so the centered panel grew to the name's full width,
clipped its own message and pushed Save/Cancel off-screen (reported on
Windows, reproduced everywhere). Wrap the title to the dialog width in the
save prompt and the password prompt, and pin the title and all three
buttons inside a 1365x719 screen in a regression test.
Co-authored-by: Mawfyy <mawfy12@gmail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* 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: 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>
* 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>
* 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>
* 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>
* 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>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Simon Leblanc <contact@leblanc-simon.eu>
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>
* 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>
* 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>
---------
Co-authored-by: Simon Leblanc <contact@leblanc-simon.eu>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: 才倾梦华V <185041488+CQMHV@users.noreply.github.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: 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>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: 才倾梦华V <185041488+CQMHV@users.noreply.github.com>
* About: contributors compiled in from contributors.json (grab bag + table, sortable, name toggle)
Adds About ▸ Contributors and About ▸ Models tabs per the craftrules standard
(standards/contributors.md): contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as
static tables; nothing is read at run time. Each contributor line: @username, opt-in
display/real name, PRs, commits, lines +/−/Δ, binary assets +/−, first/last commit.
Sortable by every column; alphabetical ignores case and the leading @.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section
build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: regenerate (verified people.toml entries)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Custom macOS larger runners in a runner group do not pick up jobs; the
built-in M2 Pro label does. Only release.yml uses it, and the
actions_macos_xl budget caps the monthly spend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The standard macOS runners are shared with all CI and queue for an hour or
more. The macos-release runner (GitHub-hosted M2 Pro) is in a runner group
usable only by release.yml on the release branch, with a monthly spend cap
on the actions_macos_xl SKU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #164, a script step's "out" resolves inside --root like every other path, so
"smoke/page.png" landed in smoke/smoke/. Name it relative to the root.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* #143: Windows MSI desktop shortcut checkbox and install-complete dialog
Add native installer dialogs (installer-ui.wxs): a welcome screen with a
"Create a desktop shortcut" checkbox (ticked by default), repair/remove,
progress, and "Setup completed successfully" / failed / cancelled screens
with Finish. Unattended /qn and /qb installs never wait on a dialog and can
skip the desktop shortcut with INSTALLDESKTOPSHORTCUT=0.
Start Menu and desktop shortcuts are now plain links to pdfcraft.exe in
their own components (HKLM key paths, removed on uninstall) instead of
advertised MSI shortcuts.
test-msi.ps1 checks the compiled MSI's shortcut, checkbox and dialog tables
during packaging; the ARM64 install smoke test checks both shortcut
targets, their removal on uninstall, and the INSTALLDESKTOPSHORTCUT=0
opt-out.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqiBTPWGWbhQDYNFcxFte6
* #143: run test-msi out of process before signing; add files-in-use dialog
test-msi.ps1 held the MSI open through a Windows Installer COM handle in
package.ps1's own process, so signtool could fail on signed release builds.
Run it in a child process and release the COM objects.
The hand-authored UI had no MsiRMFilesInUse dialog, so an upgrade or repair
while PdfCraft is running could not offer to close it. Add one (OK closes the
programs with MSIRMSHUTDOWN=2, Cancel stops setup) and check for it in
test-msi.ps1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqiBTPWGWbhQDYNFcxFte6
---------
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.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>
* macOS: File ▸ Open and file pickers no longer crash the app
A blocking rfd::FileDialog runs -[NSOpenPanel runModal], a nested run loop
inside winit's event handler. A mouse or gesture event that arrives while it
spins re-enters the handler, winit panics ("tried to handle event while
another event is currently being handled"), and because the panic can't
unwind out of the AppKit block callback the app aborts.
Open, Combine, Insert pages, Replace pages and OCR ▸ multiple files now use
rfd::AsyncFileDialog, which starts the panel as a sheet and returns at once.
A worker thread waits for the choice and the app uses it on a later frame.
Insert and Replace remember their document and drop a pick that arrives after
the user switched tabs. pick_override lets tests and automation drive these
commands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Cargo.lock: pollster for pdfcraft-ui-egui
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>
* M14: persist the default PDF workspace mode
Keep the saved workspace preference separate from current mode and explicit session overrides. Preserve CLI ordering and PDF Initial View behavior, and cover persistence, Preferences, and control-channel use.
Co-authored-by: Codex <noreply@openai.com>
* Opening a PDF keeps a closed left panel and an unchanged workspace's tool panel
The default workspace is applied only when it differs from the current mode, and never
reopens a left panel the user or `--left closed` closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: James B. Gower <gowerblake2012@gmail.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>
* M3: add middle-click autoscroll
Co-Authored-By: Codex <noreply@openai.com>
* M3: keep middle-click autoscroll toggled after release
Keep scrolling enabled after releasing the wheel, including when the
pointer moves before release. Anchor speed to the initial press event,
so movement later in the same frame cannot shift the starting point.
Cover toggle behavior and increasing speed with distance in both views.
Co-Authored-By: Codex <noreply@openai.com>
* M3: increase middle-click autoscroll speed
Double scrolling speed at the same pointer displacement and raise the
speed cap proportionally, preserving the dead zone and click toggle.
Keep the sustained-scroll fixture visible at the faster response.
Co-Authored-By: Codex <noreply@openai.com>
* M3: match Chromium middle-click scrolling
Use a 15-point dead zone and Chromium's nonlinear distance-to-speed curve
in the document viewer and Organize Pages. Request continuous redraws so
scrolling follows elapsed frame time and preserves fractional movement.
Keep click toggling, cancellation, and the existing marker unchanged.
Cover reference speeds, pathological inputs, pause/resume, and consistent
travel at 30/60/120/144 fps with different display scales and document zoom.
Co-Authored-By: Codex <noreply@openai.com>
* M3: leave README unchanged in autoscroll PR
Remove the feature bullet so README matches upstream main. Keep the
feature explanation in the pull request description.
Co-Authored-By: Codex <noreply@openai.com>
* M3: limit custom middle-click scrolling to Linux
Return before handling input on other targets so their existing
middle-button behavior, widgets, cursor, and Escape shortcuts stay intact.
Restrict the autoscroll UI regressions to Linux and add unsupported-platform
input coverage plus a non-Linux UI regression for the native CI matrix.
Co-Authored-By: Codex <noreply@openai.com>
* ROADMAP: drop the log line about merging 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>