2,330-entry clean-room catalog, Albanian month and weekday names for date
formats, Preferences entry, locale matching for sq-*, coverage tests, font
coverage test, attribution entry and docs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Validate serialized PDF bytes and page count before replacing the destination in desktop and automation saves. Reject unreadable or page-losing output before file replacement and saved-state updates.
Keep staging writes synced before publication and retain the regression for a repaired page tree that cannot survive a full rewrite.
Fixes#1698
Carry the watched path and epoch through queued file reads, and reject stale results before changing the document. Preserve newer saved contents and undo history across Save and Save As while retaining ordinary external reloads.
Fixes#1802
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sync staged output after writing all bytes and before replacing its destination, closing the handle before rename. Return sync failures while keeping the old destination and ordinary save state available for retry.
Fixes#1804
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#1537Closes#1542
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Rebasing onto newer main replayed ATTRIBUTION.toml with the hashes of the
older base, so every catalog entry failed the xtask manifest check.
Restore main's entries for the catalogs this branch does not touch and
recompute ours (ru and the four new catalogs) from the files as committed.
Correct 28 context-sensitive Korean labels in the incoming translation, distinguish redaction marking, clearing, and permanent removal, and add a regression test. Document code evidence and outstanding runtime QA.
The rebase onto main resolved the theme.rs and system_fonts.rs conflicts
in favour of the new Hangul code and deleted upstream's Telugu fallback
(SYSTEM_TELUGU_FALLBACK, telugu_fallback, telugu_candidates,
script_fallback and their tests), while tests/fonts.rs kept the Telugu
tests, so the build failed. Keep both: Telugu restored as it is on main,
Hangul added alongside it through the same script_fallback helper.
Four new complete catalogs (2554 entries each, full coverage of the
uk.tsv key set including the 12 strings that reached main during the
rebase): Hindi (hi), Indonesian (id), Korean (ko), Vietnamese (vi).
All four use a single plural form per CLDR. Hindi uses the Devanagari
font support already in the fonts crate.
Registered in LANGUAGES (22 → 26) with locale-tag and catalog tests.
Also fixes 19 duplicate keys in ru.tsv.
Tests: 114 passed, 0 failed.
Two complete catalogs, hi and ko, 2,578 entries each (every
(context, English) key the bundled catalogs hold), registered in
LANGUAGES (24 -> 26) and in the date languages (hi, ko). Both use a
single @plural form.
Korean has no Hangul face in craft-fonts, so desktop builds draw it
with one installed face (Malgun Gothic, Apple SD Gothic Neo, Nanum;
never Noto CJK / Source Han), added after the embedded faces like the
Devanagari fallback. PDFCRAFT_SYSTEM_FONTS=0 turns it off.
Tests: catalog registration and persistence, command / All tools /
tl!() literal coverage for both, date names, Hangul fallback and
candidate list. cargo xtask assets, fmt and clippy are clean. Also
refreshes the ru.tsv SHA-256 in ATTRIBUTION.toml and the docs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Two new complete catalogs (2542 entries each, full coverage of the
uk.tsv key set): Indonesian (id) and Vietnamese (vi), both with a
single plural form per CLDR. Registered in LANGUAGES (22 → 24) with
locale-tag tests. Also adds Devanagari font support (for the upcoming
Hindi catalog) to the fonts crate, and fixes 19 duplicate keys in
ru.tsv that crept in during a previous merge.
Tests: 110 passed, 0 failed.
Adopt the canonical craft-print revision from craft-libs #28 so Windows printing uses the in-process Windows.Data.Pdf/XPS backend instead of PowerShell. Keep exact paper, orientation and scaling validation and the existing PDFCraft composition and permission checks.
Pin the reviewed winprint fork, scope its compile-time licence/advisory exceptions, and distinguish the earlier WDAC Sandbox exercise from the final pinned revision's Windows CI coverage. Physical printer output remains unverified.
Add worker-barrier regressions for Home-selected tool handoff and startup document ordering.
Fixes#1107
Co-authored-by: durtyrobbin <76844303+durtyrobbin@users.noreply.github.com>
Co-authored-by: Dimitar Klaturov <7221384+bulgariamitko@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
cargo xtask assets checks each catalog's sha256; the rows this branch adds
changed the files without updating it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carry selected tool actions through native file preparation, password retries and conversion, and defer startup intake until pending native opens finish.
Await observable completion in native opening, password, recovery and linked-PDF tests while retaining their content, identity, dirty-state, cancellation and destination checks. Verify the expected validation error and preserve the original document after refused consent targets.
M3; refs #1914.
pdfcraft_organize::add_attachment puts a file in /Names /EmbeddedFiles as a
Filespec with a Flate-compressed EmbeddedFile stream (/Subtype, /Size,
/ModDate), the name made unique; delete_attachment removes one, the last taking
the tree with it. Edit::AddAttachment / Edit::DeleteAttachment (undoable),
Edit ▸ Attach a file… with a button at the top of the Attachments panel and a
trash button on each document-level row, tools attachment_add and
attachment_delete, rows in every language, parity attachments-add and
attachments-delete shipped.
Fixes set_attachments (Combine): a duplicate name was numbered by appending
ASCII bytes, corrupting UTF-16 keys; names are now numbered as text. An
indirect /Names is updated in place instead of replaced, so its /Dests and
/JavaScript stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Encode tabs and line feeds as character references so XML normalization cannot change field names, comment identities or metadata. Add failing-before-fix saved-PDF round-trip regressions.
Co-Authored-By: Codex <noreply@openai.com>
Read the effective rotation through its PDF integer object so inherited and
page-local indirect values retain their orientation when rotated.
Co-Authored-By: Codex <noreply@openai.com>
The "Dialogs, panels, menus" paragraph appears twice, the same artifact
as the Auto paragraph in #1525. Two language additions each edited it
and both versions were kept: one ends "Czech and Turkish cover the menus
so far", the other lists Slovenian among the translated languages and
says only Czech covers the menus.
Neither list is current. Turkish has had a complete catalog since #1057,
and five of the shipped languages are in neither copy: Kazakh, Polish,
Greek, Turkish and Lithuanian, all with full catalogs per
docs/localization-parity.md. Czech is still menus only (135 of the
2,143-key union there).
So keep one paragraph, and stop enumerating the languages in it: that
list is what both additions edited at once, and the paragraph already
points at localization-parity.md, which carries the per-language counts
and coverage. The rest of the paragraph is unchanged.
Docs only, no code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of #1609: the Edit-triggered recognition also read pages that have real
text (a flyer or slide export with a full-page background picture), adding an
undo step and marking the file modified just because Edit was turned on.
It now runs with skip_text_pages on, so such a page is left alone. A new
OcrSettings::native_text_only decides what counts as the page's text: its own
content and form fields, not comments and text boxes (which are also not drawn
into the picture that is read), so a scan with a few comments on it is still a
scan, as in Acrobat. The doc comment now says what the code does.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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 <11478682+bmh129@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
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 <echelon@gmail.com>
Enables visible signature appearances in pdfcraft_sign to render
Unicode characters (specifically Latin Extended-A / Central European
characters such as č, ć, ž, š, đ) by embedding the bundled
Inter-Regular.ttf font with /Identity-H encoding and a /ToUnicode CMap
when the signer name, labels, reason, or location contain non-WinAnsi
text.
When all text is within WinAnsiEncoding, the existing zero-byte overhead
behavior (using standard /Helv) is preserved.
Closes#2207
* fix(text): read a TrueType font without an encoding as WinAnsi (fixes#1468)
* fix(text): describe the WinAnsi reading by pdftotext's output
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A stamp placed on a page with /Rotate was drawn in user space, so the
label or picture turned with the page. Counterrotate the appearance so
it reads upright as the page is displayed.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Create PDF detects BigTIFF (II+/MM+) as well as classic TIFF; the tiff
decoder already reads it, so these scans now convert instead of being
refused.
- Image detection accepts only GIF87a/GIF89a, the headers conversion
decodes, so a malformed GIF header is no longer called an image and then
rejected with a misleading error.
- file-format-parity.md: XDP split into XFA data import/export (shipped
under form.xfa-data) and opening .xdp as a document (none).
Tests: bigtiff_images_become_pages; malformed GIF headers in
source_kind_tells_pdfs_images_and_text_apart. Both fail without the fix.
Claude-Session: https://claude.ai/code/session_01F8DFYWkDyTnEcin9PNkUW5
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Flattening deleted a cloudy comment it could not draw, and a refused text-box restyle or resize left the new properties in the file. Arrow defaults were stored on plain lines. A custom match ending on a separator included the next glyph, and the vertical-text redaction error claimed an overlap it had not checked.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The declaration line was built from the PDF/A identification alone, so a
document that declares only PDF/UA-1 read "Declared conformance: none".
It now lists PDF/UA next to PDF/A, and still says none when neither is
declared. pdfa_dialog_shows_a_declared_pdfua fails before the change.