Keeps the deletion of the in-repo catalogs; #12357's Layer Via Copy/Cut
rows are in craft-i18n.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Instead of turning Anti-alias off, the tests keep the default setup and
expect the reference application's measured results (#11776, #11779):
the selection ends at 381, Subtract at 219, Intersect runs 219-381.
Suggested by hectorviov on #12399.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#12146 gives near-matching neighbours a soft fringe when Anti-alias is on.
The #11848 tests left Anti-alias at its default and expected exact column
bounds, so main's engine tests failed (381 instead of 380).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cause: is_hard_rect read selection coverage over the selection's own content bounds in one buffer. After a transform those bounds can reach far past the canvas, while the patch-size guard only measures the on-canvas part, so a huge mostly-off-canvas selection could stall or exhaust memory.
Fix: is_hard_rect now calls is_hard_rect_within with MAX_PIXELS (the patch's own cap); bounds over the cap are not a hard rectangle and the selection stays in place. Otherwise coverage is scanned in 64-row bands, stopping at the first pixel below 1.0.
Verified: new test covers a limit below and at the pixel count and a hole in the third band; patch (25) and retouch (126) lib tests pass, clippy -D warnings and fmt clean.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 wrote at medium
The engine turned every "mode": "clear" stroke on the Background into
Normal, so a held ~ painted the foreground there instead of erasing to
the background colour as on main. The options bar already turns a chosen
Clear off at the press, so the engine conversion was not needed: drop it
and keep the one check in clear_off_on_background.
A Background with locked pixels refuses the stroke, so a chosen Clear now
stays chosen there. The "Tool state machine" doc line is back on
tool_event.
Co-Authored-By: DuBearte <DuBearte@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests that read a translation call needs_catalogs() first, so a build
without CRAFT_I18N_DIR stays green and English-only; CI embeds the
catalogs and runs them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 34 crates/ui-egui/src/i18n/*.tsv, LICENSE-translations.txt and
catalog.rs now live in craft-i18n, as does the credit for each catalog;
ATTRIBUTION has one row pointing there. build.rs stops deflating catalogs
and the Dockerfile stops copying the deleted licence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The i18n module becomes a thin Lang wrapper over craft_i18n::I18n and the
catalogs embedded from craft-i18n. Plurals use trn_plain and PhotoCraft
keeps its own fmt; @id rows come only from PhotoCraft's own rows that keep
the label's shape. Detection, PHOTOCRAFT_LOCALE, tag bounds and the 35
offered languages are unchanged.
Serbian: sr, sr-RS, sr-Cyrl-RS and sr-ME reach sr-latn, the only Serbian
catalog, and a saved interface.language "sr" migrates to "sr-latn".
The menu completeness test becomes a report that never fails, and
`cargo xtask i18n-coverage` is replaced by `cargo xtask i18n-report`, which
reads CRAFT_I18N_DIR through the crate. Docs describe the new system.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `craft-i18n` crate (craft-libs c5a1cbfb, PhotoCraft's existing pin; already in the tree through
craft-ui) as a ui-egui dependency.
- build.rs writes `CRAFT_I18N` from `CRAFT_I18N_DIR`: one (locale, common.tsv, photocraft.tsv)
entry per locale with a PhotoCraft catalog, paths written with `{:?}` and never canonicalized.
Unset: empty, English only; `CRAFT_I18N_REQUIRED=1` makes a missing checkout an error.
- `craft-i18n.rev` pins storytold/craft-i18n 4bafe3ed (the cutover sync of photocraft@848fa30),
LF through `.gitattributes`.
- Both flatpak manifests fetch craft-i18n at that commit; the source build sets `CRAFT_I18N_DIR`
and `CRAFT_I18N_REQUIRED=1`. A test keeps both manifests equal to the pin.
- ci.yml: the Windows, without-heif and web jobs check out craft-i18n at the pin, as the shared
gate and release workflows do; a pin bump selects the web and Windows jobs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cause: the menu bar, the command labels and the selection tools' canvas menu spelled the two commands "Layer via Copy" and "Layer via Cut", while the reference application (and PhotoCraft's own History names and ATN event table) use a capital V.
Fix: renamed the labels in crates/ui-egui/src/menu_catalog.rs, the two spec! labels in crates/engine/src/cmd/edit_cmds.rs, and the rows in crates/ui-egui/src/canvas/canvas_tool_menu.rs with its test assertions. In the 33 locale files crates/ui-egui/src/i18n/*.tsv that carry the keys, only the key column of those two rows changed (66 rows), so every translation is kept and no language falls back to English. New tests are in crates/ui-egui/src/layer_via_label_tests.rs, registered with one mod line in lib.rs like the other *_tests.rs files.
Verified: the 5 new tests pass; canvas_tool_menu 51 passed; engine edit_cmds 112 passed; i18n 66 passed and 8 live_tests failed with the known local font panic in epaint fonts.rs (font-dependent, green on CI); cargo xtask parity left docs/parity-checklist.md unchanged; clippy -D warnings and fmt are clean. TSV check against HEAD: 34 files, same row count in each, exactly 66 rows differ (2 per file in 33 files, Hindi has none), only the key column of those rows differs, the translation column is byte-identical, no old key remains, and no CR bytes in any touched file.
Not changed: the translations' text, the History names and the ATN event table (already the capital V), docs/ui-parity.md and the other parity docs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 wrote at medium
Conflict in retouch_cmds/patch.rs with #12255 (one History name, "Patch Selection", for every mode): kept the Destination branch that moves a hard rectangular selection with the patch and gave it HISTORY_NAME, and restored Plan's destination flag, which #12255 had removed as unused. patch tests 24 passed (including #12255's history-name test), clippy and fmt clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and resolved
Cause: is_hard_rect required the selection's bounds to equal plan.area, which is clipped to the canvas. A hard rectangle reaching past the canvas (e.g. moved there with select.transformSelection, which keeps the off-canvas pixels) has bounds larger than the area, so the pixels were patched but the selection was left behind.
Fix: treat the selection as a hard rectangle when nothing is selected outside its own bounds and every pixel inside those bounds is fully selected, then translate the whole selection by the drag offset. Feathered, non-rectangular and holed selections still stay in place.
Verified: new test destination_moves_a_hard_rectangle_that_extends_past_the_canvas failed before the change (selection stayed at -20,100..40,140 while the pixels changed) and passes after, including one-undo restore of pixels and selection. cargo test -p photocraft-engine --lib patch (19 passed) and retouch (120 passed), clippy -D warnings and fmt clean.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 wrote at medium