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>
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>
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
PR #11848 built the full-area selection mask for every Subtract and
Intersect click, though the global match (Contiguous off) ignores it.
Build it only when the fill is contiguous.
Tests for the review: Contiguous off keeps the global match, Subtract and
Intersect without a selection select nothing, the row check fails on a hole
in the selected run, and the fill's half-selected threshold (0.49 vs 0.5)
at the seed and at the fill's edge.
Co-Authored-By: DuBearte <130190089+DuBearte@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Destination branch of the Patch command no longer clones the whole document: it takes only the selection before the target surface borrows the document, and computes the selection's content bounds once, passing them to `is_hard_rect` and reusing them for the translate. Behaviour is unchanged.
Tests are split and extended: Destination with a feathered, ellipse, lasso and holed-rectangle selection leaves the selection in place while the patch still lands; Destination with no selection fails, adds no history entry and a following undo reverts the previous step, not the setup; redo restores both the patched pixels and the moved selection; Source and Content-Aware are separate tests, the latter asserting a pixel change; the main test also asserts source pixels unchanged, the destination carrying the source texture, the moved selection still hard-edged, and the returned damage and offset. A rectangle larger than the patched area and a partly covering selection without a hole cannot be built, because the patched area is the selection's own content bounds; an inverted selection makes the area the whole canvas, which Patch refuses.
Verified: `cargo test -p photocraft-engine --lib patch` 18 passed; `--lib retouch` 119 passed; clippy `--all-targets -D warnings` clean; `cargo fmt -p photocraft-engine` clean.
Not changed: the coverage pass in `is_hard_rect` (one pass over the patched area per command, the same order of work as the patch itself).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 wrote at medium
trim_name drops white space, ZWNJ/ZWJ and the blank characters (Braille blank, Hangul fillers,
invisible operators, tag and other default-ignorable blanks) from both ends. A variation or
Mongolian free variation selector or CGJ that belongs to a kept character stays, and so do Hangul
filler jamo that complete a syllable. History names use the same trim as plug-in names.
A manifest name such as "\nTone" was checked trimmed but shown as given, so a line break reached
the menu; padding also got past the 64-character limit. The name is now stored trimmed.
The blank Braille pattern and the unassigned default-ignorable ranges no longer count as visible,
in plug-in names and History names. Applying a patch no longer takes the selection icon, and the
History source column has its doc comment back.
A historyName of zero-width, filler or BOM characters showed an empty History
row, and a bidi control or line separator could make a row read differently
from its name. A history name now has no control or line-break character
anywhere, loses white space and ZWNJ/ZWJ at its ends, and is then held to the
plug-in menu-label rule, now shared as
photocraft_plugins::manifest::printable_name (1-64 characters with a visible
one, no hidden format characters; plug-in names also refuse U+2028/U+2029).
A Mixer Brush stroke in an Indexed Color or Bitmap document now fails before
anything is painted, with the reference's alert texts (convert to RGB Color /
Grayscale mode to edit); paint.mixerBrush returns that error and leaves the
document and History unchanged. A press on the canvas shows it as an Error
alert instead of a status-bar message. RGB, CMYK, Lab and Grayscale still
paint.
A plain rectangular selection is translated by the drag offset in the same history state as the pixel change. Source, Content-Aware, feathered and non-rectangular selections keep today's behaviour.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
When an earlier document is held for its profile prompt and the next open skips the prompt, admission numbers the second document photo-1 again, but the reply reported the name read before admission (photo-2). The reply now reads the name from the admitted document.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
paint.colorReplacement and its live stroke refuse the modes and depths
the reference application refuses, with its words: 32 bit per channel
RGB, Grayscale and Bitmap (only full color modes), and Indexed Color.
Nothing is painted and no history is added. RGB/8, RGB/16, CMYK and Lab
paint as before; cases that weren't measured keep today's behaviour.
CodeRabbit round 2 on #12212:
- 4504550937 (b): a PSD that recovered from damaged data is held for File > Open's confirmation (the app passes confirmDamage; headless callers open it as before).
- 4504550942: a document held for its prompt counts when numbering, and the number is re-taken when each document is admitted, so two held files with one stem no longer share a name.
- 4504550943 is not changed here (needs colour transforms at regeneration), as are 4504550937 (a), (c) and 4504550953.
- 4504550946: the successful reply carries smartObject, so a document queued behind another open prompt is admitted with the Open As Smart Object history label.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
A Color Replacement click, stroke or Shift+click segment adds one History
state named "Color Replacement Tool", as in the reference application.
The command's label is the same name, so a recorded action's step reads
the same as the History state.
Keeps main's Fade preview path in filter_preview.rs and passes the PR's
tool colours to the non-Fade preview; catalogs keep both sides' rows.
Co-Authored-By: Firstbober <firstbober@tutanota.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
select.lasso takes an optional historyName; the Patch tool sends "Patch Tool" so its
lasso is that step (the Lasso tool keeps "Lasso"). paint.patch records "Patch Selection"
in every mode; the content-aware job keeps its progress text.
Tests: patch_history_names_tests (engine) and patch_tool_history_names_tests (ui-egui).