* fix(x11): prefer server DPI over panel physical DPI in winit
Without Xft DPI, use the X server screen dimensions before the output physical density. Xorg can run a dense panel at 96 DPI; EDID sizes alone make only winit apps appear zoomed.
Supersedes #908: preserves Xft.dpi priority, configured HiDPI screens, and WINIT_X11_SCALE_FACTOR override (including randr). Falls back to RandR output density when server dimensions are missing.
Adds default_dpi_factor() with 4 unit tests plus vendored-copy guard test in apps/pdfcraft/tests/winit_x11_dpi.rs.
* vendor/README: record the winit X11 DPI patch as (11)
---------
Co-authored-by: Brandon Thomas <[email protected]>
* i18n(es): add 84 missing rows, consistent tú register
Spanish lacked 84 labels that every other full catalog has (About window,
field alignment, redaction marks, certificate details, address warnings…),
so they showed in English. Five strings used formal usted; the field mask
hint was garbled; per-page rotate now matches the other rotate commands.
AI-assisted, native review welcome.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LtFRDwSXTyqaPWCYnbBGnX
* Spanish: one row each for Run form script and Mark differences
---------
Co-authored-by: Diego Ospina <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M5: show actions for selected page text
* Selection actions: literal labels (so catalogs can cover them), no unwrap or unreachable
* Catalogs: Add comment in every interface language; refresh hashes
---------
Co-authored-by: Brandon Thomas <[email protected]>
* M14: choose Tools panel side in Preferences
* Catalogs: Tools panel side in every interface language; refresh hashes
* OCR dialog: the shared outlined checkbox (raw ui.checkbox is linted since #1173)
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Issue #1028: OCR can read pictures on pages that already have text
Recognize Text skipped every page with text, so a picture of text on a
page that also has real text never became searchable. The engine and
the ocr_recognize tool already had skip_text_pages, but the desktop
dialog never offered it, and turning it off read the page's own text
again and added it a second time, so search found every word twice.
- Engine: always collect the page's own glyph boxes; with
skip_text_pages false, a recognised word at least half covered by
them (an earlier OCR layer included) is left out. OcrPage counts
those words as already_on_page. The default still skips pages with
text.
- Dialog: "Also recognize pages that already have text", kept with the
other choices and used by Recognize text in multiple files too.
- Tools: ocr_recognize reports already_on_page; ocr_recognize_files
accepts skip_text_pages.
- The two new labels in the 15 catalogs that translate the OCR dialog.
- Synthetic tests (a picture of a sentence plus real text) for the
engine, the tool and the dialog; the engine test fails with the
filter turned off. Unit tests for the coverage check, including
NaN, infinite, inverted and overlapping boxes.
- parity, docs/roadmap.md M10 row and the ROADMAP.md progress log.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JqiZ658tGndQaHao5Q6mdm
* Issue #1028: Czech labels for the OCR text-pages option
The Czech catalog was completed (#1066) while #1028 was in review, and
czech_covers_ui_literals requires every tl! literal: the two new
Recognize Text labels were missing, so the test failed after the merge.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JqiZ658tGndQaHao5Q6mdm
* Contributors: credit rp6186030 as Raj Patel in the About window
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JqiZ658tGndQaHao5Q6mdm
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: rp6186030 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
The print script mapped "Flip on long edge" to System.Drawing's Duplex.Horizontal and
"short edge" to Vertical. Those are DEVMODE's DMDUP_HORIZONTAL (short-edge binding) and
DMDUP_VERTICAL (long-edge binding), so every Windows two-sided job bound on the other edge:
a PCL driver received @PJL SET BINDING = TOP for long edge. They are swapped back.
Win32_Printer's Capabilities say whether a driver prints two-sided (3, Duplex Printing). The
printer list already comes from Win32_Printer, so it now carries that as Printer::two_sided
(None on CUPS), the printers tool reports it, and the Print dialog notes when the selected
printer's driver doesn't take two-sided settings and points to Properties…. Such drivers keep
their own setting even with Two-sided Off. No vendor-specific names are used.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Both page-label readers decode each range's prefix into a string of its
own: render's inspection for its range table, Organize for the ranges it
returns. Ranges may share one prefix object, so a small file could ask
for a copy per range: within the 200,000-entry tree limit, about 200 MiB
of copies of one 1,024-byte prefix. Decoded prefixes now take at most
4 MiB in all (MAX_PREFIX_TOTAL_BYTES, beside MAX_LABEL_TOTAL_BYTES). Past
that the tree is refused before any label is made, as the other tree
limits refuse it.
Tests: 5,000 ranges sharing a 1,000-byte prefix are refused in both
readers (relabelling such a tree changes nothing), and exactly 4 MiB of
prefixes is still read.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M14: "Edit with PdfCraft" on the File Explorer menu of PDF files (#682)
The Windows MSI adds a classic shell verb under
HKLM\Software\Classes\SystemFileAssociations\.pdf\shell\PdfCraft.Edit that
runs "pdfcraft.exe" "%1" with the app's icon. It appears whichever app is
the default for .pdf and never changes that default. On Windows 11 it is
under "Show more options": a first-level entry needs an IExplorerCommand
handler and package identity, which an MSI doesn't have.
MultiSelectModel is Document: several selected PDFs (up to 15) start one
launch each, and the existing single-instance hand-off opens them as tabs
of one window.
The verb has its own component, conditioned on a new secure public
property INSTALLPDFCONTEXTMENU (default 1, a welcome-dialog checkbox,
INSTALLPDFCONTEXTMENU=0 for unattended installs), following
INSTALLDESKTOPSHORTCUT. Its menu value is the key path, so uninstall
removes every value. The portable zip still registers nothing.
test-msi.ps1 checks the compiled Registry, Component, Property and
CheckBox rows, and that the .pdf default is never written. Docs: README,
docs/releasing.md, parity notes, ROADMAP log.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Leave ROADMAP.md to main (its log moved on); the change is described in the PR
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* UI: checkboxes draw a rounded-square box, not a radio-button circle
The theme's 6 px widget radius made egui's 14 px checkbox box a circle,
so View > Page display's Continuous scrolling and Show cover page looked
like two more layout radio buttons. On the light theme an unticked box
had no outline and the panel's fill, so it couldn't be seen.
Every checkbox (81 call sites) now goes through widgets::checkbox /
checkbox_without_text, which draws the box with a 3 px radius and a 1 px
border outline, keeping an outline the caller already set (dialogs).
clippy.toml disallows egui::Ui::checkbox and egui::Checkbox::{new,
without_text} so new checkboxes use the helper too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* UI: radio buttons are outlined and filled, so they show on the light theme
On the light theme the field colour is the panels' white and widgets
have no outline, so an unselected radio button (and an unticked
checkbox) was invisible outside dialogs: View > Page display showed only
the selected dot.
Every radio button (79 call sites) now goes through widgets::radio /
radio_value, sharing the checkbox helper's styling: a 1 px border
outline, text_muted on hover, and on the light theme the plain fill
(hover) as dialogs already had, keeping what the caller set. The dark
theme keeps its fills. clippy.toml also disallows egui::Ui::radio,
egui::Ui::radio_value and egui::RadioButton::new.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Add 'Replace pages…' action to the Organize Pages toolbar, organize grid thumbnail context menu, and Pages panel context menu to replace selected pages.
- Add ViewAction::ReplacePages triggering replace_pages_dialog()
- Expose 'Replace pages…' button with 'replace' icon on the organize toolbar
- Expose 'Replace pages…' on the organize grid and Pages panel thumbnail context menus
- Update parity evidence for organize.replace-pages
- Add integration tests for toolbar and context menu replace actions in crates/ui-egui/tests/editing.rs
Co-authored-by: KrzysztofC <[email protected]>
Reuse a replaced named comment's object reference so omitted replies and
review statuses remain attached. Keep popup replacement behavior and let
the final duplicate record control its deferred reply relationship.
Resolve indirect names in existing PDF comments so updates match their
original objects instead of creating duplicate parents.
Cover existing-PDF updates, incremental and full saves, subsequent FDF
exchange, page-scoped names, unnamed additions, duplicate records and
indirect names with surviving reply and review-status references.
Co-authored-by: MMexicann <[email protected]>
Co-authored-by: Codex <[email protected]>
Encryption derives its keys from a prepared password, not the typed one:
AES-256 (R6) applies SASLprep, falls back to the password as given if
SASLprep refuses it, and truncates the UTF-8 to 127 bytes; RC4/AES-128
(R2-R4) use PDFDocEncoding cut or padded to exactly 32 bytes, and the
padding starts with "(". Protection::validate checked only the typed
strings, so a permissions password of U+00AD wrote a file that opens with
owner rights and no password, and an open password "A" with a permissions
password U+FF21 (or RC4/AES-128 passwords "a"x31 and "a"x31 + "(") wrote a
file whose open password lifts the restrictions.
pdfcraft-crypt gains effective_password(algorithm, password), the bytes the
keys are derived from (it and create share the private prepared_password
helper), re-exported from pdfcraft-cos. validate refuses a password whose
effective bytes are those of "", and an open and a permissions password
whose effective bytes are equal. Opening files is unchanged.
Fixes#1185.
Export Data on an XFA form writes its data as Acrobat does: XML (the
data alone, rooted at the form's root data node, its namespaces
declared) or an XDP holding the whole datasets and a reference to the
PDF (.xdp, a new format). Import Data takes an XDP, a datasets or data element, or a
data document whose root is the form's root data node, and makes it
the form's whole data, keeping data descriptions; a laid-out form is
laid out again from it (rows added and removed) and its calculations
run; XFDF, FDF and tables still go the AcroForm way. Undoable.
Image fields are now buttons marked /PCXfaImage (no PDF action): the
forms layer treats one as an image field, so clicking it (or
form_set_image) picks a picture, which is kept in the data as base64
with its xfa:contentType, as Designer saves it, and the laid-out form
shows it from there.
xfa: replace_data, write_data_image (hostile content types cleaned,
32 MB cap), export_data, import_data (64 MB cap, the parser's node
limit, self-closing data elements). engine: export routing by
document kind, import routing by the file's root element, re-layout
and calculation after the data changes outside the fields, the
picture's content type from its bytes. UI: XML and XDP in the export
dialog of an XFA form, .xdp in the import filter. Tool texts, README
and parity (form.xfa-data) updated.
Hardened after an adversarial review pass: the file's data is held as
a tree and written with the packet's own namespace prefix (never its
markup), every packet rewrite is parsed back before it is written,
files capped at 16 MB and 64 levels (a tag scan before the recursive
parser, also guarding the datasets parser), pictures at 8 MB and PNG/
JPEG/GIF only, the file's shape and root name checked, UTF-16 and
ISO-8859-1 decoded, pictures into data groups refused, cleaned names
for pictures, static forms empty the fields the file doesn't hold,
more rows than the layout cap reported, one datasets stream per edit,
and only image fields take pictures into the data.
* M7 #594: preserve ordinary paragraph drags (source-only GGEO candidate) [skip ci]
Route body drags through source-preserving MoveObjects, bind gestures to
the document generation, and keep added-content input ownership with
the Edit panel open or closed. Add nine real-shell source regressions.
Source-only transfer to the owner's fork, explicitly authorized before
validation. Independently source-reviewed; formatting, builds, tests,
rendering and all gates remain pending on ggeo-ai02. No upstream PR.
AI-assisted with OpenAI.
* M7 #594: record validated ordinary paragraph drags
Format the source-reviewed candidate and record successful GGEO validation:
focused UI/engine tests, exact-base source loss, native drag/history/save/reopen,
independent Poppler checks, and all eight repository CI gates.
Credit: @FredApps
* M7: use generic validation wording
* Tests: pin the clock where two saves or combines are compared byte for byte
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: FredApps <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* M7: support shaped Thai text in Add Text and UI fallback fonts
Share the bounded shaping and Type 3 writer with Thai, preserving vowel and tone clusters and original Unicode through save/reopen. Keep the Thai face optional and update UI notices and their translations.
Constraint: Thai font assets remain in the companion craft-fonts repository.
Confidence: high
Scope-risk: moderate
Co-authored-by: Codex <[email protected]>
* M7: show Thai Add Text before and after the fix
Document the rejected input on the base commit and the saved Thai text on the fixed build using genuine UI captures.
Constraint: Synthetic content and licensed fonts only; system font fallback disabled.
Co-authored-by: Codex <[email protected]>
* M7: demonstrate Thai stress cases in the comparison screenshot
Show nine lines of stacked marks, both Sara Am encodings, Thai digits and mixed Latin text. Verify all 198 Unicode code points survive saving and extraction with both PdfCraft and Poppler.
Co-authored-by: Codex <[email protected]>
* CI and releases: craft-fonts pin to 3d9ab3a (adds Noto Sans Thai)
ci.yml, freebsd.yml and the craft-fonts-ref release.yml passes to craft-common-actions move
from 8dcdacd to storytold/craft-fonts 3d9ab3a, the revision with Noto Sans Thai (#18), so
Thai Add Text is built in and tested.
* ATTRIBUTION: drop the screenshot entry again (the image isn't committed)
---------
Co-authored-by: Codex <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M14: text the fonts can't draw is kept and reported, never written as ? silently
Form values and captions, text boxes, callouts, Fill & Sign text, measurement labels and stamp by-lines keep text the standard fonts can't draw, draw ? for it, mark the annotation (PCUndrawn) and report the edit (interface notice, tools' warnings); forms set /NeedAppearances (not in PDF/A). Flattening, redaction overlay text, typed and visible signatures and create-from-text refuse instead. Reports escape such characters as [U+XXXX].
* Catalogs: the two new can't-draw messages in Czech, Slovene, Kazakh, Turkish and Greek; refresh hashes
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Heavy pages: cull off-tile images, cache decodes, preview backdrops, calmer queues
A large CAD sheet (page 24 of the benchmark file: ~5050 pt wide, 2104
images, 9 MB of content) cost a full page interpret per tile: 4.3 s for a
1024 px tile at 1000%, 10.5 s for the whole page, and a storm of renders on
every pan frame. Tiles now cost 0.6 s, the sheet is covered by a handful of
large tiles, and gestures no longer queue work faster than workers finish it.
- Vendored hayro patch (10): skip images whose stream-size box misses the
canvas before decoding them, so a tile no longer decodes, resamples and
rasterizes every image on its page. Pixel-identical; the skip regression
test now covers images too.
- Vendored hayro-interpret patch (23): decoded image pixels shared across
renders through the interpreter cache, keyed by stream and decode target
size within a 64 MiB budget.
- Preview renders (hayro patch 11, RenderRequest::preview through to the
device): backdrops, thumbnails, grid cells and signature-drag backgrounds
skip images under 16 device px. Tiles, whole pages, print and export always
draw everything. Previews may differ by those pixels; finals don't.
- Viewer: zoom/pan settle holds apply only to pages whose raster proved slow
(slow_pages, 500 ms), so fast pages render with no added latency; tile sides
grow with zoom and page size (giant sheets need ~8 tiles, not ~28), queued
center-first and keyed by exact coordinates; Edit-text outlines capped.
- vendor/README.md records the new patches with their tests.
Co-Authored-By: Muse Spark <[email protected]>
* Decoded-image cache: key every decode input, survive a poisoned lock
The key was the dictionary's bytes and the target size, so two images with byte-identical
dictionaries (uncompressed, one size) shared pixels, and an image drawn under a transfer
function reused pixels decoded without it. DecodedImageKey now holds the dictionary's place
in the document (its data follows it), its bytes and object, mask/stencil/raster output,
interpolation, the target size and preview or final quality; images under a transfer
function and inline images (named colour spaces, dictionaries in content streams) are not
cached, and an image over the budget is never kept. A poisoned lock decodes uncached
instead of panicking. Soft masks: the per-renderer cache can't carry a preview mask into a
final render (documented). Regression tests for identical dictionaries, preview then final
through one cache, and transfer functions.
---------
Co-authored-by: Muse Spark <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Hebrew fix
* Tests: the faux-redaction audit gets its own work folder (it shared "audit" with the space audit, so the two raced)
* Arabic golden test: compare bytes only in a build without an Arabic face (CI's Linux job builds with craft-fonts)
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Content edits on heavy documents: narrow refresh, cached appearances, per-page invalidation
Every content edit (drag, resize, retype) on a large document ran a full
synchronous refresh on the UI thread: a whole-file re-inspection plus a
redraw of every missing annotation appearance, then dropped all page
rasters. On a 72 MB / 202-page CAD file with 27k appearance-less
annotations each commit cost ~1.5 s before anything re-rendered. Now ~0.3 s.
- New Scope::Content for edits that touch only page content, resources and
added content/marks (move/resize/retype/images/added items/marks): the
page tree, annotations, fields, links, outline and fonts are unchanged,
so refresh_content rebuilds working bytes, display bytes and renderer and
validates with a light check_pages (parse plus page count, keeping the
roll-back guarantee) instead of a full inspect. added/marks are re-read
only when the edit can change them; undo/redo re-read both.
- AppearanceCache in pdfcraft-annot: built display appearances are reused
while no annotation edit runs (content edits leave every annotation
dictionary alone); cleared on comment, structural and full refreshes.
set_appearance is split into build/apply halves so a hit skips the draw.
- check_pages in pdfcraft-render: light post-edit validation.
- Viewer: single-page content edits invalidate only their page
(page_changed); other pages keep tiles, text and thumbnails.
- XFA dataset sync now also runs for content edits.
Co-Authored-By: Muse Spark <[email protected]>
* Fonts added by a content edit show in Properties and doc_info (read again on demand); form edits clear cached appearances too
* M14: DATE_LANGUAGES is a static (21 entries trip clippy's large_const_arrays); rustfmt the Greek entry
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Preferences scroll in a short window; tests scroll to controls below the fold (#487's roomier rows); the highlighter test waits for its mark under load
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Select tool: a multi-click selects only on the frame egui reports it, so a cleared selection (or the highlighter's marked word) isn't selected again
The highlighter test now clicks at a human pace, with frames in between, which is what exposed it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Muse Spark <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* 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 <[email protected]>
* 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 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* 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 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* 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 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M14: Italian catalog review: meaning, agreement, consistency (47 entries)
A native-speaker review of it.tsv, written from the English meaning (no
other product's strings consulted).
- Meaning: "Margins (in)" is inches (Margini (pollici)); Clear Recent Files
empties the list rather than deleting files; the comb field ("Suddividi
in", "Caselle per carattere"); rich text formatting is not RTF;
checkboxes and style values read as options, not commands; the
position fields measure from the bottom-left corner; "checked"
properties are the ticked ones.
- Agreement: counts that read wrong with 1 ("1 selezionati", "Sbloccati 1
file") become "Label: {n}"; sentences with {kind}/{tool}, which can be
feminine, no longer use masculine pronouns; plural "revisioni", "per 5
anni", subject agreement in two messages.
- Consistency: “ ” quotes and straight apostrophes throughout; "carattere"
rather than "font"; "combinare" for Combine; "email"; "Contributori";
"Adatta all'area visibile"; progress messages in the impersonal form;
quoted option names match their labels; "Più pagine per foglio" for the
print layout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Italian: one row for Unlocked {n} of {m} files (the review's wording)
---------
Co-authored-by: magdale76 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M14: complete the Czech (cs) interface catalog
Czech covered the menus and a few later additions (167 keys). The catalog
now has every key the code uses (2,194): Home, All tools, panels, dialogs,
notices, diagnostics and history labels. Translated clean-room from the
meaning of the English labels; the 167 existing entries are unchanged.
- The @plural messages use the three Czech forms (one | few | other);
other counted messages use count-neutral wording ("Vybráno stránek: {n}").
- "calendar date" months are genitive ("9. října 2026"); "Undo {action}"
and "Redo {action}" are whole phrases ("Zpět: {action}").
- Labels the code joins with a leading or trailing space keep it.
- The key-name entries (context "key") are kept as they were.
Tests that relied on the incomplete catalog:
- czech_covers_every_menu_label keeps a short list of strings spelled the
same in Czech (product and format names, Text, Index, Metadata, Model,
templates such as "{0}: {1}"), compares leading and trailing spaces with
the source instead of forbidding them, and requires a catalog without
parse errors.
- date_format.rs expected the English fallback "Same as interface
(Čeština)" under the Czech interface; that label is now translated.
ATTRIBUTION.toml: new SHA-256 of cs.tsv.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M14: check the Czech catalog like the other complete catalogs
Czech now has the same coverage gates as the complete catalogs:
- czech_covers_commands_and_catalogue: every registered command, menu
title and All tools group, section and item;
- czech_covers_ui_literals: every tl!("…") literal in the UI source;
- czech_plurals_dates_and_history_labels: 1/3/5 pages and 0 fields, the
genitive date ("9. října 2026"), and Undo/Redo/Change history labels
that keep captured names.
cs also joins the lists in missing_ocr_models_messages_are_translated and
bulk_field_properties_labels_are_in_every_full_catalog.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n (cs): terminology review fixes
An independent review of the Czech catalog found 5 entries with a wrong
or misleading meaning and 44 that read unnaturally or inconsistently.
Meaning:
- US formats say so: "Čísla sociálního zabezpečení (USA)" (not the Czech
birth number) and "Telefonní číslo (USA)" for the field format.
- Mouse Exit/Enter triggers: "Odjetí kurzoru" / "Najetí kurzoru".
- Rich text in form fields is not RTF; Form XObjects are not forms.
Wording and consistency:
- Home: pinning folders refers to "úvodní obrazovka" (also in the existing
"Pin folder to Home…"); "úvodní stránka" reads as the first page.
- Callout is "Popisek se šipkou"; Self-signed is "Samopodepsaný" (fixes
the agreement in "Samopodepsaná ID").
- Reset (forms), Apply redactions, guided actions, the radio button and
checkbox styles, line endings (also the existing arrow entries), border
styles, "Omezit na … znaků", "Rozlišovat velikost písmen", grammar in
several messages, and the About window wording shared with the other
Crafting Apps.
History labels for the Creator and Producer document information keys
use the Document Properties wording: "Aplikace" and "Producent PDF".
ATTRIBUTION.toml: new SHA-256 of cs.tsv.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* contributors: add my own people.toml entry
Own entry only, as AGENTS.md describes: credited by the name on my public
GitHub profile (sync_github_name), otherwise by @username.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n (cs): translate "Offset from centre" (watermark offset, #1076)
New upstream string added after the catalog was last synced.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
* i18n (cs): follow the reworded Combine files strings (#1032)
Upstream generalised the Combine files dialog (images and text files, a
prompt for files that can't be read) and replaced six strings. The old
Czech entries are dropped and the ten new ones translated.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
* i18n (cs): translate the new Enhance page / camera image strings (#1027)
Four strings added upstream after the catalog was last synced.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
* i18n (cs): margin labels Top/Bottom read Nahoře/Dole
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n: translate the Digital ID errors, the Signatures panel and key usages
The digital-ID dialog printed its validation errors, the Signatures panel
headings ("Rev. n: Signed by ...", "Unsigned signature field"), the
certificate issuer line of the signed-document bar and the nine key-usage
names in English. Translate them at the point where they are drawn, the way
title() and notify_fmt() already do, so engine and OS error texts that
share those paths pass through unchanged. Czech catalog entries added; other
languages fall back to English.
The signature appearance preview is left alone on purpose: it mirrors the
Helvetica text the engine bakes into the PDF ("Digitally signed by", "Reason:"),
which is document content, not interface.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n: translate form action, print, panel layout, rendering and OCR language texts
Route the remaining plain-English messages through the translation lookup
where they are drawn: the Add-action validation hints, the Cut and stack
print error (preview and notice), the two panel-layout notices, the
"Rendering..." placeholders on unrendered pages, the page number under the
Page Boxes preview (reuses "Page {label}") and the OCR language name.
The messages of the docking validator and the engine and OS errors stay
English: they report internal invariants or text this code does not own.
"My stamps" stays too: it is stored as the category of the user's stamps, so
translating it would split a library when the language changes. The dynamic
stamp chip text mirrors the English text baked into the stamp and is left as is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n (cs): translate the Appearance preferences and the wheel scroll speed
The strings added upstream with the Appearance section and the mouse wheel
scroll speed setting were missing from the Czech catalog, so
czech_covers_ui_literals failed after the merge.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n: use plural forms for counts built in code
The saved notice after Optimize ("3 images optimized", "2 items discarded"),
the pixel sizes in Export all images, the exact size in bytes (human_size and
the document properties) and the history label of a multi-field delete were
assembled from English words and a trailing "s". Use trn() for them, with
@plural entries in the Czech catalog (1 | 2-4 | 5+), and translate the
"Delete N fields" history label in action_label(). English output is unchanged;
other languages without a plural entry keep the English wording.
The Czech plural test now also checks bytes, optimized images and the
Delete N fields label.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n: translate the text-box and organize toolbar tooltips
"Done adding text" and "Discard this text (Esc)" (the buttons under a new
text box) and the Rotate tooltips of the organize toolbar were passed to
icons::button as plain English. Translate them at the call site, as the
other icon buttons do.
The two text-box tooltips are tl!() literals, so every complete catalog
(ar, bg, cs, de, fr, hu, it, ja, pt-br, ru, uk, zh-hans) has to carry them;
the entries for the languages other than Czech are AI drafts that want a
native speaker's look. The Rotate tooltips already had translations.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Losminek <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M3: add cursor zoom and temporary mouse-button navigation on Windows
Anchor Ctrl-wheel zoom to the cursor; share bounded, immediate ScrollArea pan between the middle button and Space + primary drag. Preserve ordinary scrolling and editing, cancel capture on focus/document changes, and initialize numeric zoom before the viewport exists.
Validation on Windows x64: formatting, application check and workspace Clippy pass; full workspace tests pass (928 passed, 4 ignored), including 53 UI unit tests and 27 control integration tests. No branding changes, new dependencies, machine-specific build configuration or local development notes.
* M3: add contributor display name for mhadidi06
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* i18n: let the layers below hand their sentences over, so they can be translated
The accessibility findings, the PDF/A problems, the signature details, the
page-range mistakes and the edit labels were English in all ten languages,
because matching their wording from the UI with a tl! literal would break
silently whenever the engine changed it.
So each layer now hands the wording over instead, apart from the document's
own words: a WORDINGS list of every sentence it can say, with holes in it, and
said(&words), which looks the wording up through the closure it is given and
fills the holes in a single pass. in_english changes nothing, for the CLI and
the tests; the UI passes &i18n::words, a catalog lookup. A wording with no
entry comes back as it stands, which is the English, so the nine catalogs this
does not touch read exactly as before.
a11y::report_html takes a language tag and a words closure, so a saved report
reads the way the panel read, down to its lang attribute. print::PrintError
gained a variant per thing a person can be wrong about, with the numbers apart
from the words; faults rather than mistakes keep their own text.
Spanish translates all of it (107 entries). spanish_covers_what_the_engine_says
requires an entry per wording in the eight lists and
spanish_keeps_the_holes_in_what_the_engine_says requires the holes to survive
the translation, so the catalog cannot fall behind the engine unnoticed.
No new tl! literal, so the Chinese, French and Russian UI-literal gates are
unaffected.
Co-Authored-By: Claude Opus 5 <[email protected]>
* Sign tests from #1099: compare against the &'static str kinds
* Camera tests: undo returns a Label now
---------
Co-authored-by: Fernando Areán <[email protected]>
Co-authored-by: Claude Opus 5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Give dialogs roomier text fields and padding
Single-line fields in dialogs and side panels were only as tall as one line of text, and modals used the popup frame's tight margin. Fields are now 30 points with more inset, and every modal shares one padded card so titles and buttons sit clear of the edge.
* Keep long labels inside their rows and dialogs
Painted names ran past the edge of fixed rows, and a page hover tip was drawn above open dialogs. Labels now end in an ellipsis, tab rows wrap, and those tips stay hidden while a dialog is open.
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Issue #432: global Units preference (mm, cm, in, pt), defaulting to the system region
Preferences ▸ Documents and view ▸ Units sets the unit for page sizes
(Document Properties, Print), Header & Footer margins, the start of Set
Page Boxes and the Measure tool on pages without a viewport scale. On
first start it follows the system region: AppleMeasurementUnits on macOS,
Region ▸ Measurement system (iMeasure) on Windows, then LC_ALL /
LC_MEASUREMENT / LANG; inches for US, LR and MM, millimetres elsewhere.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Issue #432: macOS falls back to AppleLocale when the measurement system was never set
AppleMeasurementUnits only exists in the global domain once someone changed
Measurement system in System Settings, and apps started from the Finder get no
LANG, so a German Mac with untouched settings landed on inches. The region of
AppleLocale (always present) decides next; AppleMeasurementUnits still wins.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu
* Issue #432: Units labels in the Arabic and Italian catalogs new on main
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01REz7wTfQgnopyvN9eQJJVu
* Issue #432: the Print dialog's PDF page size follows the Units preference
Main's new "PDF page: {w} × {h} in" label was always in inches. It now uses
the preferred unit like the sheet size next to it; each catalog keeps its
wording with the size in place of the inch values.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Tristan von Kries <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* fix(theme): complete Linux System appearance tracking
Follow up on #881 for #446 with the standard numeric appearance portal setting
and event-driven updates that wake idle windows. Keep manual modes independent,
cache their desktop updates without repainting, and cancel the worker on close.
Retain inherited native window appearance and populate both egui style branches
with the chosen palette. Preserve saved System, the Light default and unknown
source fallback, and avoid desktop session access in headless UI contexts.
Assisted-by: Codex (gpt-6.1-sol)
* Desktop theme: keep the GNOME colour-scheme fallback XFCE needs
The portal tracker read only org.freedesktop.appearance, which XFCE's portal
doesn't answer (it proxies the GSettings schemas). It now also reads and
subscribes to org.gnome.desktop.interface color-scheme and uses it when the
freedesktop setting states no preference, as main's reader did. KDE answers
only the freedesktop one, which wins when it states a preference.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* tests: consolidate safe ui-egui integration targets
* Group the safe UI tests as tests/ui_group/ instead of turning off test discovery
With autotests = false, a test file added later builds only if someone also lists it in
Cargo.toml, so new tests could silently never run. A tests/ui_group/main.rs target groups the
same 15 files while Cargo keeps discovering every other file in tests/.
---------
Co-authored-by: Brandon Thomas <[email protected]>
A PDF protected with only a permissions (owner) password opens without
asking, restricted, and nothing let the user enter that password
afterwards: the security notice only led to a read-only Security tab and
the hint said to reopen the file with the password, which the app had no
way to do (#995, #1077).
Now the security notice and Document Properties > Security offer Unlock...,
and Change settings / Remove security on a restricted document ask for the
permissions password first and then carry on, as Acrobat does. The open
password is refused there.
- crypt: SecurityHandler::authenticate_owner accepts only the owner
password and refuses a different file key.
- cos: Document::unlock upgrades the open document in place; owner and user
passwords unlock the same file key, so nothing is decoded again.
- engine: Session::unlock (not an edit: no undo step, not marked unsaved)
also unlocks the undo and redo states and reopens saves as owner;
Document::can_unlock, EditError::WrongPassword, command protect.unlock.
- ui: the permissions password prompt shares the open prompt's modal; the
notice and Security tab get Unlock...; the disabled-command hints no
longer point at reopening the file.
- i18n: the new strings in all 15 full catalogs (catalog hashes updated).
Tests cover crypt, cos and engine (all four algorithms, wrong and open
passwords, undo, redo, save, removing security afterwards) and the UI
(notice flow, Remove security asking first).
Closes#995Closes#1077
Co-authored-by: Brandon Thomas <[email protected]>
* Forms: keep tiles and render unbaked field values during page re-render
In complex interactive forms, switching focus away from an edited field
caused its text to disappear for several seconds while background tiles
re-rendered.
- In DocView, retain existing tiles as stale rather than immediately
purging them on page_changed so the canvas remains sharp without flashing.
- Prioritize visible tiles over backdrops in the render queue.
- Track unbaked form fields and paint an immediate egui widget preview
for committed text, checkbox, and radio inputs until fresh raster
tiles arrive.
- Scope page invalidation on form edits to pages containing the edited
or calculated fields.
- Hoist fields lookup in recalculate_with and apply_changes to eliminate
repeated parsing.
- Run JavaScript scripts via catch_unwind on the current thread instead of
spawning a new thread per script invocation.
- Add contributor entry for Piohrun in contributors/people.toml.
- Add test switching_fields_keeps_committed_text_visible_immediately.
* Forms: address review feedback on tile stale tracking, hoist, and diff invalidation
- Revert crates/js to preserve dedicated 1 GiB stack thread for boa scripts.
- In DocView::page_changed, purge non-current-tag tiles and mark only current-tag tiles stale.
- Restore demand queue order in prepare_render_queue.
- Add unbaked overlay entries only on successful edit apply and only for fields whose values changed; fix font zoom calculation on 90°/270° pages.
- Re-read fields(doc) in recalculate_with only when script returns changes.
- Diff form before and after edit in apply_edit, invalidating pages with changed fields, falling back to document_changed for XFA or page-count changes.
- Add tests covering script recalculation side effects across pages, unbaked overlay cleanup on tile delivery/zoom/rejection, and checkbox recalc chaining.
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Fill & Sign: scale down large signature images instead of rejecting them (#1075)
A 6025 x 2506 PNG signature failed with "Image size exceeds limit": imports
were capped at 4096 px per side and 4 megapixels, and the decoder's limit
error leaked through raw. Sources up to 8192 px per side and 32 megapixels
are now decoded and scaled down to fit the old bounds; anything larger
reports the normal size message.
Reported-by: @HassanAlthaf
Co-authored-by: mjohnson7cd <[email protected]>
* A placed image too large to preview gets its own message; scale with thumbnail, which needs far less memory
---------
Co-authored-by: mjohnson7cd <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* fix(create): cap decoded size of imported PNGs
A PNG's declared dimensions sized an infallible allocation, so a few-byte file claiming huge dimensions aborted the process when the allocation failed. Cap the decoded size at the TIFF ceiling and reserve every input-sized buffer fallibly, returning CreateError::Image instead. (Security audit run-2: v3-024.)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* fix(xfa): bound total picture bytes kept during layout
Each laid-out picture kept its own copy, so one picture in a repeating subform multiplied without bound and could exhaust memory on open. Add a per-layout aggregate cap (256 MiB) that stops with XfaError::TooLarge. (Security audit run-2: v3-103.)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* fix(xfdf): bound XML nesting and protect redaction marks on import
Deeply nested XFDF/XML overflowed roxmltree's recursive parser and aborted the process; a non-recursive depth scan now rejects it before parsing. Imported comments could also replace a locked or redaction mark by /NM or add a hidden one; place() no longer replaces locked/Redact annotations and clears the hidden/invisible flags on imported redaction marks. (Security audit run-2: v3-105, v3-111.)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* fix(sign): classify post-signing changes by the signed revision's roles
A signed object's modification was classified from labels in the attacker-written new version, so relabelling a signed page /Type /Sig (or giving it /ModDate), or relabelling a drawn stream /ObjStm or /XRef, let a forged page validate as Valid even under DocMDP P=1. Decide an object's role (page content, signature, metadata, file plumbing) from what it was in the signed revision, not its new dictionary. (Security audit run-2: v3-082, v3-083.)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* fix(redact): remove redacted content from the saved file, fail closed otherwise
Apply Redactions and Remove Hidden Information left the originals behind: dropped or cleaned image/form XObjects kept their resource names (so the full rewrite saved the originals), an XFA form kept the redacted value in its datasets packet, and vertical-writing text under a mark was never hit by the horizontal removal geometry yet reported success. Prune replaced XObject names the page no longer draws, drop /XFA and /NeedsRendering when redacting an XFA form, and fail closed (RedactError::VerticalText) rather than silently leave vertical text. (Security audit run-2: v3-064, v3-067, v3-062.)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* fix(ui-egui): trust the displayed signer, not the document's embedded root
The Signatures panel's one-click 'Add to trusted certificates' trusted the top of the chain the document supplied, installing the author's self-signed root as an anchor instead of the signer shown. Trust the displayed signer certificate. (Security audit run-2: v3-098.)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Use main's decoded-size ceiling for PNGs too; keep the depth tests on main's checker
---------
Co-authored-by: Claude Opus 4.8 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M10: enhance camera images: straighten a page from four corners (ocr.camera-perspective)
Four corners on a page (any order, a convex quadrilateral) are warped to an
upright rectangle that replaces what the page shows, read at the page
picture's own resolution (at most 600 dpi) and kept at that physical size.
One undoable step through Replace Pages, so comments, links and bookmarks
stay.
- geom: a pure 3x3 homography solve and corner ordering (no dependencies);
collinear, concave and non-finite input gives None.
- engine: camera::warp (bilinear, over white, capped at 40 MP) and
Session::enhance_camera_page.
- automation: page_enhance_camera (corners in top-left-origin points).
- parity: ocr.camera-perspective is partial; the UI handles, automatic edge
detection and contrast cleanup are still missing.
Refs #1008.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M10: Enhance camera image: drag four corners on the page, then Enhance page (ocr.camera-perspective)
Scan & OCR ▸ Enhance camera image (ocr.enhance_camera) shows four corner
handles on the current page, inset 5% from its edges, with what will be cut
away dimmed. Enhance page straightens what they enclose through
Session::enhance_camera_page (one undoable step); Cancel and Esc leave the
page alone. The buttons float at the top right, clear of a centred page's
corners, and the instructions are a notice, as for Crop.
A command of its own, beside Enhance scanned file (ocr.enhance), which
stays for scan correction (#1012).
- ui-egui: camera_ui (handles, dimming, bar), QuickTool::EnhanceCamera,
quick_tool "enhance-camera" for ui.state and the shot example.
- Catalogs: the three new labels in every language with UI coverage
(plus es, pl, zh-hant); ATTRIBUTION.toml hashes updated.
- automation: page_enhance_camera is linked to the command.
- parity: ocr.camera-perspective lists the command and the UI test.
Refs #1008.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M10: Enhance camera image finds the document's edges (ocr.camera-perspective)
The corners now start on the photographed document's edges when they can
be found, else inset 5% as before.
camera_edges::detect_document, pure Rust and deterministic: shrink to 400
px, blur, Sobel edges thinned and thresholded against the 98th percentile;
a Hough transform where each edge votes within 6 degrees of its own
normal; pairs of near-horizontal and near-vertical lines make convex
quadrilaterals (at least 12% of the picture), kept when every side lies on
a matching edge along at least 55% of its length and scored by area times
the square of the mean support; each side is then refitted through its
edge points (total least squares). None when nothing convinces.
- Session::detect_camera_corners (user space); page_enhance_camera finds
the corners itself when `corners` is omitted and reports the corners it
used and whether they were detected; the UI command starts from them.
- Tests on synthetic photos: plain table, text on the sheet, wood grain
with a shadow, strong perspective, a 30 degree turn, a sheet running off
the picture, no document, white on white, bad input; end to end through
the engine and the tool.
- Parity notes: what is still missing (turning the result upright, rounded
corners, clean-up).
Refs #1008.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M10: after Enhance page, rotate, crop or organize the page (ocr.camera-perspective)
Enhance camera image no longer closes on Enhance page: the bar then offers
rotate counterclockwise and clockwise for the enhanced page (one undo step
each), Crop pages and Organize pages (which leave the tool for those
tools), and Done; Esc leaves too. A photo of a card often comes out a
quarter turn off until the page is turned upright automatically.
The two icon buttons are named for screen readers. Every label already
exists in the catalogs.
Refs #1008.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M10: Enhance camera image cleans the photo up and chooses its look (#1034)
After Enhance page the bar offers a look (Original, Colour, Greyscale,
Black and white) and a Clean-up slider (0-100, default Colour at 50).
Choosing a look or letting go of the slider enhances the page again from
the straightened photo and turns it as before, so the history keeps one
enhancement (and one turn), never a pile of them.
camera_clean, pure Rust:
- clean: the paper's illumination is estimated per channel on a coarse
grid (each block's 90th percentile, lifted to the 75th percentile of the
5x5 blocks around it so a stamp or picture isn't taken for paper, then
smoothed) and divided out, so casts, shadows and darker edges go and the
paper turns white; a contrast stretch grows with the level, and from 60
isolated dark pixels are removed. Colour ink stays.
- apply(look): Original is the photo as taken; Greyscale is the cleaned
page in grey; Black and white evens the lighting fully, thresholds the
whole page (the level sets the threshold: higher keeps fainter strokes)
and despeckles. Black and white is stored as PNG (lossless): JPEG would
smear grey around every letter.
- Session::enhance_camera_page_at(look, level); page_enhance_camera takes
`mode` (original, colour, greyscale, bw) and `level` (0-100), refusing
anything else.
- "Clean-up", "Original", "Greyscale" and "Black and white" in every
catalog with UI coverage, plus es, pl and zh-hant.
- Tests: shadow and yellow cast become white paper with dark text, a blue
stamp stays blue, strength grows with the level, specks go but text
stays, the four looks (black and white is only black and white, the
threshold moves with the level), bad sizes; a shadowed sheet through the
engine; level and mode through the tool; the slider and the looks
re-enhancing from the photo and keeping the turn.
- Parity: ocr.background-removal, ocr.edge-shadow-removal and
ocr.despeckle are partial (camera images only).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M10: Enhance camera image cleans the photo up and chooses its look (#1034)
After Enhance page the bar offers a look (Original, Colour, Greyscale,
Black and white) and a Clean-up slider (0-100, default Colour at 50).
Choosing a look or letting go of the slider enhances the page again from
the straightened photo and turns it as before, so the history keeps one
enhancement (and one turn), never a pile of them.
camera_clean, pure Rust:
- clean: the paper's illumination is estimated per channel on a coarse
grid (each block's 90th percentile, lifted to the 75th percentile of the
5x5 blocks around it so a stamp or picture isn't taken for paper, then
smoothed) and divided out, so casts, shadows and darker edges go and the
paper turns white; a contrast stretch grows with the level, and from 60
isolated dark pixels are removed. Colour ink stays.
- apply(look): Original is the photo as taken; Greyscale is the cleaned
page in grey; Black and white evens the lighting fully, thresholds the
whole page (the level sets the threshold: higher keeps fainter strokes)
and despeckles. Black and white is stored as PNG (lossless): JPEG would
smear grey around every letter.
- Session::enhance_camera_page_at(look, level); page_enhance_camera takes
`mode` (original, colour, greyscale, bw) and `level` (0-100), refusing
anything else.
- "Clean-up", "Original", "Greyscale" and "Black and white" in every
catalog with UI coverage, plus es, pl and zh-hant.
- Tests: shadow and yellow cast become white paper with dark text, a blue
stamp stays blue, strength grows with the level, specks go but text
stays, the four looks (black and white is only black and white, the
threshold moves with the level), bad sizes; a shadowed sheet through the
engine; level and mode through the tool; the slider and the looks
re-enhancing from the photo and keeping the turn.
- Parity: ocr.background-removal, ocr.edge-shadow-removal and
ocr.despeckle are partial (camera images only).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Camera level: when another edit broke the run, redo what was undone instead of warping the page again
---------
Co-authored-by: magdale76 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M10.5: Restore architectural paper presets and PDF scale controls
Port the missing paper catalog and scale controls from PR #599 onto the merged Windows printing implementation. Keep the spooler and background discovery from #847/#1007. Document the feature-by-feature audit and verification limits.
Assisted-by: OpenAI Codex
* Drop the provenance write-up and the screenshot example (the kittest covers the dialog)
* Refresh catalog hashes
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Cap editing and text-layer caches that grew without bound
The cos editing cache kept every touched object until save, so editing
a large document pinned its whole object graph. Bound it like the scan
reader: 1024 objects / 32 MB of owned bytes, 256 object streams /
32 MB, evicting to the newcomer. Returned objects outlive eviction, and
clones share the caches, so this only costs re-parsing.
The viewer text cache had the same shape: every visited page's text
stayed for the session. Retain visible, selected and matched pages plus
a 24-page prefetch window each frame; evicted pages re-extract on
revisit. Skipped while a search is active, where demand still wants
every page. Failures are never evicted: they cost nothing to remember.
cli info on a 1024-page/147 KB file (debug build, same host/command):
before 66.7 MB RSS, after 54.0 MB RSS (3 runs, tight).
Tests: editor count bound, byte bound, snapshot-clone reads (cos);
evict-far/keep-visible+matched+selection, search-active keep-all
(canvas). All mutation-checked: each bound goes red when disabled.
Signed-off-by: Chiranjeevi U <[email protected]>
* Index form fields once per refresh instead of once per field
The form-scope refresh ran a linear find over all fields for every
field, O(F^2) on each keystroke; forms reach 10k fields. Build one
name index up front, O(F). entry().or_insert() keeps the old
first-match semantics for repeated field names.
New test with two same-named widgets pins first-wins and goes red
under insert. 141 engine tests green, clippy warnings and perf lints
clean.
Signed-off-by: Chiranjeevi U <[email protected]>
---------
Signed-off-by: Chiranjeevi U <[email protected]>