Alt-drag, Cmd+Opt-drag and Free Transform on a copy duplicate with
`inPlace: true` for every layer, but the flag only changes anything for
an artboard. With in-place duplicates now refused on strict .atn export,
a recorded copy of a plain layer could no longer be exported, although a
plain Duplicate replays it exactly. Recording now drops `inPlace` when no
copy is an artboard, as `layer.select` already rewrites its recorded
params.
Refs #7200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Photoshop's Duplicate Layer has no in-place flag, so `layer.duplicate
{"inPlace": true}` was exported as a plain Duplicate and replayed with
the artboard copy beside the original. Strict export now fails on it and
skipUnsupported reports it, as for a Close with a document target.
Fixes#7200
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(filters): Lens Blur reads its depth from an alpha channel, listed in Depth Map › Source as in Photoshop (#7400)
* test(filters): Lens Blur depth from an alpha channel by name or index, its errors, and the Source menu (#7400)
Copy refused fill and adjustment layers ("the active layer has no
pixels"). With no pixel selection they are now taken whole, like smart
objects, type, shape and group layers, and Paste recreates them in this
or another document. An adjustment layer's clip spans the canvas, so
paste placement and the OS-clipboard mirror stay consistent.
Scorecard UI-217-23 note, gaps.md G9 and target-app-parity.md updated;
the row stays partial (a single pixel layer still copies pixels only).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Edit > Preferences > Enhanced Controls > Enable Gestures (enhancedControls.touchGestures)
was saved but read by nothing, so it was hidden from the dialog. Now, with it off, two
fingers on a touch screen no longer zoom the canvas or the Navigator, nor turn the view.
The mouse and trackpad behave as before, including a trackpad pinch or Rotate event that
arrives while fingers rest on the screen.
- wheel_nav: pinch_factor() drops egui's multi-touch zoom when gestures are off and keeps
the trackpad's own Event::Zoom (Windows' folded Ctrl + wheel pinch included);
read_with() takes the preference.
- rotate_view: trackpad_delta() only falls back to the multi-touch rotation while gestures
are on; Rotate events always count.
- canvas, Navigator: pass the preference through.
- prefs: touchGestures leaves HIDDEN_UNTIL_IMPLEMENTED, so the dialog shows it.
- Tests: synthetic two-finger pinch and twist, on and off, and the trackpad events with
gestures off.
- docs/scorecard.md regenerated: settings that do nothing 26 -> 25 of 168.
* fix(print): the last print's settings are kept in the preferences, so Print opens with them after a relaunch (#7410)
* test(print): the last print's settings survive a relaunch for Print and Print One Copy (#7410)
When the crop quad's horizon crossed the layer and most of the layer
(or its centre) lay beyond it, warp picked the source's majority side
and skipped every output pixel, so the crop came out blank. A clip
wholly on one side of the inverse horizon only ever samples that side,
so draw it; a straddling clip keeps the majority rule (#4863).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The target size was read with as_u64 only, so 100.0 (json.dumps of a
float, many agent clients) was refused as not a whole number, while the
crop rectangle and image.imageSize accept it. Fractional, negative,
zero and out-of-range values are still refused.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(fade): detect uniform mask changes and blend full canvas
* test(fade): cover uniform mask inversion and Fade across bit depths
* style(fade): keep default-pixel comparison readable
* Format edit_menu_cmds.rs with rustfmt
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>
Typing or pasting over a selection now takes the style of the first selected character instead of the one before it, so retyping a smaller second line no longer takes the first line's size from the line break. Fixes#7413.
* file.saveACopy refuses the document's own file without "overwrite" (#7437)
Save a Copy through the engine (control channel, MCP, serve, headless
action playback) silently replaced the open document's own file when
given its path, so a lossy re-encode could not be undone. It now returns
BadParams when `path` is the active document's file (same text, ./ or ..
spelling, hard link, case on Windows via same_file) unless the params
pass "overwrite": true, and nothing is written. The desktop dialog writes
through the shell after the OS confirms a replace, so it is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* same_file: compare file identity on Windows, so a hard link is the same file
On Windows `same_file` compared canonical path strings, so a hard link to
the input (another name for the same file) passed the new convert guard
without --in-place. It now compares the volume serial number and file index
(the `same-file` crate, already in the dependency tree; std's file_index is
unstable), as unix compares dev and inode, falling back to the
case-insensitive canonical path when a file won't open. The engine's
same_file test now checks hard links on every platform. (Review on #7454.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* saveACopy own-file test: hard link on every platform, doc state unchanged after a refusal
Each refused copy (same path, ./ spelling, hard link) now also checks the
document keeps its path, revision and unsaved state. The hard link is no
longer unix-only: same_file compares file identity on Windows too. (Review
on #7456.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sampler points are pixel indices, but rotate_arbitrary mapped them as the
pixel's top-left corner while the warp moves pixels by their centres, so
after a turn a point sat one pixel off its pixel, or fell off the canvas
(a corner point at 90° or 180°). Map the pixel centre, as #6720 does for
flips and right-angle turns; the turned Crop shares this map.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
rotate_arbitrary read `angle` as f32. rotated_crop sizes its canvas with
rotated_size at f64 and passes the angle through JSON; near a whole-pixel
ceil boundary the f32 angle grew the canvas 1 px more (1000x1000 at
0.11470651449337071: 1003 instead of 1002), shifting the crop by 1 px.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A fill layer from a PSD keeps its stored pixels as a fill cache that covers
only the old canvas. Canvas Size, an extending Crop and Reveal All translated
that cache but kept it, so the added area of the fill layer stayed
transparent. Drop the fill caches whenever the canvas grows, so the fill
re-renders from its model over the whole canvas, as Photoshop does (Free
Transform and Perspective Crop already drop it).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The copies were made with Document::new and kept the mode but not the
duotone inks, so a Duotone scan came out as plain gray. Copy them, and
grey the command out for Indexed Color documents, as Photoshop does.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/commands.rs:117-120 (turn) with crates/engine/src/canvas_geom.rs:224 (Turn::affine); applied in crates/engine/src/lib.rs:807-814 (Session::edit)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Edit > Presets > Preset Manager renamed and deleted brush presets directly, so a rename
left the current preset pointing at the old name (brush.presets.update then failed), accepted
a case-insensitive duplicate name, and a delete left the current preset dangling. It now uses
the same rules as brush.presets.rename / brush.presets.delete through two small shared
helpers (rename_at, forget_deleted_preset). Patterns and custom shapes are unchanged.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found while testing crates/engine/src/notes_cmds.rs:105
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Snap To Layers built each layer's edges and centre from transform_bounds, which clips the
layer to the selection. With a selection, every layer's targets were the selection's own
bounds, so dragging the outline with a marquee snapped it back to where it started (shown
as layer alignments) and beat nearby guides and document edges. Layer targets now use the
layer's whole content bounds, as in Photoshop; the moving box (layer_rect) is unchanged.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Covers #5044's two names: BMDoHyeon (family "BM DoHyeon", guessed "BM Do Hyeon") and Daum_SemiBold (guessed "Daum_Semi Bold"). Both resolve to their installed faces and aren't reported missing on main, through the exact PostScript lookup (#1844) that missing-font checks follow since #608.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>