186 Commits
Author SHA1 Message Date
24f93e6102 feat(ui): preference to hide author-only highlight hover previews (#1432) (#1466)
* feat(ui): preference to hide author-only highlight hover previews (#1432)

* Keep the Identity heading, translate the new hover preference, move its persist key out of json!

The heading rename dropped the existing Identity translation in every catalog; the
new checkbox label now has an entry in every catalog. The json! object in persist()
is at serde_json's recursion limit, so the new key is set after it like redact_prefs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* cargo fmt

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-15 03:33:43 -04:00
a6124bcdfe M12: find untagged accessibility content (#1463)
* M12: find untagged accessibility content

* i18n: translate untagged content checker (rebased)

* fix(i18n): refresh untagged content translation hashes

* fix(tests): restore full test modules for find-untagged content

* fix(i18n): preserve existing translations after rebase

* ci: rerun shared timeout test

* chore(deps): refresh rust for the advisory gate

Use the current lockfile, including the rustls security update, so the dependency check can run on this branch.

* Refresh catalog hashes after merging main

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-15 03:33:19 -04:00
afa50999ef M12: tag unstructured AcroForm widgets (#1451)
* M12: tag unstructured AcroForm widgets

* i18n: translate form tag command

* i18n: repair form-field label merges

* fix(i18n): refresh translation catalog hashes

* fix(i18n): preserve main translations after rebase

* Merge main; tag a widget listed on several pages once, docs say existing trees are extended

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Merge main; add the Vietnamese catalog entry for Tag form fields

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-15 03:24:55 -04:00
9b6dab142f M10: Windows print jobs show per-sheet progress; printers list faster (follow-up to #944) (#1236)
* M10: Windows print jobs show per-sheet progress; printers list faster

Follow-up to #944, carrying the two parts main doesn't have yet.

- The progress card follows a job being spooled and counts its sheets
  as they reach the spooler ("Sending sheet 2 of 3 to Office…"), using
  craft-print's new submit_with_progress. It replaces the "Sending to…"
  toast, leaves another long job's card alone, and goes when the job
  ends; the spooler's answer is still a notice.
- craft-print bumped to storytold/craft-libs#27, which also lists
  Windows printers through .NET's PrinterSettings (the API jobs spool
  with) instead of a CIM query of Win32_Printer.
- "Sending sheet {s} of {n} to {printer}…" in all 20 catalogs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 03:04:29 -04:00
e08f87f138 M5: Make Properties Default in Comment Properties (#2031)
* M5: Make Properties Default in Comment Properties

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

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:59:01 -04:00
bfe61bebc5 M3: arrow keys follow the Scroll speed preference (#985) (#2333)
* M3: arrow keys follow the Scroll speed preference (#985)

The preference set how far a wheel notch scrolls, but the arrow keys
always moved 40 points. They now move 40 points times the preference's
factor (half at Slow, double at Fast), Page Down / Page Up still move a
screen, and the preference is labelled for both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCdPYhYkggLJVYoME2kr5R

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:55:49 -04:00
b33445aa2e fix(print): the Print dialog waits for the printer list instead of offering Save as PDF (#160) (#2332)
* fix(print): the Print dialog waits for the printer list instead of offering Save as PDF (#160)

The first time the dialog opens, the printer list is read in the
background and no printer is selected yet, so the dialog showed Save as
PDF and its button saved a PDF. It now says it is looking for printers
and the button waits until the list arrives or the user chooses Save as
PDF. An empty list says that no printers were found. The button's
Print / Save as PDF label is translated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PCdPYhYkggLJVYoME2kr5R

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:55:22 -04:00
89f0fa2bd5 M14: Italian catalog: complete the missing entries (#2320)
* M14: Italian catalog: complete the missing entries

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

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

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: kcobelli-pixel <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:54:32 -04:00
b850cbb5c9 M4: lay out Organize pages as Acrobat's panel (#2248)
* M4: lay out Organize pages as Acrobat's panel

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M4: parity notes for the Organize pages panel

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: abc2cb <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:51:32 -04:00
3de8d56784 M3: Save asks before writing over a changed file, one tab per file, locked autosaves (#2166) (#2218)
* M3: Save asks before writing over a changed file, one tab per file, locked autosaves (#2166)

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

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

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

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

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Fable 5.1 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:49:40 -04:00
RidwanandBrandon Thomas 586f2b2be3 M9: harden .p12 digital IDs (#1847) (#2201)
* M9: harden .p12 digital IDs (#1847)

New digital IDs are written with 600,000 PBKDF2-HMAC-SHA-256 rounds (OWASP)
for both encrypted parts and the MAC, instead of 2048. The MAC gets the same
count because checking it alone tells whether a guessed password is right.

Opening a .p12 now spends at most 10 million rounds of key derivation across
the whole file, charged before any hashing. The file chose its own iteration
counts unchecked (`u64 as u32`), so a crafted file could hang the password
check for about an hour, or wrap 2^32+1 to one round.

Creating an ID needs at least 8 characters (NIST SP 800-63B), from one
constant, pkcs12::MIN_PASSWORD_CHARS, shared by the app, sign_id_create and
its MCP schema. Opening an existing ID takes any password.

* Refresh catalog hashes after merging main

---------

Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:48:32 -04:00
b369a50b68 fix: Space Audit lines up its columns and counts form fonts as fonts (#2118)
* fix(optimize): count what form fields and comments draw with by kind

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

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:45:58 -04:00
712ae9c36a M3: a document that asks to open in Full Screen mode asks first (/PageMode /FullScreen) (#2086)
* M3: a document that asks to open in Full Screen mode asks first (/PageMode /FullScreen)

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

Refs #379, #1459.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Fable 5.1 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:43:17 -04:00
c0f76fc73a M4: set page transitions and display times (/Trans, /Dur) through the engine and the tools (#2083)
* M4: set page transitions and display times (/Trans, /Dur) through the engine and the tools

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

Refs #379.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Fable 5.1 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:42:28 -04:00
bbc99126db i18n(es): translate the new print and Add text strings, Type on the signature pad, tú register (#2049)
* i18n(es): translate the new print and Add text strings, Type on the signature pad, tú register

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

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:41:10 -04:00
572870eca8 M2: clip intersection starts its row walk at the first shared row (#2026)
* M2: clip intersection starts its row walk at the first shared row

clip::intersect in vello_common, the step every clipped path passes
through, began its walk over strip rows at the earlier of the two paths'
first rows. Each path therefore visited every row above it, although rows
where one path has no strips produce no output. Every page is clipped (the
crop box is a clip), so this ran for every stroke of every render: on the
A1 engineering sample it was the hottest function in a full-page render.

Start at the later first row, as vello_common 0.1 does. The strips and the
pixels are unchanged; a test counts the rows the walk visits and fails
without the change.

vello_common 0.0.8 is vendored with that one-line PdfCraft patch (marked
in the source), its unit test, the vendor/README.md row and an
ATTRIBUTION.toml entry for its reference image. Cargo.lock records it as a
path crate.

Co-Authored-By: Claude Haiku 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Haiku 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:38:54 -04:00
8da1d3efce Redaction Tool Properties: the mark's appearance, and Make properties default (#2008)
* Redaction Tool Properties: the mark's appearance, and Make properties default

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:38:22 -04:00
6c22c78a72 Edit: crop an existing page image with its handles (#2002)
* Edit: crop an existing page image with its handles

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:37:56 -04:00
75c9a9c27b Comments: font size for text boxes in Comment Properties (#1908) (#1984)
* Comments: font size for text boxes in Comment Properties (#1908)

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:36:35 -04:00
8e803b6290 Print: print as image (Advanced), with grayscale pictures (#1965)
* Print: print as image (Advanced), with grayscale pictures

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:34:09 -04:00
Zeyu LiandBrandon Thomas f52c92882d M9: signature labels follow the interface language, and one the font can't draw is refused, never written as ? (#1951)
* M9: signature labels follow the interface language, and one the font can't draw is refused, never written as ?

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

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

Follows up the note on #1112.

* Refresh catalog hashes after merging main

---------

Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:32:38 -04:00
9caddc6e9b Compare files: side by side, scrolled and zoomed together (#1927)
* Compare files: side by side, scrolled and zoomed together

After Compare files, when the old version is open in a tab and the window is
at least 1000 px wide, the document area splits: the old file on the left, the
new on the right, each a page view with its own shading (deleted and replaced
on the old, inserted and replaced on the new, visual differences on both). The
half under the pointer leads; the other takes its zoom, fit, layout and
rotation, and its scroll offset on the next frame. A change clicked in the
panel jumps both halves; Side by side / New file only toggle the mode; Clear
ends it. The old file keeps rendering while shown. Rows in every language,
parity misc.compare-side-by-side shipped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

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

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

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Osvaldo Duran <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:30:08 -04:00
df0bc0ffbc M11: merge image fragments, and the Standard and Mobile optimizer presets (closes #1783) (#1784)
* M11: merge image fragments, and the Standard and Mobile optimizer presets

Some PDF printers draw a page picture as thousands of images one pixel row
high (one real page: 3,300 strips, 4.3 MB). The optimizer works image by
image, a one-row image can't be resampled, and every JPEG strip carries its
own headers, so Reduce File Size saved 2 bytes on it.

- pdfcraft-optimize `fragments` (Settings::merge_fragments, on by default):
  runs of `q … cm /Im Do Q` drawing upright 8-bit grey or RGB images in one
  colour space, sharing their left and right edges and stacked edge to edge
  (at least four, nothing else drawn between them; or, failing that, the
  rows drawn one under the other up to a gap) become one lossless image at
  their finest resolution. The image settings then resample it, and Clean
  Up drops the strips. A page whose content can't be read back exactly is
  left alone.
- Presets as in Acrobat's Settings menu: Standard (Reduce File Size's
  choices) and Mobile (images above 144 ppi to 96 ppi, JPEG high). The PDF
  Optimizer dialog has a Settings menu (Custom once an option changes) and
  a Merge image fragments box; doc_optimize takes preset and
  merge_fragments and reports merged_images and fragments_merged.
- A progress stage for the merge; four new strings in all 21 catalogs.

On the real page: Standard/Reduce 4.3 MB → 231 KB, Mobile → 161 KB, with
a rendering closer to the original than Acrobat's Mobile result (147 KB).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: magdale76 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:25:19 -04:00
c5c5d0d661 M2: Add word and character counts with optional content exclusions (#1780)
* M2: Add word and character counts with optional content exclusions

Retain the complete reviewed change, including prior integration resolutions,
on current upstream.

M2: Add document word counts and semantic exclusions

* Refresh catalog hashes after merging main

---------

Co-authored-by: LabDennis <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:24:33 -04:00
7c2ca46d2f i18n: Taiwan vocabulary and the missing entries in zh-hant (#1750)
* i18n: Taiwan vocabulary and the missing entries in zh-hant

The Traditional Chinese catalog says it uses the vocabulary of Taiwan,
but 113 entries used mainland terms, often next to the Taiwan term for
the same word elsewhere in the file (對話框 and 對話方塊, 應用 and
套用 for Apply, 列表 and 清單). They now read 套用, 捨棄, 對話方塊,
另存新檔為, 自訂, 目前, 清單/清單方塊, 置中, 平面化, 選取, 送出 (submit),
浮水印, 醒目提示, 一般, 進階, 重設, 存取, 全螢幕, 範例, 標題列,
工具列, 收件者 and 字型大小, and quotations use 「」 instead of “”.
Application (應用程式) and Commit (提交) keep their words.

The 80 entries the Simplified Chinese catalog has and this one lacked
(About ▸ Contributors and Models, link and email confirmations,
preferences defaults, Create PDF from images, print scale notes, Fill &
Sign) are translated from the English, so those labels no longer show
in English. Reviewed by a native Taiwanese speaker.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* i18n: zh-hant Grab bag reads 精簡

The contributor list's compact view keeps the chosen sort order, so it
is named for its look (精簡, against 表格), not as an unordered pile.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Refresh catalog hashes after merging main

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-15 02:23:03 -04:00
db5b9b4364 M14: add Vietnamese interface and date localization (#1712)
* G14: Vietnamese catalog — menus, file and shell chrome

Add the Vietnamese (vi) TSV catalog header and translations for menus,
File/Edit/Help chrome, clipboard and common shell labels. PhotoCraft and
VectorCraft catalogs used only as translation memory when English source
and context matched. ATTRIBUTION records the asset hash.

Co-authored-by: Cursor <[email protected]>

* G14: Vietnamese catalog — forms, signatures and pages

Translate form/Fill & Sign/certificate wording and page organize/rotate/
extract/split labels. Keep product and format names unchanged.

Co-authored-by: Cursor <[email protected]>

* G14: Vietnamese catalog — comments, view, a11y and contexts

Translate comments/markup, view/zoom/preferences tools, accessibility and
preflight sentences, plus contextual rows (certificate, calendar, keys,
scroll speed, signature pad, action wizard, @plural one-form).

Co-authored-by: Cursor <[email protected]>

* G14: Vietnamese catalog — remaining dialogs and notices

Complete the Vietnamese catalog to every (context, English) key in the
bundled union (2,580 entries). No unapproved English UI fallbacks remain
outside the documented VI_KEEP_AS_IS list.

Co-authored-by: Cursor <[email protected]>

* G14: restore complete Vietnamese catalog key order

Replace the domain-appended catalog with the verified 2,580-entry union
in stable key order so coverage matches every bundled reference key.

Co-authored-by: Cursor <[email protected]>

* G14: register Vietnamese interface and date language

Add `vi` / Tiếng Việt to LANGUAGES and DATE_LANGUAGES with plural_none
and rtl false. Locale tags vi, vi-VN and vi_VN.UTF-8 resolve. Calendar
uses Tháng Một / tháng Một; Fill & Sign refuses non-WinAnsi month names.
Coverage tests require full command, literal, key and credits coverage.

Co-authored-by: Cursor <[email protected]>

* G14: document Vietnamese localization

Record Vietnamese at 100% catalog coverage in localization-parity and
ROADMAP, list Tiếng Việt in Preferences/docs, and note the G14 catalog
in the progress log. Engine/OS details, document contents, filenames,
CLI/MCP and external help remain out of scope by design.

Co-authored-by: Cursor <[email protected]>

* G14: use ASCII > in Vietnamese preference paths

Inter lacks U+25B8 (▸). Match other Latin catalogs and write preference
paths as "Tùy chọn > …" so the glyph coverage test stays green.

Co-authored-by: Cursor <[email protected]>

* G14: finalize Vietnamese terminology and current coverage

Disambiguate theme Appearance (Giao diện) from object look (Diện mạo),
normalize form Field to Trường, make redaction apply wording permanent,
use numeric calendar months, close the GoToR/digital-ID key gap, and
pin regression assertions. Refresh the vi.tsv attribution hash.

Co-authored-by: Cursor <[email protected]>

* G14: align localization docs with Vietnamese coverage

Recompute the twelve-language mean (≈59% → ≈68%), document 23 interface
languages, and remove stale claims that Vietnamese is missing.

Co-authored-by: Cursor <[email protected]>

* G14: cover Wayland title-bar strings in Vietnamese

Translate Minimize/Maximize/Restore and the system title-bar preference
added on upstream main, and refresh the catalog attribution hash and counts.

Co-authored-by: Cursor <[email protected]>

* G14: correct current union key count in localization docs

Co-authored-by: Cursor <[email protected]>

* G14: prepare Vietnamese localization for review

Trim the vi.tsv header to maintainer-oriented notes, record native-speaker
community review in attribution and localization docs, and refresh the
weighted readiness arithmetic (localization ≈68% → ≈50%).

Co-authored-by: Cursor <[email protected]>

---------

Co-authored-by: DUY Tran Nhat (SAE-KIDS) <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-10-15 02:21:11 -04:00
Earl Co 0b732e90ff M12: mark leaf tags as artifacts (#1475)
* M12: mark leaf tags as artifacts

* chore: refresh translation asset hashes

* fix(a11y): retain text autotag exports after artifact merge
2026-10-15 02:18:13 -04:00
Ibn ZwanckaertandClaude Opus 5.5 5e12f5fd6d feat(ui): reorder and cycle document tabs
Reorder document tabs by dragging while keeping the active document and its view state. Add Ctrl+Tab and Ctrl+Shift+Tab cycling in visible order, including Combine files, and translate the menu labels.

Keep timestamped batch test expectations tied to the originally generated PDF bytes.

Closes #1537
Closes #1542

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 20:06:20 -06:00
Tanish MankuandClaude d9607ec3be F3: one documented substitution chain, and report what it chose and why
A font the document does not embed was replaced by whatever the caller
happened to pick, and nothing told the user. `doc_info` said only
`embedded: false`, which is why "my Chinese PDF shows nothing" has been
a mystery rather than a sentence.

`crates/fonts/src/fallback.rs` applies one chain, in a documented order,
and returns what it chose with a reason:

1. embedded -> nothing substituted;
2. /BaseFont names a standard-14 face or a known alias (exact metrics);
3. craft-fonts holds that exact family;
4. /CIDSystemInfo /Ordering names a script we have a face for;
5. the ordering names a script we have no licensed face for -- report
   none, so nothing is drawn rather than the wrong glyphs;
6. otherwise the nearest of the fourteen by descriptor flags.

It delegates to the existing per-script selectors instead of restating
their order, so each keeps one source of truth. It answers the
dictionary question only; "which face has a glyph for this character?"
depends on the string being drawn and stays where the drawing happens.

`doc_info` gains `substitute` and `substitution` per font. Both null
means the document's own glyphs are shown; a null `substitute` with a
reason means no face was available at all. That is Acrobat's Actual
Font, minus the interface, which `parity/` now records as still missing.

Two findings on the way:

`Std14::from_base_font` matches loosely, so "SomeHouseSerif" resolved to
Times by its name containing "serif". The face is a fair guess but
reporting it as "names one of the standard 14" would have been untrue of
a font nobody has heard of. `Std14::recognise` now says whether a name
matched an alias or only a family hint, and the two report differently.
`from_base_font` is unchanged.

The generated table moved to `crates/fonts/src/std14_metrics.rs`, data
alone, and the ATTRIBUTION entry and AGENTS.md 1.1 line follow it. F1
put the hash on a file holding both the table and the code reading it,
so this commit's ordinary edits tripped the gate -- and a hash that
churns on unrelated changes gets re-attested reflexively, which is the
protection gone. Verified both directions after the split: editing
std14.rs passes the gate, changing one width in std14_metrics.rs fails
it.

Every new test was watched failing first: removing the script step
breaks the two unit tests and the doc_info integration test.

Co-Authored-By: Claude <[email protected]>
2026-10-13 16:42:10 -07:00
7437bde378 M3: draw a GNOME-style title bar on Wayland (#553) (#1530)
GNOME's Wayland compositor draws no window frames (it offers no
zxdg_decoration_manager_v1), so winit drew its own fallback title bar,
which ignores GNOME's theme, dark mode and button layout. PdfCraft now
asks the compositor at launch (apps/pdfcraft/src/decorations.rs) and,
where it draws no frames, turns winit's frame off and makes the tab strip
the header bar, as in GNOME's own apps
(crates/ui-egui/src/window_controls.rs):

- Minimize, maximize/restore and close in Adwaita's style (round buttons,
  symbolic glyphs drawn in code, no new assets), in the order and on the
  side named by org.gnome.desktop.wm.preferences button-layout, read
  through gsettings; Fedora's default `appmenu:close` shows only close.
  Hidden in full screen.
- Resize zones along the window's edges and L-shaped corners, above the
  rest of the UI so a press there resizes instead of reaching a
  scrollbar; a hairline outlines the window while it isn't maximized.
- Preferences > Use system title bar (shown only where the app draws the
  frame) gives the window back winit's frame at once, without a restart
  (part of #1001). Named as in VectorCraft and PhotoCraft, but GNOME draws
  no title bars for apps, so on GNOME this is winit's basic frame, not
  GNOME's own; the tooltip says so. Saved as `system_title_bar`;
  `system-title-bar` on the control channel.
- PDFCRAFT_TITLEBAR=app|system overrides the detection.

KDE, Sway, Hyprland, COSMIC and X11 offer server-side decorations and
keep the frame their desktop draws; macOS and Windows are unchanged.

Not done: right-clicking the header bar doesn't open GNOME's window menu
(egui has no command for it), the window has no shadow or rounded
corners, the resize zones lie just inside the window rather than outside
it, and Windows still uses its system frame (the rest of #1001).

The new labels (Minimize, Maximize, Restore, Use system title bar and its
tooltip) were added to all 21 translation catalogs. These translations
have not been checked by anyone fluent in those languages and should be
reviewed. The catalogs' hashes in ATTRIBUTION.toml are updated.

Made with Claude Code (Claude Opus 5.5, medium effort). It passes the
workspace quality gates (fmt, clippy, cargo test --workspace, xtask
assets) and was checked by hand on Fedora 44 GNOME (light and dark,
maximized and not, the preference toggled live), but it has not been
exhaustively regression tested.

Co-authored-by: bmh129 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 03:04:13 -04:00
RidwanandBrandon Thomas 7739685796 Issue #1396: remove an imported digital ID (#1515)
Digital IDs added through Sign with a Digital ID (imported .p12/.pfx files
or new self-signed ones) are saved with the app settings, but nothing ever
took one off the list, so an old or mistakenly imported certificate stayed
there for good (#1396).

Sign with a Digital ID now has Remove next to Configure New Digital ID. It
acts on the selected file-based ID, asks first ("Remove "<name>" from this
list? The file itself is not deleted."), and takes the entry off the saved
list. The .p12 file stays on disk. Keychain and Windows store IDs can't be
removed here: the OS manages them and they are listed again on every
signing.

- sign_ui: PdfCraftApp::remove_digital_id refuses OS store IDs and stale
  indexes, keeps the dialog's selection on the same ID (or the first
  usable one), and goes to Configure when no IDs are left.
- SignDraft::confirm_remove holds the entry awaiting confirmation; clicking
  another row cancels the question.
- i18n: the confirmation is translated in all 21 catalogs; ATTRIBUTION.toml
  hashes updated.
- Tests: a unit test for the list, the selection and persist/restore, and a
  real-shell test that imports a .p12, cancels, then removes it.

Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 02:49:38 -04:00
4cb803038d M3: confirm before opening absolute GoToR paths (#1364) (#1489)
* M3: confirm before opening absolute GoToR paths (#1364)

* fix(i18n): translate GoToR confirmation dialog labels

---------

Co-authored-by: srt8hellcat2-ui <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-13 02:16:21 -04:00
Earl Co ecc4a305d7 M12: move tags in the order panel (#1482) 2026-10-13 00:04:16 -04:00
Tanish MankuandClaude Opus 5 aaa19d87af F1: put the generated table on the AGENTS.md 1.1 closed list
Isolated so it can be reverted on its own.

AGENTS.md 1.1 keeps a closed list of Adobe-authored non-visual technical data,
and adding to it needs the owner's explicit approval, which was given on
2026-10-08. The item itself -- the standard-14 font metrics and encoding tables
from the PDF specification -- was already on that list; what is new is holding
a copy first-party rather than only through hayro, which is what lets generated
appearances measure with it and what survives hayro being retired in M2.8.

The gate only understood the list for dependency-bundled files, and rejected
adobe_data on a repository file outright. It now accepts it for a closed list
of first-party paths, so crates/fonts/src/std14.rs carries an ATTRIBUTION entry
with its SHA-256: regenerating the table fails the gate until the entry is
updated, which makes a change to Adobe-derived data a deliberate act rather
than an ordinary edit.

Worth noting that .rs is not an asset extension, so the table would never have
been scanned at all. Entering it on the list is a choice to be checked, not a
requirement the gate imposed.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-12 13:57:43 -07:00
4bfd3c2729 M14: add Lithuanian interface and date localization (#1401)
* M14: add Lithuanian interface localization

Register Lithuanian UI and date languages with three integer plural forms
and a 2,572-entry contributor-original catalog. Cover dynamic dialog labels,
contextual calendar and certificate wording, commands, tools, and every key
in the other bundled catalogs. Add real-shell calendar, inspector and
password-dialog regressions; preserve PDF values and document state.

Update localization documentation and asset attribution. Native-speaker/VLKK
terminology review remains outstanding.

Validation: cargo xtask ci passed; cargo-deny was skipped because it is not
installed. Five rendered dialogs and contributor manual testing checked UI.

Co-Authored-By: OpenAI gpt-6.1-sol <[email protected]>

* M14: preserve multilingual localization documentation

Restore the existing language descriptions instead of replacing general
localization guidance with Lithuanian-specific coverage. List every
registered language and control-channel code; keep Lithuanian details
alongside the existing catalog descriptions.

Include the previously omitted Kazakh, Polish, Slovenian, Greek and Turkish
catalogs in the shipped-language table without changing the historical
coverage snapshot or existing assessments. Keep Lithuanian roadmap notes
concise and additive.

Validation: all 22 registered languages appear in documentation; every
existing descriptive paragraph is retained. cargo xtask ci passed, with
cargo-deny skipped because it is not installed.

Co-Authored-By: OpenAI gpt-6.1-sol <[email protected]>

* M14: record completed Lithuanian native-speaker review

The contributor confirms they are a native Lithuanian speaker and have
reviewed the translation. Remove obsolete pending-review labels from the
asset manifest and documentation; regenerate ATTRIBUTION.md. Remove the
redundant generated-message disclaimer from the Lithuanian description,
keeping the existing general localization documentation unchanged.

Validation: cargo xtask ci passed; cargo-deny was skipped because it is
not installed. Catalog content and its SHA-256 are unchanged.

Co-Authored-By: OpenAI gpt-6.1-sol <[email protected]>

* Lithuanian: cover main's Copy Image labels, list lt last, check its glyphs

Adds the six Copy Image rows from #1363, moves lt to the end of LANGUAGES
and DATE_LANGUAGES (after tr), adds lt.tsv to the primary-font coverage
test, writes the JavaScript preferences path with '>' as the other
catalogs do (Inter has no ▸), and refreshes counts and hashes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: OpenAI gpt-6.1-sol <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 08:54:15 -04:00
0cb311d21c M3: select an image on the page and copy it (#1363)
* M3: select an image on the page and copy it

Select tool: a click on a raster image selects it (also one drawn through Form artwork, or with
text over it such as an OCR layer); ⌘C copies it as a file in its original format (a JPEG as
stored in the PDF, otherwise a lossless PNG). Right-click ▸ Copy Image copies its pixels at full
resolution, and Copy Image in Original Format the file; Edit text & images has both for the
selected image. Both respect the document's copy permission.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GWUuTDPKL5wivnSJuCkC8Q

* M3: translate the copy-image strings into Czech, Kazakh, Slovene and Turkish

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GWUuTDPKL5wivnSJuCkC8Q

* Copy image: keep the clipboard file in a private per-user folder

Copy Image in Original Format wrote into the shared temporary folder
(/tmp/pdfcraft-clipboard) and deleted everything in it first, so another
user on the machine could plant a link or folder there and have PdfCraft
delete or overwrite their target. The file now goes in PdfCraft's
per-user cache folder (or the portable data folder), which is checked to
be a real folder and set to 0700 on Unix (refused when it isn't ours).
Only earlier copies (regular files named '<stem> p<N> img<M>.jpg|png')
are removed, links are never followed, and the new file is created with
create_new so it can't be redirected.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Tests: escape Windows paths in synthetic GoToR file specifications

canonicalize() returns \\?\C:\... on Windows; unescaped backslashes in a PDF literal string become control characters, so link_open refused the target and main's Windows CI failed. Taken from #1359's helper fix.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 07:47:35 -04:00
0e55d07b5f M12: say when a form's scripts were turned off (#1251)
* M12: say when a form's scripts were turned off

When an XFA script runs past its time limit it is abandoned and the
form's scripts are turned off for the document. The only trace was an
error line in the JavaScript console, so calculations and buttons just
stopped working.

- Engine: Document::xfa_scripts_off() reports the state (set on open and
  on an edit), so a viewer shows it without reading error text. doc_info
  reports it to agents as xfa_scripts_off.
- App: when it happens (at open or after a click) a toast says so once,
  and the notice bar says so from then on, ahead of its other notices,
  with a JavaScript Console button. A notice bar the user had closed
  comes back for it; closed again, it stays closed.
- The sentence is translated in the 17 catalogs that are complete
  (hashes in ATTRIBUTION.toml updated).

Tests: two kittest UI tests (runaway at open; runaway click after the
bar was closed), which fail without the app change; engine assertions
for both states; a doc_info tool test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Catalogs: the scripts-off notice in Greek, Polish and Telugu; refresh hashes

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 03:42:56 -04:00
7d419a6c73 feat(edit): remove all links command and context menu (#539) (#1213)
* feat(edit): remove all links command and context menu (#539)

- Expose 'edit.remove_links' and 'edit.remove_all_links' commands to remove all links across the document or on specified pages.
- Add right-click context menu options for links: Properties, Delete, and Remove all links in both Link tool and Select tool modes.
- Extend automation tool 'links_remove' with optional 'pages' parameter and removal count.
- Add notification feedback in UI for link deletion.
- Mark 'edit.remove-all-links' as shipped in Parity with evidence tests.
- Add unit and integration tests covering automation, engine, and UI undo/redo.

* Remove all links: one command id, catalog rows, link menu only on a link

- Drop the duplicate edit.remove_all_links command (same label as the
  existing edit.remove_links, so it showed twice); edit.remove_links now
  runs remove_all_links with its toast.
- Add the toast strings (No links to remove, Removed 1 link, Removed {n}
  links) to all 20 catalogs.
- The canvas context menu shows the link items only when the right-click
  hit a link (LinkView::context, set per right-click), so a link left
  selected no longer takes over the comments menu, and the Link tool no
  longer replaces the comments menu away from links. Regression test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Restore the PR's links_remove pages test (lost with the workdir conflict)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 03:31:39 -04:00
06480e395d M3: add PDF tab context menu (fixes #1233) (#1266)
* feat(ui): add PDF tab context menu (#1233)

* Tab menu: reveal through D-Bus off the UI thread

`dbus-send --print-reply --reply-timeout=2000` (and the xdg-open fallback)
could block the frame for 2 s or more; on Linux and FreeBSD the reveal now runs
on a `pdfcraft-reveal` thread and a failure is logged. The existence check stays
on the UI thread and is still reported; Windows and macOS launches are
unchanged. Refreshes the catalog hashes after the main merge.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: srt8hellcat2-ui <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 03:15:02 -04:00
7183bf4264 Comments: "Other colour…" picker and recent custom colours under the swatches (#1193) (#1261)
* Comments: "Other colour…" picker and recent custom colours under the swatches (#1193)

The annotation tools offered only the ten fixed swatches. The shared swatch
grid (comments' quick bar, right-click menu and Comment Properties, and the
other colour choosers that use it) now ends with "Other colour…", a full
colour picker that starts from the current colour; Apply commits it once.
Applied custom colours are kept as a "Recent colours" row (newest first,
five at most, swatches not repeated) for the session.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CVtZHu998NDhf4Z8QvS5pd

* Comments: import kittest's Queryable in the Other colour test (#1193)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CVtZHu998NDhf4Z8QvS5pd

* Comments: "Other colour…" is a "+" swatch so the grid keeps its height (#1193)

A row under every swatch grid made dense dialogs (Redaction Tool
Properties has two grids) taller, pushing their lower controls out of
reach. The picker now opens from a swatch-sized "+" at the end of the
first row; the Recent colours row appears only once a custom colour has
been applied.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CVtZHu998NDhf4Z8QvS5pd

* Comments: each colour grid keeps its own "Other colour…" state

Two swatch grids drawn in one parent (Redaction properties, Prepare a form) shared the
picker's open state, so "+" opened both. The state and the swatch grid are now keyed on
each grid's own place in its parent.

* Comments: test that two colour grids in one parent open separately

* ATTRIBUTION.toml: catalog hashes after merging main

---------

Co-authored-by: ngrafton2006 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 03:11:33 -04:00
4541974ef9 Reading colours: replace document colours for reading comfort (#897) (#1270)
* test: give faux-redaction audit its own workdir (parallel-test flake)

* hayro: ColorReplacement type and luminance mapping (reading colours, #897)

* fmt: engine dates (upstream Greek localization miss)

* hayro: apply ColorReplacement to solid text and line-art paints (#897)

* render: ReadingColors view setting flows into RenderSettings (#897)

* engine: per-document reading colours state (#897)

* automation: view_reading_colors tool; page_render honours it (#897)

* ui: Reading colours preference (Replace Document Colors, #897)

* ui: seed reading colours on revision and signed-version opens (#897)

* parity: a11y.replace-page-colors shipped (#897)

* render: map stencil-mask paints through reading colours (#897)

An ImageMask stencil's solid paint was baked into the raster bytes raw,
bypassing the reading-colours hook every other solid fill goes through:
under white-on-black, CCITT scans, JBIG2 text and stencil logos drew
black ink on the black mapped background and vanished. Route it through
replaced(paint, false) — stencils are line art in disguise — and cover
it with an ImageMask fixture test sampling mapped ink on a mapped
background.

* render: print previews render with reading colours off (#897)

Print previews went through the document's render pool, whose config now
carries the reading colours: a white-on-black user saw black sheets in
the Print dialog while the actual print stayed uncoloured. The request
tag is the per-request channel that already crosses the render boundary,
so PRINT_TAG now lives in pdfcraft_render and the renderer honours it by
rendering previews with reading colours off — a preview's job is
fidelity to the printout. The main view stays recoloured. Covered on
both render paths (inline PageRenderer and pool worker).

* ui: seed reading colours on created-document opens (#897)

open_created (comment summaries) and open_created_bytes (images, text,
blank pages, clipboard) pushed new DocViews without seeding the reading
colours, unlike every other open path. A created document now starts
with the app-wide preference too; the seed-on-open test drives both
paths.

* docs: reading-colours flag echo and display-fidelity parity notes (#897)

The view_reading_colors description now says a flag that differs from
the mode's preset default makes the echoed mode read custom, and the
a11y.replace-page-colors parity notes record that print previews and
view snapshots render with the document's true colours: reading colours
apply to the reading view only.

* Merge follow-ups: reading colours beside preview renders and the new catalogs

- Renderer::new carries both color_replacement and the new preview flag
- our raster tests construct RenderRequest with preview: false
- the preference dialog grew past the window: scroll the combo into view
  and give the fixture a valid xref so no repair banner covers the dialog
- checkboxes use the crate's square-box widget (clippy disallows egui's)
- Kazakh and Turkish catalogs gain the ten reading-colours strings

* Reading colours: record the hayro patch in vendor/README.md; add missing translations

vendor/README.md gains hayro patch (13): `RenderSettings::color_replacement` /
`ColorReplacement`, the `render_pdf` signature change and the removed
`[dev-dependencies.log]`, with its tests. Adds the ten reading-colours labels
to el, es, pl, te and zh-hant.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 02:49:31 -04:00
8cacb44728 M3: follow Windows' Animation effects setting; add an Animation effects preference (#1235)
* M3: follow Windows' Animation effects setting; Smooth scrolling preference

With Settings > Accessibility > Visual effects > Animation effects turned
off, the wheel still eased: egui spreads a coarse wheel step over about a
quarter of a second, for scrolling and for Ctrl-wheel zooming, and has no
switch for it.

- crates/ui-egui/src/motion.rs reads each frame's wheel input the way egui
  does, minus the easing, and hands it to the scroll areas and the views
  (zoom_delta). A test sums what egui eases and compares it with what is
  read at once, and fails if egui stops easing.
- With the system asking for no animations, egui's own animations take no
  time and the progress card's bar steps without its sweep.
- The desktop app asks WinRT's UISettings.AnimationsEnabled at launch and
  every two seconds of use, so a change applies without a restart.
- Preferences has Smooth scrolling: Use system setting (default), On, Off.
  On or Off decides the wheel's scrolling and zooming, and scrolling to a
  place, whatever the system says. Saved, settable as smooth_scrolling on
  the control channel, translated in all 20 catalogs.

How far a notch scrolls or zooms is unchanged. macOS, Linux and the
browser aren't asked for their setting yet; Off works everywhere.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Credit Jeremy Yao by name in About (contributors/people.toml)

My own entry: real name and display name "Jeremy Yao".

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M3: rename Smooth scrolling to Animation effects; it decides every animation

The preference also governs Ctrl-wheel zooming, so "Smooth scrolling"
described only part of it. As "Animation effects" (Use system setting, On,
Off) it now decides every animation in the window, the way the Windows
setting of the same name does:

- the wheel's scrolling and zooming, and scrolling to a place (as before);
- egui's own animations, the progress card's sweep, and the chrome's hover
  and press washes (new on main, with durations of their own), which used
  to follow the system alone or not at all.

Saved and set on the control channel as animation_effects (system, on,
off); the unreleased smooth_scrolling name is gone. The three labels are
retranslated in all 20 catalogs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Tests: the app's two real-GPU tests take turns (FreeBSD SIGSEGV)

FreeBSD CI died with SIGSEGV in the pdfcraft binary's tests on this branch
(d4b928f1). The log shows a_gpu_error_while_a_window_is_set_up... (started
first, still running) and pipeline_creation_error... (started shortly
before) both in flight when the process died: two test threads inside the
graphics libraries at once. Nothing else still running was native code.

Both now take one lock for the whole test, as the UI tests already do for
the same reason (crates/ui-egui/tests/view.rs). Each takes about two
seconds with a device, which is why they can overlap.

The FreeBSD job has been failing at the same step with the same exit code
on main (84a75eae, 4a1e205f, 3d2bdfe7: every completed run since #1042
added the second real-GPU test) and on unrelated PRs. Their logs weren't
available to confirm it is the same crash.

Not reproduced locally (no FreeBSD machine here); the next FreeBSD run is
the check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 02:46:40 -04:00
c1d98946a9 M7: open local GoToR PDF links natively (#1265)
* M7: open local GoToR PDF links natively

* GoToR links: read only regular .pdf files under a size cap; add missing translations

A GoToR link in an untrusted PDF could name /dev/zero (unbounded read), a FIFO
(UI thread hangs) or a directory. Both the UI path and the `link_open` tool now
go through `pdfcraft_engine::read_remote_document`, which requires a `.pdf`
extension and a regular file (metadata checked before opening) of at most
1 GiB, and stops reading if the file grows past the cap. Regression tests cover
directories, a /dev/zero symlink and a FIFO (unix) in the engine, the tool and
the UI.

Adds the four link notifications to all 20 catalogs and "The existing action
will be preserved." to el, es, pl, te and zh-hant.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 02:34:19 -04:00
2aea7dcfeb M10: print from the web build through the browser's print dialog (gap 4) (#1243)
* Gates: fix two clippy failures on main

- ocr_ui: "Also recognize pages that already have text" used egui's own
  checkbox, which clippy.toml disallows (#1184 landed beside #1173);
  use widgets::checkbox like every other box.
- camera_edges: Rust 1.97's manual_range_contains; the rewrite keeps the
  old answer for a NaN angle (false) with an is_finite check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019ojKsSCHWk2AuyDTFMHCd7

* M10: print from the web build through the browser's print dialog (gap 4)

The web build had no printer: spool::submit refuses on wasm, so Print
could only download the print-ready PDF. Now its Print dialog offers
"Browser's print dialog" (the default there), which reaches every
printer the operating system knows.

- web_print: PdfCraft draws the print-ready PDF's sheets itself
  (engine Exporter), one sheet a frame since wasm has no threads:
  300 dpi up to 100 sheets, 200 up to 300, 150 up to 1,000, at most
  64 MP a sheet and 1 GiB of PNGs a job. The PNGs become blob URLs
  and print from a hidden srcdoc iframe, one sheet per page, margin 0.
- Chromium prints every page at the default @page size, so a sheet of
  the paper's size turned sideways is rotated 90 degrees CCW onto it
  (as drivers do); other sizes get named @page rules.
- Copies, two-sided and colour are greyed out (the browser's dialog
  sets them). If print() is refused (e.g. a sandboxed frame), the
  print-ready PDF downloads instead, with the reason.
- pdfcraft-print stays free of web APIs; six new strings in all 20
  catalogs (ATTRIBUTION hashes refreshed); docs and parity updated,
  gap 4 closed.

Verified in headless Chromium against a trunk build: three sheets at
300 dpi, the landscape sheet turned, no blank trailing page, and the
download fallback. Firefox and Safari not yet tried.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019ojKsSCHWk2AuyDTFMHCd7

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 02:31:36 -04:00
Earl CoandBrandon Thomas b1784fc2d9 Accessibility: add visual tag properties panel (#1216)
* Expose tag parent and depth for hierarchy view

* Accessibility: add visual tag properties panel

* Show collapsible tag hierarchy in accessibility panel

* Open checker findings in the matching structure tag

* Translate Tags panel across supported languages

* Tags panel: walk the structure tree once per edit, not every frame

The Tags panel and checker findings that open a tag share a tag list cached per
document and edit generation.

---------

Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 02:28:56 -04:00
bde9dc52fe honour the /Lab colour space /WhitePoint (#1203) (#1237)
* fix: recognize paragraph drafts as unsaved work (Issue #811)

* fix(color): honour the /Lab colour space /WhitePoint (#1203)

The old path fed every Lab value to the bundled ICC transform as if the
space's white point were D50, so a /Lab space with any other /WhitePoint
(e.g. D65 [0.9505 1 1.089]) rendered off by several levels per channel
and a D65 blue came out 45 levels too red.

Map L*a*b* to XYZ in the space's own white coordinates (ISO 32000-2
§8.6.5.4), Bradford-adapt it to D65 and convert to sRGB directly, as
Poppler does. Missing or degenerate white points fall back to a flat
white and hostile components are clamped without panicking.

Add a render regression that checks the five reported flat patches of
the D65 white-point fixture against their CIE sRGB values.

* Lab: map normalised Indexed palette components onto L*/Range; drop unused LAB.icc

An /Indexed palette over Lab passes its entries as byte/255 (manual_scale),
which the new Lab conversion read as raw L*a*b* and drew near black. Map
them onto L* 0..100 and the a*/b* /Range, with a regression test. Record the
patch as hayro-interpret (25) in vendor/README.md, and remove LAB.icc (no
longer referenced) with its ATTRIBUTION, NOTICE and assets README entries.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Main gates: rustfmt tests/viewer.rs; a type alias for the refresh-steps array (clippy type_complexity)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-12 02:08:21 -04:00
5a91d1b7d0 fix(i18n): refine Greek translations and May abbreviation (#1262)
Co-authored-by: George Athenakis <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 01:35:56 -04:00
cyfung1031andBrandon Thomas 5cccbd6ba5 M7: report unsupported link actions without a JavaScript claim (#1242)
Co-authored-by: Brandon Thomas <[email protected]>
2026-10-12 01:31:50 -04:00
84a75eae1b M10: Presentation (.pptx) export (#1137) (#1162)
* feat: presentation (.pptx) export (#1137)

- Implement OpenXML PresentationML (.pptx) generator in crates/export/src/pptx.rs:
  - Generate [Content_Types].xml, _rels/.rels, core properties, presentation.xml
  - Build slide master, blank slide layout, and DrawingML theme
  - Render slides per page: largest text block as title shape, remaining paragraphs as body text box with font size, bold, italic, and color formatting
  - Embed images with proper DrawingML picture shapes and relationships
  - Generate DrawingML table shapes with column grid and span merging
- Wire OfficeFormat::Pptx into crates/engine/src/compare.rs and export_office
- Register export.pptx command and mark Presentation (.pptx) Ready in catalog
- Support pptx in automation doc_export_office tool
- Wire export.pptx into UI commands and file save dialog
- Add unit and integration tests across crates/export, engine, automation, and ui-egui
- Update create.export-pptx parity feature evidence and docs

* pptx export: saturating slide positions, matching cell-border tags, theme ea/cs fonts, package test

- Heights and the running vertical position use clamped/saturating arithmetic, so a hostile
  text size or image rectangle (1e300, NaN, inf) can't overflow i64; the item loop uses get().
- The table cells' a:lnR and a:lnT borders were closed with </a:lnL>, which made every slide
  with a table malformed XML; they now close with their own tags.
- The theme's major/minor fonts carry the schema-required a:ea and a:cs children.
- New test walks the produced zip entry by entry (sizes, inflation, central directory), checks
  every XML part's tags balance, the required parts, content-type overrides, relationship
  targets and that no placeholders are used; a hostile-sizes test covers the overflow.
- Test literals gain main's Block/Image rotation and size fields.
- Parity notes say what is still unverified.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: KrzysztofC <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-11 21:55:25 -04:00
a8940f0003 M10: Create separate PDFs from multiple files (#1183)
* M10: Create separate PDFs from multiple files

* Separate PDFs: translatable notices

"Left out: {names}" and the per-file reasons (already a PDF, no unused output name, no
output folder was chosen) go through tl!; the browser's download notice is a plural
("Downloaded {n} PDF" / "Downloaded {n} PDFs") via trn. Rows in all 20 catalogs, with
each language's plural forms; catalog hashes refreshed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-11 21:20:13 -04:00