mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 01:57:12 -06:00
main
88
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c4a6dd6be3 |
Assets: don't hash translation catalogs in ATTRIBUTION.toml (#4284)
* Assets: translation catalogs aren't hashed The 33 UI translation catalogs are project files under the project licence, and git history records their provenance. Their ATTRIBUTION.toml entries keep title, author, source and licence but carry no sha256, so a PR that changes UI text no longer has to edit ATTRIBUTION.toml. cargo xtask assets accepts a kind = "translation" entry for a .tsv under crates/ui-egui/src/i18n/ without a hash, rejects one that has a hash, and still requires a hash for every other entry. * Assets: a leftover hash on a translation catalog is a note, not an error |
||
|
|
cf0f80604f |
Allow OFL typefaces, Noto CJK and Source Han included (AGENTS.md §1.1) (#3908)
* Allow OFL typefaces whoever designed them (AGENTS.md §1.1) Noto CJK and Source Han from their open-source releases are allowed like any other OFL font. The fonts build no longer skips them, the asset check accepts an OFL font credited to Adobe, and the docs describe the Chinese faces that result. The ban on Adobe icons, artwork, UI graphics and the fonts Adobe products install stays. * Leave out whole CJK faces other than the Japanese ones for size A Chinese or Korean face is embedded whole only under 4 MiB, so the 16.4 MB Noto Sans CJK SC in craft-fonts stays out of the binary and the embedded total against the pinned craft-fonts is unchanged (25,204,828 bytes). * docs: replacement boxes for Chinese without craft-fonts only on the web or without an installed Chinese face * xtask assets: the OFL font exemption needs at least one licence ID * fonts build: count a CJK script tag with a region (Hant-HK) as CJK, shared with the embedding test * AGENTS.md: the asset gate exempts OFL fonts from the Adobe-author check * fonts build: count a Hangul-only face (Hang) as CJK for the size rule * fonts: the CJK embedding test checks the size rule, sharing its constants with build.rs * docs: the missing Hans face is a property of the pinned craft-fonts, not of every build |
||
|
|
7cdde6c91c | chore(assets): record the changed translation catalogues in ATTRIBUTION.toml | ||
|
|
9526363152 |
Build each crate's integration tests as one binary (#4087)
* Build each crate's integration tests as one binary Every tests/*.rs file is its own executable that links the whole crate graph. Move each crate's test files into tests/it/ as modules of one tests/it/main.rs, for apps/pdfcraft, apps/pdfcraft-cli, automation, cos, crypt, engine, render, sign and ui-egui. ui-egui's ui_group binary and the support modules move into tests/it/ too. Crates with a single test file keep it. The test executables go from 150 to 52, and every crate runs the same number of tests as before. ui-egui perf keeps its own binary (own process): its frame-time budgets would charge each measurement for the other tests running beside it. What the move needs: - include_str!/include_bytes! and #[path] paths gain one "../". - ui-egui: one GPU lock in tests/it/main.rs replaces the five per-file ones (content, document_links, editing, fill_sign, view), so one process creates one wgpu device at a time. The drop-line test in ui.rs and the screenshot blocks behind PDFCRAFT_*_SHOT(S) and CRAFT_UI_DOCKING_DIR, which render without holding it, take it too. - pdfcraft winit_wayland_scroll: the vendored winit source it compiles names crate::event, so that shim lives in main.rs. - parity evidence names the new paths. * Name the one-binary test paths in docs and comments - READMEs, docs, ATTRIBUTION and source comments name tests/it/<file>.rs. - The oracle README's example filters with --test it encryption::qpdf_agrees. - pdfcraft's GPU lock comment points at the UI tests' shared lock in crates/ui-egui/tests/it/main.rs. |
||
|
|
e85fca66e5 |
Integration: text field table labels in the batch's catalogues (upstream #3479 × r2 catalogues)
Albanian, Catalan, Finnish, Hindi, Korean, Indonesian and Romanian get all 22 text-field-table strings; Brazilian Portuguese gets the 4 limit messages its full-union test requires. AI-assisted, marked in ATTRIBUTION.toml. |
||
|
|
472717fcd7 |
Merge upstream main 88dad98dd into next
# Conflicts: # ATTRIBUTION.md # ATTRIBUTION.toml # crates/engine/src/dates.rs # crates/ui-egui/src/i18n/ar.tsv # crates/ui-egui/src/i18n/bg.tsv # crates/ui-egui/src/i18n/cs.tsv # crates/ui-egui/src/i18n/de.tsv # crates/ui-egui/src/i18n/el.tsv # crates/ui-egui/src/i18n/es.tsv # crates/ui-egui/src/i18n/fr.tsv # crates/ui-egui/src/i18n/hu.tsv # crates/ui-egui/src/i18n/it.tsv # crates/ui-egui/src/i18n/ja.tsv # crates/ui-egui/src/i18n/kk.tsv # crates/ui-egui/src/i18n/lt.tsv # crates/ui-egui/src/i18n/mod.rs # crates/ui-egui/src/i18n/pl.tsv # crates/ui-egui/src/i18n/pt-br.tsv # crates/ui-egui/src/i18n/ru.tsv # crates/ui-egui/src/i18n/sl.tsv # crates/ui-egui/src/i18n/te.tsv # crates/ui-egui/src/i18n/tr.tsv # crates/ui-egui/src/i18n/uk.tsv # crates/ui-egui/src/i18n/vi.tsv # crates/ui-egui/src/i18n/zh-hans.tsv # crates/ui-egui/src/i18n/zh-hant.tsv # crates/ui-egui/tests/fonts.rs # docs/localization-parity.md # docs/localization.md # docs/roadmap.md # parity/acrobat-features.toml |
||
|
|
4bd08a759b | Integration: Albanian signature labels all translated (#3101 × r2 catalogues) | ||
|
|
d9306ab2c7 |
Integration: catalogues cover the r2 batch's new literals
English fill (the "Untranslated" section of each catalogue), for keys the
coverage tests only need present:
- ca, fi, ro, pt-br: keys other catalogues gained (print, thumbnails,
layers, signature checks, Reshape comment, Remove {standard}
identification)
- sq: UI literals and the visible-signature labels
- hi, ko, id: calculation-order, rich-text and print/thumbnail literals
- pl: signature-check wordings
AI-assisted translation (noted in ATTRIBUTION.toml):
- cs: the four new signature-check wordings and Remove {standard}
identification, since Czech asserts every entry is translated
Lao's file.word_count command id said its own id; it reads as the
plain "Word count…" translation.
|
||
|
|
c0c8dc573f | Integration: ATTRIBUTION hashes | ||
|
|
8bb2eef740 |
Merge r2 track t13: cos-parse, vendored-crate fixes
# Conflicts: # ATTRIBUTION.md # crates/annot/src/tests.rs # crates/forms/src/tests.rs # crates/organize/src/tests.rs |
||
|
|
88dad98dd4 |
M14: add Norwegian Bokmål interface and date language (#1836)
* M14: add Norwegian Bokmål interface and date language A 2,592-entry `nb.tsv` covering every key the other bundled catalogs have, written from the meaning of the English labels (clean-room); terminology reviewed by a native-speaking contributor. Registers `nb` with the English one/other plural rule, adds Norwegian month and weekday names for Fill & Sign dates, resolves `no` and `nn` locales to Bokmål until a Nynorsk catalog exists, attributes the catalog and records it in parity. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * M14: translate the strings main added since the Norwegian catalog was written 69 keys (signature appearance labels, word count, full-screen prompt, print as image, image crop, redaction mark appearance, …) and the attribution hash. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * M14: translate the picture-menu and label-paper strings in the Norwegian catalog Seven keys main added (Cut/Redact Image, Create Link…, Recognize Text…, Turn upside down, the 4 x 6 in label paper) and the attribution hash. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * M14: translate the page-group folding commands in the Norwegian catalog Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * M14: drop the superseded localization summaries left beside the current ones Concurrent merges on main left older copies of several localization summaries next to their replacements. Review of #1836 flagged them: - localization-parity.md: remove the 2026-10-13 status line and the 14-language summary that still listed Norwegian as missing; the current summary keeps the Italian (and now Norwegian Bokmål) measurement dates. - localization.md: keep one catalog-coverage paragraph (now naming Norwegian Bokmål, up to about 2,700 entries) and one font paragraph. The font paragraph folds in what only the older copies said: the installed Chinese face fallback (#826) and Slovenian's Latin faces and four plural forms. - roadmap.md: end the M14 Missing list before the Fontconfig note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com> Co-authored-by: aiongg <aiongtaigi@gmail.com> |
||
|
|
86739c11ae | Integration: ATTRIBUTION hashes | ||
|
|
a52b8d9f4a |
Merge PR #3498: i18n: add Central Kurdish (Sorani) as an interface language
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: arrays sized 30. ATTRIBUTION.md: both sides' rows; count regenerated after all merges. docs/localization.md: main's text with the ckb name, code and catalog-list entry added. |
||
|
|
8f47b6ffe8 |
Merge PR #2714: i18n: add Lao (lo) translation support
crates/ui-egui/src/i18n/mod.rs: both sides' language rows, array sized 29. ATTRIBUTION.md: both sides' rows; count regenerated after all merges. |
||
|
|
bf3a199fbb |
Merge PR #2505: G14: Romanian (ro) interface catalog
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 28. crates/ui-egui/tests/fonts.rs: both catalog lists. ATTRIBUTION.md: both sides' rows; count regenerated after all merges. ROADMAP.md: main's summary with Romanian added (15 more catalogs); both log lines. docs/localization-parity.md: main's rows plus the ro row; 28 interface languages; both revision rows. docs/localization.md: main's text with Română, the ro locales and code, and the PR's catalog-list edits. |
||
|
|
13b21c7f64 |
Merge PR #2227: M14: add Catalan interface and date localization
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 27. crates/ui-egui/tests/fonts.rs: both catalog lists; Catalan added to main's comment. ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges. ROADMAP.md: both log lines. docs/localization-parity.md: main's rows plus the ca row; 27 interface languages; both revision rows. docs/localization.md: main's text with Català, the ca locales, code and catalog paragraph added. |
||
|
|
f0f4aeeb19 |
Merge PR #2079: i18n: add Albanian (sq) interface language
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows (vi, fi, sq), arrays sized 25. crates/ui-egui/tests/fonts.rs: both catalog lists; Albanian added to main's comment. ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges. ROADMAP.md: both log lines. docs/localization-parity.md: main's rows plus the sq row; 25 interface languages; both revision rows. docs/localization.md: main's text with Shqip, the sq locales and code added. |
||
|
|
b7999e8766 |
Merge PR #3603: M14: Spanish catalog covers the bundled key union
ATTRIBUTION.toml: kept main's sha; rehashed after all merges. ROADMAP.md: summary names Vietnamese, Brazilian Portuguese and Spanish against the current union; both log lines. docs/localization-parity.md: main's status line with the Spanish change added; the PR's denominator paragraph naming Brazilian Portuguese too; the PR's lt row plus main's fi row; revision rows newest first. |
||
|
|
84e091d9b3 |
Merge PR #3195: Vendor and patch hayro-cmap for tolerant CMap parsing
ATTRIBUTION.md: generated header count set to 277 (main's 276 plus the CMap blob moved from bundled to asset). |
||
|
|
9f44f24448 |
Merge PR #2210: Add Finnish interface and date language
ATTRIBUTION.md: kept main's asset count; regenerated after all merges. |
||
|
|
6dec57bf80 |
M7: lay out Format text as Acrobat's panel (#2249)
* M7: lay out Format text as Acrobat's panel Format text was a row of separate combos, a Load font button, an Installed fonts combo, a swatch grid and labelled drag values. It now follows Acrobat's arrangement: the font across the panel (the page's fonts, the standard families, Load font… and Installed fonts in one list, showing the paragraph's own font by name); size and a colour swatch, then lists and an alignment flyout; bold, italic, underline, superscript and subscript; line and paragraph spacing; horizontal scale and character spacing. One panel serves an edited paragraph and added text. What PdfCraft can't apply yet (lists, superscript and subscript, paragraph spacing, spacing for added text) is shown disabled and says it is planned. Icons stay Lucide's: superscript, subscript, pilcrow and move-horizontal are added from lucide-static 1.49.0 with attribution. Eight new strings are translated in every catalogue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M7: parity notes for the Format text panel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n(vi): Superscript and Subscript for Format text The Vietnamese catalogue came from main without the Format text panel's Superscript and Subscript labels; vietnamese_covers_ui_literals lists them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M7: Format text takes any horizontal scale and character spacing again Review of #2249: the panel's preset lists narrowed what could be set to 50-200 % and -1..5 pt, where the old fields took 10-400 % and -5..50 pt. Each now has a number field over the old range again, with the list of common values beside it. Test: the_format_panel_takes_any_scale_and_character_spacing types values between the listed ones and beyond them, saves and reopens, and compares the paragraph with the engine's own rewrite. It fails without the field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M7: Format text's rows line their controls up and fit the panel Raised in #3037: the size list sat lower than the colour swatch and the alignment button beside it. egui lays a list out from the default control height and grows it downward, so a row's shorter controls ended up off its middle. Each row is now one control height and centres what it holds, at a list's own height. The spacing rows' columns are measured from what they hold, so the second list no longer runs past the panel; the number fields' lists open from a small arrow, which leaves the field the room its value needs. Test: format_text_rows_line_up_and_fit_the_panel checks the size list is centred with the swatch and alignment button and as tall as the font list, that each spacing row shares one line, and that nothing passes the panel. It fails without the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Format text: choosing the family a font name reads as still changes the font CodeRabbit on #2249: a Calibri paragraph is shown as Helvetica, so picking Helvetica left look.family equal to look0.family and no family change was sent. The pick is now tracked (LineEditor::choose_family) and counts as a change in style(), font_shown(), has_changes() and the editor preview; Keep source fonts and an applied edit clear it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Format text: the reusable text style follows a selection only when its edit was applied A refused UpdateContent used to leave the new style in app.text_style, so the next added text inherited formatting the selected text never received. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jk4iYSh3mEd145niec77LF --------- Co-authored-by: abc2cb <271337822+abc2cb@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0a78f31fec |
Add Finnish interface and date language
Adds a Finnish (Suomi, `fi`) catalog with 2,592 entries: every key of the other bundled catalogs, written in sentence case from the English labels. Finnish uses the one/other plural rule with the partitive singular after any number but 1 (1 sivu, 2 sivua, 0 sivua). Finnish is also a date language: calendar headings use the nominative month (Lokakuu 2026) and dates the partitive (14. lokakuuta 2026). Any fi-* locale selects it. Tests cover registration, locale tags, persistence, plurals, calendar wording, commands/tools, scanned UI literals, every bundled catalog key and the Latin UI fonts. The catalog has its ATTRIBUTION.toml entry (ATTRIBUTION.md regenerated with `cargo xtask assets --write`). Clean-room, AI-assisted (Claude Code) translation; not yet proofread line by line by a native speaker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5fc66c4595 |
M14: refresh the Spanish catalog's ATTRIBUTION entry and date the parity figures
`cargo xtask assets` pins a SHA-256 for every catalog, so editing `es.tsv` left its ATTRIBUTION.toml entry stale and `assets::tests::the_real_manifest_passes` failed on both runners. Re-verified the file's provenance, updated the hash, recorded that the coverage additions are AI-assisted and not separately native-reviewed -- as #2510 did for `lt` and `vi` -- and regenerated ATTRIBUTION.md. Review notes on docs/localization-parity.md: - Lithuanian's `2,585 entries` was measured on 2026-10-13 and read as if `lt` were behind. Re-measured today it covers the whole 2,674-key union, so it is restated like `es` and `vi`. - *How this was measured* now says outright that the union is a moving target: every change that adds a string widens it, a count only compares with others of the same date, and a catalog at 100% falls behind without losing a translation. Earlier revisions quote the union of their own date, not this one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
14fcc82a24 |
i18n: add Central Kurdish (Sorani) as an interface language
2,670 entries in ckb.tsv, written in logical order, with the same (context, source) key set as ar.tsv. Registered with rtl: true and plural_one_other (CLDR ckb: one, other); Sorani keeps the noun singular after a numeral, so the 13 @plural rows usually read the same in both forms. RTL reuses the Arabic path: the catalog is put into display order by bidi::display_rtl when it loads, so Kurdish has the same four limits docs/localization.md lists for Arabic. No new layout or bidi code. DATE_LANGUAGES gains Kurdish month and weekday names, because date_languages_are_the_interface_languages keeps both registries in step. ku-* locales are not mapped to ckb on purpose: ku is the macrolanguage and means Kurmanji in Latin script. Only ckb* tags resolve. |
||
|
|
a30a0ab713 |
G14: Romanian (ro) interface catalog
Add a clean-room Romanian translation of the UI catalog (2,670 entries, every bundled key), the CLDR plural rule for Romanian (one / few / other, "20 de pagini"), the ATTRIBUTION.toml entry, coverage and plural tests, and the localization docs and roadmap entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JadgGo4G2GHaAMNb3qyygk |
||
|
|
fb1f72e9ed |
Attribute the vendored cmaps.brotli as a repository asset
With hayro-cmap vendored, its Adobe-authored CMap data no longer arrives through a crates.io dependency: record it as an [[asset]] (same SHA-256, licence_file at vendor/hayro-cmap/assets/LICENSE.txt) instead of the [[bundled]] entry, and add the vendored path to the closed first-party list in xtask so cargo xtask assets passes. Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com> |
||
|
|
5fdd820bdb |
fix(ui): validated signature display, snapshot tab reuse (#1895)
* fix(ui): validated signature display, snapshot tab reuse Signature widgets with layered appearances baked the unverified state into the page, so a valid signature kept reading 'Signature Not Verified' while the banner and panel said valid. Validated verdicts are now drawn into the icon and caption layers in the display copy only: the signed bytes are untouched, saving keeps the original appearance, and files without those layers or without a valid verdict render exactly as before. Other valid signatures still get the verdict mark over their widget, sharing one style with the Signatures panel, and hide it again once the appearance itself shows the verdict. View signed version and View revision opened a new tab on every click, and every tab showed the document title, so repeats piled up identical tabs. Repeats now reuse the snapshot tab, snapshot tabs read like their source plus the suffix in the tab strip, window title and Combine picker, and carry a notice bar naming the revision with a Close that asks about saving first. Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * Address review comments on validated appearances and trust refresh Count every field when checking shared appearance streams, so a caption shared with an unvalidated field is never rewritten for the valid one alone. Pick the icon slot by drawing instead of length, and only when a single layer qualifies. Bare rectangles never count as the icon, and ambiguous or missing slots fall back to the overlay badge. Revalidate and invalidate together through one helper, so trusting a certificate always drops rendered pages. Trust alone still marks nothing dirty. Tests: mixed-verdict sharing, longer decorative borders, competing and doubled layers, capture-before preservation, trust invalidation without zooming, and signed Session round trips with rendering, saving and passwords. Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * Prove the icon slot geometrically and cover real layered signatures A lone line drawing is still not the icon: frames and borders span the box while the status mark lives in a corner of it. The icon now has to sit strictly inside the appearance frame and cover at most half of it; anything else falls back to the overlay badge. Widgets without a measurable frame veto the rewrite the same way. Tests: parsed extents and inhabitant checks as units, both reported border repros plus a frameless widget, and real layered signatures built with cms::sign_detached through Session open, trust, rendering, save and reopen, plain and password-protected. Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * Identify the icon by shape instead of geometry A lone line drawing is still not the icon, but size and position were the wrong signals: the real status mark is large, and geometry blocked it. Frames, borders and panels are rectangles by nature while the mark is not, so a drawing made only of axis-aligned rectangles is decoration however it is stroked. Singularity, shared-stream and template checks are unchanged; anything ambiguous still falls back to the overlay badge. Tests: rectangularity as a unit, nested line-drawn frames, and a triangular fixture mark in place of the rectangular one. Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * Budget the appearance walk and keep the marks with the bytes The walk that finds a signature's icon and caption capped depth, layers and fields, but not the work: a dictionary whose entries all point at the next one is visited once per entry per level, so a small hostile file never finishes opening. The walk now counts its visits and gives up, and a field whose tree cannot be read to the end stops the whole pass, because a layer we never read may be the one two fields share and a rewritten shared layer puts one field's verdict on another field's page. The sharing count is exact again with that: two widgets of one field pointing at the same appearance claim it once, or the field vetoes its own rewrite. A mark used to outlive the bytes it described. A failed display write left the page showing the file's own caption while the verdict still claimed its state was drawn; a metadata edit dropped the mark while the page kept drawing the state, so the badge landed on top of it. The write fallback now drops the marks and logs the loss, and metadata carries them over. Tests: a self-referential appearance tree, which never finished before the budget, two widgets sharing one appearance, a metadata edit that keeps its mark, a save that goes through the writer instead of short-circuiting on the opened file, and the snapshot bar's Close button pressed for real. Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * Cite the new regressions in parity, restore main's installer note The appearance entry now cites the bounded walk and the save through the writer, and says what a tree that cannot be read to the end does. The installer entry carried main's older text, which still claims signed installers are planned; main's newer note is taken verbatim. Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * M9: preserve rounded decoration beside raster status glyphs * M9: place appearance progress beside signature history * M9: retain appearances with ambiguous curved layers * M9: keep signature verdicts in viewer overlays Defer generic appearance rewriting: caption text and a unique drawing do not establish an icon role. Preserve original artwork for saves, image and text exports, and both print modes while retaining snapshot reuse and trust refresh. Cover chamfered borders and mixed signer/caption streams through signed Session round trips. --------- Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> Co-authored-by: skylar taylor-barrick <hi@skylar.cc> |
||
|
|
4164c0e385 | i18n: add Lao (lo) translation support | ||
|
|
aad803caae |
M14: add Catalan interface and date localization
Register Catalan (`ca`) as an interface and date language with a 2,592-entry contributor-original catalog written from the meaning of the English labels. Plurals use the one/other rule; every `ca-*` locale, including Valencian (`ca_ES@valencia`), resolves to this catalog. Tests cover registration and persistence, every command, menu, tool group and section, every `tl!` literal in the UI source, and every key in the other bundled catalogs. The Latin-script glyph test in tests/fonts.rs now includes Catalan (the interpunct of l·l is covered by the bundled faces). Catalan dates elide the preposition before a vowel (7 de març, 7 d'abril, 8 d'octubre), so the date language sets months_with_day, as Czech and Russian do; a unit test pins it. Update localization docs, the parity table and the roadmap log, and add the ATTRIBUTION.toml entry (ATTRIBUTION.md regenerated). The contributor, a native Catalan speaker, has reviewed the translation. Validation: cargo fmt --check; clippy -D warnings and tests on pdfcraft-ui-egui (i18n, fonts); pdfcraft-engine dates tests; cargo xtask assets; cargo xtask layers. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
dc41044ba7 |
Fill Vietnamese and Lithuanian catalog coverage gaps (#2510)
Co-authored-by: Codex <codex@openai.com> |
||
|
|
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 <noreply@anthropic.com> * Refresh catalog hashes after merging main --------- Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * G14: correct current union key count in localization docs Co-authored-by: Cursor <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> --------- Co-authored-by: DUY Tran Nhat (SAE-KIDS) <duytn@sanancorp.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c699e88c93 |
i18n: add Albanian (sq) interface language
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> |
||
|
|
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 <noreply@anthropic.com> |
||
|
|
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 <noreply@anthropic.com> |
||
|
|
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 <noreply@openai.com> * 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 <noreply@openai.com> * 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 <noreply@openai.com> * 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 <noreply@anthropic.com> --------- Co-authored-by: OpenAI gpt-6.1-sol <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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 <noreply@anthropic.com> * Main gates: rustfmt tests/viewer.rs; a type alias for the refresh-steps array (clippy type_complexity) 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> |
||
|
|
3d2bdfe71a |
feat(i18n): add complete Turkish interface language support (#1057)
* feat(i18n): add complete Turkish interface language support * Turkish: rows for the labels that landed alongside this catalog; refresh hashes --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
d1980ec99f |
M14: Kazakh interface language (#955)
* M14: Kazakh interface language A complete clean-room Kazakh (kk) translation of the interface: 2105 catalogue rows covering every English key (commands, tool catalogue, UI literals), with the plural forms and the DATE_LANGUAGES entry (months, months_short, weekdays) the other languages have. - Register 'kk' (Қазақша) in LANGUAGES; every kk-* locale resolves to it, and it appears in Preferences and the date-language picker. - Three coverage tests cloned from the Ukrainian battery: every registered command and All tools label, every tl! literal in the UI sources, and no untranslated English left (format-only strings excepted). - tests/fonts.rs covers the catalogue's Kazakh Cyrillic (әғқңөұүһі) against Inter and JetBrains Mono, which both carry the letters. - ATTRIBUTION.toml gains the kk.tsv entry (contributor-original, MIT OR Apache-2.0). cargo test -p pdfcraft-ui-egui (lib 171 + fonts 7), clippy -D warnings, cargo fmt --check and cargo xtask assets all pass. * Tests: give the Kazakh literal check its own doc comment and restore the Ukrainian one * Kazakh: rows for the labels that landed alongside this catalog; refresh hashes --------- Co-authored-by: pechnikov2026-star <pechnikov2026-star@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
58ae136267 |
Add Greek (el) interface localization (#994)
* feat(i18n): add Greek localization * Greek: same place in both language lists, covered by the font test; Μαΐ * Greek: rows for the labels that landed alongside this catalog; refresh hashes --------- Co-authored-by: George Athenakis <2944375+Camthalion77@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
a0d68dafd2 |
M14: Slovenian (Slovenščina) interface language (#1091)
* Add Slovenian translation * Slovene: a date language for the date picker; drop two stale PRINTCRAFT_MODELS rows * Slovene: rows for the labels that landed alongside this catalog; refresh hashes --------- Co-authored-by: spacekomet <1369225+spacekomet@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
cc344a26de |
Typed signatures draw Cyrillic and Greek letters (#846) (#963)
Dancing Script, the typed-signature face, is Latin and Vietnamese only, so script_outline skipped every Cyrillic or Greek character and a Serbian Cyrillic name came out empty. Characters the script lacks are now drawn from the bundled Inter Regular, slanted about 12 degrees and scaled to the script's capital height. Characters neither face has are still skipped. Inter's bytes are now exported as pdfcraft_fonts::INTER_REGULAR and the UI uses that static, so the font is still embedded once. Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
046df9dfe4 |
UI: theme picker with light and dark theme cards (#1082)
* UI: theme picker with light and dark theme cards * UI: update the icon count and the theme test for the cycling theme button --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
892f0b78f0 |
M14: give Windows PDF files a dedicated document icon (#890)
* M14: give Windows PDF files a dedicated document icon Embed the document icon as resource 2 through the existing Windows resource build and select it in both PDF association registrations. Keep the asset reproducible and carry its required licence notices in Windows packages. Formatting, strict Clippy, 1,842 existing workspace tests, all 30 WASM crate checks, assets, layering and parity pass. The Windows x64 release package checks pass, and the contributor confirmed the icon in Explorer. Co-authored-by: Codex <noreply@openai.com> * Refresh catalog hashes after merging main --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
603cefe213 |
M14: Add Polish interface language (#782)
* M14: Add Polish interface language * Polish catalog: attribution entry; format the Polish date names * Polish catalog: keyboard key names * Refresh catalog hashes after merging main --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
+21 |
26dec1030b |
Combine files: expand a file into its pages (2 of 3, refs #611) (#788)
* Combine files: a thumbnail grid, card sizes and card actions (refs #611) Combine files shows each file as a card (the default; Grid/List kept in the settings, --combine-view): the first page it adds, stacked when it adds more, its name shortened in the middle. Cards select (click, Ctrl, Shift, arrows by card and by row), drag to reorder (one undo step; the grid scrolls at its edges) and share the list's toolbar, selection and history. Locked, unreadable, bad-range and unmeasurable files say why. Thumbnails render off the UI thread, only for cards in view: at most three render threads at once, counted until they exit (no watchdog replacements), a 32 MB texture budget, keys that never show another page, password or file, and one heavy step per frame on the web. Cards are sized from 60% to 200% (toolbar slider and buttons, Ctrl +/-/0, Ctrl+wheel or pinch over the grid, kept in the settings, --combine-zoom), keeping the card under the pointer in place. Hovering a card shows its actions over the top of its page: remove that file, and a magnifier that shows it large, flipping through the pages it adds; the same on a right click, and Space by keyboard. The toolbar wraps between groups at narrow widths. RenderPool gains retire(), without_replacements() and threads() so a caller can bound the render threads that exist at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Combine files: expand a file into its pages (refs #611) A file that adds more than one page (up to 2000) expands into a card per page it adds, in its range's order, each labelled with its page and the file's name, on a shared tinted band: from its card's hover bar, a right click, or Expand all / Collapse all in the toolbar. A page card's magnifier opens the preview on that page; Up and Down step rows of cards. Expanding is a view: not an undo step, kept across views and zoom, and the combined file is the same either way. Thumbnails are now kept per file and page, looked up on demand (the cost follows the files, not the pages of an expanded file), and a render's document stays open for two seconds so the next page of the same file isn't read again. Adds the Lucide minimize-2 icon (ISC, recorded in ATTRIBUTION). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#873) Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, PDF-spec, hardware, UI and localization parity docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Measure: snap to the implicit closing edge of filled paths (#803) (#862) * fix(organize): reject rotation angles that are not multiples of 90 (#801) (#833) Page rotation only turns in quarter steps, but the degree value was read with an integer division by 90 that threw the remainder away. Asking for 45 degrees saved an unrotated page, 91 and 135 saved a 90 degree turn, and the command still reported success. Reject the value before touching the document instead, so every caller gets an actionable error and the pages are left alone. The automation tool already rejected it; the single edit command did not. Fixes #801. Co-authored-by: Big Pickle <noreply@opencode.ai> * Autosave: snapshot a new unsaved document before its first edit (#815) (#850) * OCR: Recognize text in multiple files refuses paths outside the root (#795) ocr_recognize_files turned a path outside the automation root into a per-file error and returned success, so no_tool_escapes_the_root reported 9 leaks once the OCR models were installed. Without them, loading the models failed first and hid it, and CI never installs them. Every path is now checked before any work starts, and one outside the root refuses the whole call, like the other tools. A missing file inside the root stays a per-file error. Nothing was ever read from outside the root. ocr_recognize_files_refuses_paths_outside_the_root checks this without the models. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(text): match phrases across repeated whitespace (#814) (#831) The phrase search collapsed runs of whitespace in the query but not in the page text it was matching against, so a document drawn with two space glyphs in a row like the issue example never matched the phrase. Single words still matched, which is why it looked like only some files were affected. Collapse whitespace while the text is flattened, the same way the query is normalised. The regression test fails on the old code because the phrase find returns an empty range, and it also covers tabs and single word queries. Fixes #814. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(edit): keep a comment whose appearance is missing when flattening (#805) (#838) Viewers draw notes, FreeText, ink and stamps from their dictionary when the file stores no /AP /N. flatten only drew annotations that already had an appearance and then removed the rest, so a supported note disappeared and the saved page came back blank. Build the appearance a viewer would show for every selected, visible, non-widget annotation that has none, then draw it as usual. A subtype this build cannot render still fails to build and is handled exactly as before. The new synthetic test draws the note icon into the page and asserts it is baked in; without the fix flatten reports 0 drawn and the icon is lost. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(comments): keep a reply to a reply in the comment listing (#806) (#839) comment_list collected only the replies whose /IRT pointed at the root comment, so a reply to a reply was listed nowhere at all. Roots and the root count were already right; the nested reply was simply dropped from its thread. Follow the /IRT chain to any depth, with a bound, so every reply under a comment is listed in document order. A reply to a reply now stays discoverable through the tool that promises every comment and its replies. The new test builds a root note, a direct reply and a reply to that reply, then checks both texts are listed and the two notes stay roots. It listed only the direct reply before the fix. Co-authored-by: Big Pickle <noreply@opencode.ai> * Issue #774: keep the catalog out of object streams in encrypted files (Acrobat) (#836) Acrobat reports an encrypted PDF as "damaged and could not be repaired", before asking for the password, when the document catalog sits in an object stream. Every other reader tried (qpdf, pypdf, poppler, PDFKit, PdfCraft) accepts such files, and qpdf keeps the catalog standalone too. Full saves of encrypted documents now write the catalog as its own object; unencrypted output is unchanged. Verified with Acrobat DC on macOS: the same file fails with the catalog packed and opens with it standalone, in AES-256 and RC4-128. Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * fix(edit): refuse a header start number that would wrap the next page (#804) (#834) Page numbering adds the start number to the position of the page in the run. At the top of the u32 range that addition wrapped, so a release build saved zero for the second page and still reported success, and a debug build hit a caught overflow error instead. Check the whole run before drawing anything. When the last page number would pass the u32 limit the operation returns an actionable error and the document keeps no header, rather than a half applied run. Fixes #804. Co-authored-by: Big Pickle <noreply@opencode.ai> * OCR: a scanned euro sign reads as €, not E (#796) The ocrs crate's default alphabet has an E where its recognition model was trained with € (ocrs-models DEFAULT_ALPHABET), so a scanned "€250" was recognised as "E250". pdfcraft-ocr now gives ocrs the alphabet the model was trained with. the_alphabet_is_the_trained_one checks the alphabet without the models; recognize_text_reads_the_euro_sign reads a scan end to end when they are installed. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(comments): include replies to replies in the comment summary (#820) (#840) The comment summary gathered a comment's direct replies only, matching /IRT against the comment itself. A reply to a reply points at the direct reply, so its text never reached the generated summary even though the stored discussion kept it. Replies are now walked to any depth, with a bound, and indented one level per reply. The existing output for direct replies is unchanged. The new test builds a rectangle, a reply and a reply to that reply, then checks the summary carries both texts with the nested one indented under its parent. It omitted the nested reply before the fix. Co-authored-by: Big Pickle <noreply@opencode.ai> * fix(xfdf): drop the replaced note's popup when re-importing a comment (#807) (#860) * fix(view): check Shift-Command-G before Command-G for find navigation (#810) (#835) * view: check Shift-Command-G before Command-G for find navigation (#810) * Tests: the find shortcut test waits for find by a deadline (cherry picked from commit eaf3180bd8ebb353b3d8c5ca7b9ef7548ed55c37) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * fix(editing): recognize paragraph drafts as unsaved work (#811) (#843) * view: check Shift-Command-G before Command-G for find navigation (#810) * fix: recognize paragraph drafts as unsaved work (Issue #811) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * fix(automation): retain batch results with identical filenames (#877) * fix(automation): retain batch results with identical filenames * Format --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * M13: preserve desktop Action Wizard batch results (#886) * M13: preserve desktop Action Wizard batch results Co-Authored-By: Codex <noreply@openai.com> * ROADMAP: date the desktop action outputs entry today --------- Co-authored-by: Tevinhead <173904641+Tevinhead@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Fit to width fits the shown page in single-page view (#808) (#859) * fix(links): find URLs when lowercasing changes the text length (#816) (#830) find_urls case-folded a copy of the page text and then sliced the original characters with offsets taken from that copy. Case folding can change a string's length — U+0130 (İ) lowercases to "i" plus a combining dot — so the offsets drifted past the end of the source and `chars[i..j]` panicked. The CLI exited 101 before it could create the link; MCP reported an internal tool error but kept serving. Match the ASCII prefixes case-insensitively against the original characters instead, so every offset stays in the source's own index space, and use get() so no lookup can index past the end. A new test reproduces the reported panic on the old code (range start index 41 out of range for slice of length 39) and passes with the fix. pdfcraft-annot's tests, clippy and rustfmt are green. Fixes #816. Co-authored-by: Big Pickle <noreply@opencode.ai> * organize: deleting the opening page drops the catalog OpenAction (#821) (#855) * fix(text): Turkish dotted and dotless i match in case-insensitive search (#892) Case-insensitive search folded the query and the page with str::to_lowercase, which follows no locale: U+0130 (İ) becomes "i" plus a combining dot above (two characters) and U+0131 (ı) has no case pair. So "istanbul" never found "İSTANBUL", and "ırmak"/"kış" never found "IRMAK"/"KIŞ" (or the reverse). The same PageText::find serves Find, Search & Redact (phrases and word lists) and the automation text search, so Turkish words could also be left unredacted. Fold I, i, İ and ı to "i" and every other character with its own lowercase, one character for one character. Case-sensitive search is unchanged. * XFDF keeps polygon and polyline vertices (#809) (#848) Export wrote polygon and polyline comments without their points and import had nowhere to read them from, so an XFDF round trip left the comments with no /Vertices and no appearance. Export now writes the points as XFDF <vertices>x,y;x,y;…</vertices>, and import reads them back (whole x,y pairs only) before the appearance is drawn. Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * XFDF keeps a callout's leader, text-box inset, intent and arrowhead (#819) (#854) Co-authored-by: Brandon Thomas <echelon@gmail.com> * Web: warn before reloading a document with unsaved changes (#812) (#856) * Issue #813: a cancelled recovery password no longer attaches to the next unlocked document (#858) Recovering an encrypted snapshot parked its metadata in `pending_recovered` until the password prompt was answered. Cancel left it there, and the next encrypted document unlocked in the foreground took the snapshot's original path and recovery key: Save would overwrite the other file and delete its snapshot. The association now travels with the prompt (`PasswordPrompt::recovered`). Cancel puts the entry back on offer and leaves the store alone; a wrong password keeps it on the re-raised prompt; the correct one finishes the recovery of the tab that was just opened. `pending_recovered` is gone. Test: crash with an encrypted snapshot of A, Recover, Cancel, open encrypted B with its password. B keeps its own path and is clean, A's snapshot stays in the store, also after closing B. Fails on main at "B keeps its own path". Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * Viewer: touchpad scrolling tracks the finger 1:1 (#759) (#868) A precision touchpad sends pixel-precise `MouseWheel` events. On Windows they arrive with no `TouchPhase::Start`, so egui never takes its no-smoothing "in touch" path and eases any delta of 8 px or more over several frames, leaving the page a frame behind the finger. The page view now applies precise (Point-unit) wheel input itself, 1:1, through the same un-eased scroll path as keyboard and middle-button auto-scroll, and suppresses egui's eased copy for a short window so its internal easing tail neither doubles nor trails the motion. A coarse mouse wheel (Line/Page units) keeps egui's smoothing, which is pleasant for chunky notches, and single-page paging still owns the vertical axis. Regression test: one 200 px precise swipe scrolls the full 200 px at once (an eased path would ramp there over ~0.1 s) and leaves no drift. Co-authored-by: Justin John <babyjjbabyjj549@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * Keep every part when bookmark titles repeat in doc_split (#800) (#863) * Windows: add reproducible security diagnostics for storytold/pdfcraft#747 (#769) Add packaging/windows/security-audit.ps1, a read-only maintainer helper that records a release file's SHA-256, Authenticode status and, with an external YARA binary and rule, every matched string identifier and offset. Document the procedure in docs/releasing.md ("Scanner detections"). No application, dependency, installer or network behaviour changes: the diff touches only the helper and the release documentation. Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com> * Notice bar: the message wraps beside its buttons and lines up with them (#785) (#832) The notice bar above the pages (security, forms, XFA, repaired file, warnings, signatures) laid its message out before its buttons. In a narrow document area (a side panel open, a longer translation) the message ran under "Security settings" and on into the side panel, and it wasn't centred on the buttons. A shared notice_bar helper now lays the buttons out first, right to left, centred on a 28 px row, and gives the message the width that's left: it wraps there (cut short with the full text on hover when less than 120 px is left) and its first line is centred on the buttons' row. Regression test: editing::the_security_notice_wraps_beside_its_buttons_and_lines_up_with_them (one line, wrapped and cut-short widths; fails on main). Co-authored-by: DWAA1660 <DWAA1660@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * M10.2: scan JPEG metadata beyond frame headers (#764) Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Issue #878: Combine stores an image once when its colour space is an indirect name (#883) ImageMagick writes an image's colour space as its own object (`/ColorSpace 10 0 R`, `10 0 obj /DeviceRGB`). Each source's copy of that object was never merged, since it isn't a resource, so identical images compared unequal and every copy was kept: 8 copies of an 80 MB ImageMagick PDF combined into 648 MB instead of 81 MB. Dedupe now compares a reference to a plain value (name, number, boolean or string) as that value. The value objects themselves are still never merged. Combine, Insert Pages and Reduce File Size share this code. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * organize: drop named destinations and their links when a page is deleted (#817) (#851) * JavaScript: limit util.printf widths and output, and array arguments (#770) util.printf accumulated a conversion's width and precision with unchecked arithmetic and no maximum, and its output had no bound; the array arguments of resetForm and the field value/colour setters were read for as many items as the array's length says, which a sparse array makes free. Saturate the numbers and refuse widths and precisions above 4096, output above 1 MiB and arrays longer than 2^20 with a script error, before anything is allocated from them. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> * Linux: "Use system setting" asks the desktop, not winit (#881) winit reports no system theme on Linux — `ActiveEventLoop::system_theme` is `None` there on every desktop — so `ThemePreference::System` never saw the desktop's choice and silently fell back to whatever was on screen: PdfCraft came up light on a dark desktop, and picking "Use system setting" after Dark kept it dark. The default setting did not follow the system. `pdfcraft-platform` now reads the XDG Desktop Portal's Settings interface off the UI thread and the app follows the answer, including a change made while it runs. The colour scheme is asked for under its GNOME schema name first, which GNOME's and XFCE's portals both answer, then under the older freedesktop one, which KDE answers. A desktop that answers neither leaves the user's own choice alone, exactly as before. Co-authored-by: GyuriX <gyurix@users.noreply.github.com> * Extract/Combine: a page repeated in one selection gets its own annotations (#818) (#852) * M5.7: Fill & Sign tools pick up placed signatures to move and resize them (#822) A signature placed with Fill & Sign could only be moved with the Select tool. While a Fill & Sign tool was active (Sign, Initials, Add text, Check, Date...), hovering the signature showed the placement crosshair, a drag did nothing and a click stamped another mark on top of it. Now, as in Acrobat, a Fill & Sign tool picks up the Fill & Sign marks already on the page: hovering one shows the move cursor, a click selects it, a drag moves it and the selected mark's handles resize it. Away from them the tool places as before, including on other comments. Image signatures keep their live move/resize preview. Comments now carry a fill_sign flag (pdfcraft_annot::is_fill_sign for the engine's list, the same rules in the viewer's inspector), so this also works on reopened files. Co-authored-by: lloyd <lloyd@stackworx.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Web: opt-in embedding bridge for host pages (?host=parent) (#842) A page that frames PdfCraft on the same origin can now pass it PDFs and receive saves through postMessage, instead of Save becoming a browser download. Built for a Nextcloud integration, but nothing in it is host-specific. - ui-egui: HostSaveFn/HostDirtyFn hooks (`host_save`, `host_dirty`), None by default. On wasm, Save hands the bytes to `host_save` when set; `host_dirty` hears each frame whether any tab has unsaved work. - pdfcraft-web: `host::Bridge`, enabled only by `?host=parent` inside a frame. Messages: pdfcraft:ready / open / save / dirty / failed; only the parent window on our own origin is heard or answered. `?author=` names comment authors unless the user chose a name. - Test: host_dirty_hears_when_unsaved_work_appears_and_goes. Without `?host=parent` nothing changes. Claude-Session: https://claude.ai/code/session_01G6GtUCaPiCMEQCN4QcdQnp Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> * Print: a scaled annotation appearance no longer prints larger than its rectangle (#802) (#861) * Single instance: enable tab handoff in Windows portable mode (#889) (#891) * Single instance: enable tab handoff in Windows portable mode (#889) * Single instance: a portable copy's socket stays in %LOCALAPPDATA%, under its own name (cherry picked from commit c7db1e80a374e80a11504efefbaac680dfa5e872) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> * Issue #390: build the DMG with hdiutil create so the app passes codesign --strict (#823) * Issue #390: build the DMG with hdiutil create so the app passes codesign --strict makehybrid -hfs wrote a com.apple.FinderInfo (Finder location -1,-1) on every file in the image, and codesign --verify --deep --strict rejects that as "Finder information, or similar detritus", on the app in the DMG and on a copy installed from it. hdiutil create -srcfolder writes the files as they are; it attaches a device while copying, so it gets up to five tries. A gate mounts the finished image read-only and verifies the app inside with --strict. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu * Issue #390: retry hdiutil attach and detach in the DMG gate too As suggested in review: the gate mounts the signed image under set -e, so a "Resource busy" there would stop a release after the DMG was built. The same five tries with growing pauses now cover create, attach and detach. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu --------- Co-authored-by: Tristan von Kries <tristan@MacBook-Pro-von-Tristan.local> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> * M4: make page-label generation lazy across large documents (#845) * M4: make page-label generation lazy across large documents Page labels were expanded into one String per page, and the whole sequence was refused once the labels passed 4 MiB in total. A valid document with long labels could therefore neither be read nor edited. Add a PageLabels view over the label ranges: label(page) builds one label, and iter() builds the labels one at a time, so no string is kept per page. Edits validate each range from its largest number (Roman numerals by their thousands) instead of building every label, so the check no longer depends on the page count. The per-label length limit, the number-tree work and depth limits and the overflow checks are unchanged. page_labels() keeps its signature and returns the same labels as before for ordinary documents. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * M4: drop the aggregate page-label cap from document inspection Document inspection refused every custom label once their total passed 4 MiB, so a valid document with long labels showed physical page numbers and a warning. Labels are now applied to every page. The per-label length limit, the number-tree work, depth and cycle limits and the prefix bounds are unchanged, and labels are still applied only after every range is accepted. MAX_LABEL_TOTAL_BYTES is removed. The inspector keeps one label per page (PageInfo.label), so its label memory grows with the page count times the label length. The page count is bounded by the objects in the file, and each label by 1,024 bytes. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> * M4: keep the inspector's 4 MiB page-label cap until its labels are lazy Commit |
||
|
|
1c33e6f851 |
M14: Arabic interface language (#692)
* M14: Arabic interface language A 2,136-entry Arabic catalog (six CLDR plural forms, Arabic month and weekday names), selected by `ar` and every `ar-*` locale. egui joins Arabic letters but places words left to right, so the catalog is put into display order when it loads (bidi::display_rtl); placeholders, keyboard shortcuts and caption colons stay in place. The command search matches a multi-word Arabic query word by word. The craft-fonts pin moves to the revision with Noto Sans Arabic. crates/fonts/build.rs now refuses to embed Adobe type designs, so the Noto Sans CJK SC in that revision is never embedded (AGENTS.md §1.1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M14: Arabic catalog follows the {key} shortcut labels and has Redaction code 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> |
||
|
|
49c6e2ec54 |
i18n: complete Brazilian Portuguese (pt-BR) interface catalog (#604)
* i18n: complete Brazilian Portuguese (pt-BR) interface catalog Catalog by André Cardoso (andrecards) from #288, unioned with the strings merged after the catalog was cut. Asset hash refreshed. * i18n: translate the pt-BR strings added since the #288 catalog and gate coverage * i18n: pt-br review fixes (line ending, redact/sanitize/page-boxes terminology) * i18n: credit the pt-BR translator and document full coverage Name André Cardoso (andrecards) as the pt-br catalog's translator in ATTRIBUTION.toml: the ~1,826-entry gist posted in issue #288, with the labels added since and the wording review as this change's contribution. ATTRIBUTION.md is regenerated from it. docs/localization.md moves Brazilian Portuguese from the menus-only clause into the full-coverage list, states the 1,997-entry count next to the provenance (following the Ukrainian paragraph's pattern), and lists pt-br with the languages whose coverage tests gate every command, All tools label and tl!() literal (added with the catalog). people.toml is unchanged: PR #165's translator has no entry there, and its policy is that only one's own entry may be added. * i18n: pt-br writes '>' like the other Latin catalogs (Inter has no ▸) * i18n: pt-BR final review fixes (toast grammar, contextual entries, menu gate) * pt-BR: add the labels main gained while this catalog was completed (98) and drop stale OCR keys --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
94f9ce69c6 |
i18n: add Italian (it) interface translation (#494)
* i18n: add Italian (it) interface translation - Complete it catalog (about 2,000 entries); it and it_* locales use it - Fonts test covers the Italian catalog; ▸ written as > like fr/es/ru, since Inter has no ▸ - Docs, attribution and ROADMAP Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * it: add the labels main gained since this catalog (83), drop stale OCR keys, and coverage tests --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
56f85f76df |
M9: more signature validation algorithms (SHA-3, RIPEMD-160, Ed25519, P-521, brainpool, PSS, rsa_sha1) (#386)
* M9: more signature validation algorithms Validation now reads what real signers write, and says when it is weak: - Digests SHA-224, SHA-512/224, SHA-512/256, SHA3-224/256/384/512 and RIPEMD-160, beside SHA-1 and SHA-2. - RSA PKCS#1 v1.5 for every digest, in `rsa_pad`: the one expected encoding is built and compared byte for byte, with or without the DigestInfo's NULL (RFC 8017 App. B.1). A signature over the bare digest, which doesn't say which hash it uses, is refused. The signature value must be below the modulus (RFC 8017 §5.2.2); s and s + n both verified before. - RSA-PSS with the hash, MGF1 hash and salt length the signature declares. - ECDSA as DER, non-minimal DER or raw r||s on P-256, P-384, P-521 and brainpoolP256r1/P384r1/P512r1 (BSI "plain" ECDSA OIDs too); brainpoolP512r1 is hand-written on crypto-bigint because no RustCrypto crate has it. - Ed25519 (RFC 8419), and the legacy adbe.x509.rsa_sha1 format. - An algorithm PdfCraft can't check is Unknown, never Invalid. - SHA-1 and RIPEMD-160 signatures validate, but the signature's details say the algorithm is weak (wording pinned by tests). - Encoding irregularities that were tolerated are listed in the details (`verify_noting`; `verify` keeps its signature). Found while testing, each with a regression test that failed before the fix: a brainpoolP512r1 public key with a coordinate >= p panicked inside crypto-bigint's `sub_mod` (the certificate comes from the PDF); it is refused now, and the point at infinity is an invalid signature, not an error. Vectors were made with OpenSSL (tests/data/README.md has the commands). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Update comment to clarify MAC digest restrictions Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * M9: a legacy x509.rsa_sha1 signature whose key the verifier rejects is Invalid; only unsupported keys are "can't check yet" Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |