* Fix duplicate gradient preset names on import and restore
* Preserve addressable gradient names during duplicate migration
* Preserve gradient identities during import and restore
* Assert restored selected gradient retains its stops
* Pass replace in the duplicate gradient names test after #10640
Main now imports a re-imported .grd beside its group unless replace is set
(#10640). The test re-imports over the same group, so ask for that.
* Use the same replace default for gradient name allocation and group insertion
After #10640 the group insertion defaults replace to false, but the name
allocation still defaulted it to true, so a default re-import created a
sibling group with the same preset names. Resolve replace once and add a
regression for the default and explicit replace paths.
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* Add headless GPU backend foundation for composition and flat export (issue #8062 PR 1)
* Fix headless GPU readback bounds, API compliance, and error preservation (#8062)
* Fix GPU readback bounds, context thread spawn error, and test parity (#8062)
* Fix BandRenderer Send bound, PNG reborrows, and clippy lints (#8062)
* Fix effect shadow test construction and remove dead code wrappers (#8062)
* Use short reborrows for Headless renderer without lifetime escaping (#8062)
* Fix clippy collapsible_if and ensure multi-band geometry in flat export tests (#8062)
* Fix automation Rect import, add renderer diagnostics, and harden flat export retries (#8062)
* Expose optional GPU rendering in photocraft-cli (#8062)
* Fix GPU feature test APIs and native target gates
* Fix CLI locked metadata, bounded benchmark params, and protocol diagnostics (#8062)
* Verify unsupported GPU effects before testing band seams
* Fix CLI renderer borrows and GPU-enabled test compilation
* Use compositor buffer ownership API after upstream memory accounting change
* Fix GPU CLI lint failures and validate run environment policy
* Update export renderer test helper for owned compositor buffers
* Fix needless benchmark layer name borrow
* Fix headless GPU backend review findings (#10842)
- release a closed document's GPU caches through a BandRenderer hook
- bound the monitored readback wait and report a stalled GPU as device loss
- reject 32-bit float documents before band readback so exports fall back to CPU
- keep previews at 8-bit RGBA and report the palette/Duotone renderer bypass
- retain renderer diagnostics when encoding fails
- strengthen renderer, save-path and GPU diagnostics tests
* Use valid batch arguments and inspect method in GPU CLI tests
* Add GPU close-hook and 8-bit preview regression tests (#10842)
* Fix GPU CLI review findings (#10912)
- guard the benchmark's modulo divisors for one-pixel dimensions
- count a cold export as GPU only when the renderer context was used
- report every batch file's export warnings, deduplicating only GPU diagnostics
- drain renderer diagnostics before propagating convert and run save errors
- cover renderer use in the GPU precedence, bridge and serve tests
* fix(io): correct renderer binding mutability
* test(cli): verify narrow benchmark inputs and GPU diagnostics
* Fix remaining headless GPU review findings (#10842)
- release a document's GPU caches when file.close, file.closeAll or
file.closeOthers removes it through a command
- honor cancellation while band readback waits, polling in 100 ms slices
- fault the device after a readback timeout so later exports skip it
- report single-layer, CMYK ink-mixing, ORA and PDF CPU paths to a
requested renderer
- keep fallback warnings in their export instead of leaking them into the
next one
- requeue renderer diagnostics when a renderer-aware save's final write
fails
- write MCP/serve diagnostics without panicking on a broken stderr
- document the actual colour-space contract and drop F32 from the GPU
depth list
* Fix remaining headless GPU review findings (#10842)
- release a document's GPU caches when file.close, file.closeAll or
file.closeOthers removes it through a command
- honor cancellation while band readback waits, polling in 100 ms slices
- fault the device after a readback timeout so later exports skip it
- report single-layer, CMYK ink-mixing, ORA and PDF CPU paths to a
requested renderer
- keep fallback warnings in their export instead of leaking them into the
next one
- requeue renderer diagnostics when a renderer-aware save's final write
fails
- write MCP/serve diagnostics without panicking on a broken stderr
- document the actual colour-space contract and drop F32 from the GPU
depth list
* Fix remaining GPU CLI review findings (#10912)
- reject non-UTF-8 PHOTOCRAFT_GPU values instead of silently using the CPU
- release a batch item's document caches before taking the renderer to the
next item, and report fallbacks once
- count warm benchmark runs that fell back to CPU in the summary
- write serve diagnostics without panicking on a broken stderr
- assert that the batch test's --gpu policy reaches the renderer
* Use question mark for cancellable readback errors
* Use question mark for cancellable readback errors
* Keep GPU export diagnostics with their attempt and stop bands on cancel
Encode native .pcraft bytes before draining renderer diagnostics, and
requeue export warnings when a workspace write fails, so callers still
report the adapter or fallback reason with the error.
Move a failed GPU attempt's queued diagnostics into its CPU retry result
instead of leaving them for a later successful export to report.
Stop GPU band readback at the next band after cancellation, and check the
interrupt before each CpuRenderer sink delivery.
Band readback uploads half-precision textures, so reject U16 alongside
F32: their samples cannot survive the upload. Tighten the 8-bit CPU/GPU
parity checks to 1/255.
* Keep GPU export diagnostics with their attempt and stop bands on cancel
Encode native .pcraft bytes before draining renderer diagnostics, and
requeue export warnings when a workspace write fails, so callers still
report the adapter or fallback reason with the error.
Move a failed GPU attempt's queued diagnostics into its CPU retry result
instead of leaving them for a later successful export to report.
Stop GPU band readback at the next band after cancellation, and check the
interrupt before each CpuRenderer sink delivery.
Band readback uploads half-precision textures, so reject U16 alongside
F32: their samples cannot survive the upload. Tighten the 8-bit CPU/GPU
parity checks to 1/255.
* Report partial warm fallbacks and CPU fallback on stderr
The averaged GPU Warm row now says how many warm runs fell back to the
CPU and were excluded from the average, through the existing helper.
The single-layer convert diagnostic test checks that the CPU fallback is
reported on stderr, in GPU-enabled and CPU-only builds alike.
* Keep headless export warnings when a workspace write fails (#10842)
A capability-scoped save encodes the export, then writes it; when the write
fails, the export warnings (folded together with renderer diagnostics) were
pushed back through `BandRenderer::push_diagnostic`, whose default is a no-op.
A custom renderer that overrides only `drain_diagnostics` therefore lost the
adapter or fallback reason that belonged with the error.
Keep them in `Headless` instead and return them from the next
`drain_diagnostics`, alongside the renderer's own queue. The regression uses a
renderer without a `push_diagnostic` override and checks the warnings survive
the failed write and drain exactly once.
Also name the actual source of the depth limit in the headless GPU matrix:
layer content is uploaded as half-precision textures, not band readback.
* Stop CPU bands and CPU retries on cancellation (#10912)
The CPU retry after a retryable GPU error composes through
`FlatSource::bands`, whose shared `emit` sink did not poll the export
interrupt: a cancel was only observed after the whole composite finished.
Check it once per band, as `CpuRenderer` does, for both RGB and CMYK ink
bands. The flat and streamed-PNG retry branches also check before starting
the full CPU composite again, so a cancelled export returns
`IoError::Cancelled` instead of re-rendering the document.
Tests: band-sink cancellation for RGB and CMYK inks, a renderer call counter
proving a second export reaches the renderer, and a pending `wait_slices`
poll observing cancellation set mid-wait.
* Merge origin/main into GPU foundation (#10842)
* Keep GPU-path diagnostics on every failure path, honour cancel in native copy
- save: export warnings of a failed local write stay in Headless pending diagnostics
- render: preview fallback warnings are returned, drained, and added to the doc.render reply
- io: renderer-neutral fallback wording; native single-layer copy checks cancellation per band
- gpu: gate wait_slices tests off wasm32 (#10842)
* Address CodeRabbit review: keep fallback reasons on failed retries, report native-copy and quick-export CPU use, guard diagnostics drain (#10912)
* Accept render diagnostics from the doc.render reply in the serve GPU test (#10912)
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* feat(paint): add History Brush opacity/flow controls, custom icon, and options bar source label
- Options bar and keyboard shortcuts (digits 0-9 and Shift+digits) for History Brush opacity and flow %
- Options bar displays translated source state or snapshot name with missing-source hint when deleted
- Dedicated clean-room history-brush icon combining Lucide brush with counter-clockwise rewind arc
- Preserved JetBrainsMono attribution in ATTRIBUTION.md and added history-brush icon attribution
- Add contributor credit in people.toml
* History Brush options bar: rustfmt the source label
* Opacity keys: 00 gives Clone Stamp and History Brush their 1% minimum
Their Opacity fields and the retouch command start at 1%, so 00 set a 0%
the stroke then painted at 1% (CodeRabbit). The Brush still goes to 0%.
---------
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* Add Bosnian (bs) to the interface languages
bs.tsv: 4,552 rows, Latin script, Ijekavian, the same keys as the newest
catalogs. Wording shared with storytold/craft-i18n locales/bs/; the 306
strings craft-i18n doesn't have yet are new. Every Bosnian locale,
Cyrillic included, resolves to it, as for Serbian; plurals reuse the
one/few/other rule of plural_russian.
Locale and plural tests, live locale case, localization docs and an
ATTRIBUTION row (AI-assisted, not yet reviewed by a native speaker).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Tayw8nmxoLLmKv13jK93z
* Add Bosnian to the translation license notice; document the locale test
LICENSE-translations.txt now lists the Bosnian catalog and its
provenance (shared with storytold/craft-i18n locales/bs/, AI-assisted,
not yet native-reviewed), as ATTRIBUTION.md points to it. The touched
live locale test gets a doc comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Tayw8nmxoLLmKv13jK93z
* bs: wording from the craft-i18n catalog review
The Bosnian catalog review (storytold/craft-i18n#72) asked for one word
per concept; this catalog follows: pan is "pomjeranje" (not
"pomicanje"), a spreadsheet is "proračunska tabela", Greek & Cyrillic
reads "Grčko pismo i ćirilica". Placeholders and ellipses are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Tayw8nmxoLLmKv13jK93z
* bs: translate the 30 strings added on main; match Camera Raw labels
Main gained Path operations, the SVG smart object editor, Esc/Return pen
status messages and more since this catalog was written, so the
every-language tests (esc_ends_a_refused_draft_and_return_keeps_it) failed
on bs. Five cameraRaw-context entries (White Balance: As Shot, Tone
Histogram, Show Selected Region, Opened {name}, Loaded Camera Raw settings
from {file}) differed from the plain entries the panel actually draws, which
failed the Camera Raw localization test; they now match.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* Paste into another artboard keeps the place on the board (#8788)
Copying from one artboard and pasting with another board (or a layer on it)
selected put the pixels at their old canvas spot on the first board, where
the second board hid them, or in the middle of the view; the new layer still
went beside the selected one, inside the second board.
The clipboard now remembers the artboard a Copy came from (the copied layer's
board, every copied layer's when several are selected, or for Copy Merged the
board under the middle of the copied area). Paste and Paste in Place into a
different board of the same document move the pixels by the offset between
the two boards' origins, so they land at the same spot on the new board, and
the layer goes inside it as before. Pasting on the same board, outside the
boards or into another document is unchanged, and Paste Into / Outside keep
centring on the selection.
Co-Authored-By: Claude <noreply@anthropic.com>
* Paste between artboards: copied layers and true middle for the source board
Review follow-ups:
- With a pixel selection, Copy takes the active layer only, even when several
layers are selected, but the source board was worked out from every
selected layer, so selected layers on another board cancelled it. It now
uses the layers Copy actually takes.
- Copy Merged found the board under the middle of the copied area by halving
each edge first, which rounds 9..11 to 9 and could pick the wrong one of two
boards that meet. The edges are now added before halving.
Adds a test for each, both failing before.
Co-Authored-By: Claude <noreply@anthropic.com>
* Paste between artboards: locked boards and the placement midpoint
Review follow-ups:
- With a locked board itself selected, the pasted layer goes above the board,
not into it, yet it was still moved to the board's spot. The board shift is
now skipped in that case; a layer inside a locked board still lands on it.
- layer_place found the board a new layer lands on by halving each edge
first, so a paste moved to 19..21 had its middle at 19 and was turned away
from a board starting at 20. The edges are now added before halving, as for
the copy's source board.
Adds a test for each, both failing before.
Co-Authored-By: Claude <noreply@anthropic.com>
* Paste between artboards: floored placement middle, pastes join smaller boards
Review follow-ups (FredApps):
- The placement middle truncated toward zero, so content at -4..3 (middle
-0.5) counted as 0 and a board starting at x = 0 took it in. One helper,
layer_place::centre, now adds the edges in i64 and floors the sum; Copy
Merged's source board uses it too, and so does #10512's auto-nesting, with
a byte-identical layer_place.rs so the two merge in either order.
- Pasting onto a smaller board kept the offset from its origin, but the
layer's middle then fell past the board's edge and it was placed outside.
A paste moved onto a board now joins it; the board clips the rest.
Tests: negative-boundary placement, a document without boards, a smaller
board (offset kept, board clips), each failing before where it applies.
Co-Authored-By: Claude <noreply@anthropic.com>
* Paste between artboards: keep a clipped paste's clipping on a board
Review follow-ups (CodeRabbit):
- A paste that joins a board always lost its clipping. It now keeps its own
clipping flag (the copy's, from before placement) while the board holds a
layer it can clip to, and drops it on an empty board, as a new layer does.
- Documented that boards more than the 32-bit range apart can't shift a paste,
which then keeps its usual placement rather than wrapping around.
Adds a test (a clipped group pasted onto a board with a layer and onto an
empty one), failing before.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* feat(ui-egui): native Pen cursor bitmaps with bounded cache (#9122)
- Render standard Pen cursors (Pen, Freeform Pen, Curvature Pen, Add
Anchor, Delete Anchor, Convert Point) as OS cursor bitmaps to eliminate
presentation/canvas frame dependency and tracking latency.
- Preserve exact 8-offset dark outline and white foreground composition,
native platform scaling, and tool hotspots.
- Implement bounded caching (up to 16 entries) keyed on shape and scale to
ensure zero re-rasterization on warm pointer movement and rapid tool toggling.
- Preserve precise/crosshair mode, vector close-path cursor state, and painted
tooltip fallback for unsupported platforms/viewports.
- Add comprehensive regression tests for rasterization, alpha/color fidelity,
hotspots, lifecycle, and cache bounding.
- Add release benchmark example (cursor_bench) measuring cold rasterization
and warm movement/cache performance with optional PNG artifact export.
- Retain Pencil painted outline: documented that rigid OS cursor hardware
planes cannot preserve document pixel grid snapping across fractional
zoom/scales without continuous drift or per-frame churn.
* fix(ui-egui): correct Pen cursor rasterization, caching, and benchmark (#9122)
- Fix cursor_bench imports to use public tool_cursor::pen with correct frame lifecycle.
- Apply and verify requested native scale per platform in benchmark Context.
- Replace benchmark hardcoded metrics with truthful measured counters and Result error returns.
- Fix Arc::ptr_eq identity checks, unused imports, and Image::from_raw codecs API.
- Add baseline painted fallback measurements to benchmark for real speedup comparisons.
- Keep original single-entry cache for non-Pen shapes and add separately bounded 6-entry Pen cache without whole-cache cloning.
- Preserve fractional-scale logical geometry via subpixel resampling and compensate for OS integer hotspot quantization.
- Add independent comparison test against painted path across scales and backgrounds (light/dark/midgrey).
- Fix Shape::Pen tuple match hazard and doc comments on pen().
- Register cursor_bench in perf/budgets.toml for xtask perf runner integration.
* fix(ui): benchmark cursor preparation inside real egui frames
* feat(ui-egui): visual validation reference, comparison metrics, and contact sheet for native Pen cursors (#9122)
* fix(ui): render cursor reference pairs without duplicate Area ids
* fix(ui): use egui 0.37 frame API in cursor benchmark
* fix(ui): retain rendered cursor textures and restore existing documentation
* fix(ui-egui): restore crisp Pen cursor rasterization and update cursor tests (#9122)
- Eliminate double-resampling blur in Pen cursor rasterization: use direct discrete
vector glyph rasterization matching the painted fallback without fractional softening.
- Update CapsLock test to assert native OS cursor bitmap and fallback Crosshair for
Pen tools, validating exact bitmap RGBA, dimensions, and hotspot.
- Add synthetic test proving mathematical equivalence between straight RGBA hardware
blending and multi-pass painter fallback across dark, midgrey, and light backgrounds.
* test(ui): expect the native bitmap for Precise crosshair cursors
* fix(ui): bound cursor benchmark samples and strengthen bitmap tests
(cherry picked from commit 118c673e5d822742da0a1d99c5aa3a9989b47ff8)
* test(ui): pin cursor percentile rank and glyph-derived hotspots
* test(ui): cover every Pen cursor mapping and per-variant edge samples
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* Add headless GPU backend foundation for composition and flat export (issue #8062 PR 1)
* Fix headless GPU readback bounds, API compliance, and error preservation (#8062)
* Fix GPU readback bounds, context thread spawn error, and test parity (#8062)
* Fix BandRenderer Send bound, PNG reborrows, and clippy lints (#8062)
* Fix effect shadow test construction and remove dead code wrappers (#8062)
* Use short reborrows for Headless renderer without lifetime escaping (#8062)
* Fix clippy collapsible_if and ensure multi-band geometry in flat export tests (#8062)
* Fix automation Rect import, add renderer diagnostics, and harden flat export retries (#8062)
* Fix GPU feature test APIs and native target gates
* Verify unsupported GPU effects before testing band seams
* Use compositor buffer ownership API after upstream memory accounting change
* Update export renderer test helper for owned compositor buffers
* Fix headless GPU backend review findings (#10842)
- release a closed document's GPU caches through a BandRenderer hook
- bound the monitored readback wait and report a stalled GPU as device loss
- reject 32-bit float documents before band readback so exports fall back to CPU
- keep previews at 8-bit RGBA and report the palette/Duotone renderer bypass
- retain renderer diagnostics when encoding fails
- strengthen renderer, save-path and GPU diagnostics tests
* Add GPU close-hook and 8-bit preview regression tests (#10842)
* fix(io): correct renderer binding mutability
* Fix remaining headless GPU review findings (#10842)
- release a document's GPU caches when file.close, file.closeAll or
file.closeOthers removes it through a command
- honor cancellation while band readback waits, polling in 100 ms slices
- fault the device after a readback timeout so later exports skip it
- report single-layer, CMYK ink-mixing, ORA and PDF CPU paths to a
requested renderer
- keep fallback warnings in their export instead of leaking them into the
next one
- requeue renderer diagnostics when a renderer-aware save's final write
fails
- write MCP/serve diagnostics without panicking on a broken stderr
- document the actual colour-space contract and drop F32 from the GPU
depth list
* Use question mark for cancellable readback errors
* Keep GPU export diagnostics with their attempt and stop bands on cancel
Encode native .pcraft bytes before draining renderer diagnostics, and
requeue export warnings when a workspace write fails, so callers still
report the adapter or fallback reason with the error.
Move a failed GPU attempt's queued diagnostics into its CPU retry result
instead of leaving them for a later successful export to report.
Stop GPU band readback at the next band after cancellation, and check the
interrupt before each CpuRenderer sink delivery.
Band readback uploads half-precision textures, so reject U16 alongside
F32: their samples cannot survive the upload. Tighten the 8-bit CPU/GPU
parity checks to 1/255.
* Keep headless export warnings when a workspace write fails (#10842)
A capability-scoped save encodes the export, then writes it; when the write
fails, the export warnings (folded together with renderer diagnostics) were
pushed back through `BandRenderer::push_diagnostic`, whose default is a no-op.
A custom renderer that overrides only `drain_diagnostics` therefore lost the
adapter or fallback reason that belonged with the error.
Keep them in `Headless` instead and return them from the next
`drain_diagnostics`, alongside the renderer's own queue. The regression uses a
renderer without a `push_diagnostic` override and checks the warnings survive
the failed write and drain exactly once.
Also name the actual source of the depth limit in the headless GPU matrix:
layer content is uploaded as half-precision textures, not band readback.
* Stop CPU bands and CPU retries on cancellation (#10912)
The CPU retry after a retryable GPU error composes through
`FlatSource::bands`, whose shared `emit` sink did not poll the export
interrupt: a cancel was only observed after the whole composite finished.
Check it once per band, as `CpuRenderer` does, for both RGB and CMYK ink
bands. The flat and streamed-PNG retry branches also check before starting
the full CPU composite again, so a cancelled export returns
`IoError::Cancelled` instead of re-rendering the document.
Tests: band-sink cancellation for RGB and CMYK inks, a renderer call counter
proving a second export reaches the renderer, and a pending `wait_slices`
poll observing cancellation set mid-wait.
* Merge origin/main into GPU foundation (#10842)
* Keep GPU-path diagnostics on every failure path, honour cancel in native copy
- save: export warnings of a failed local write stay in Headless pending diagnostics
- render: preview fallback warnings are returned, drained, and added to the doc.render reply
- io: renderer-neutral fallback wording; native single-layer copy checks cancellation per band
- gpu: gate wait_slices tests off wasm32 (#10842)
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* fix(ui): cache adaptive wide angle preview mesh
* test(ui): match explicit constraint stroke color mode
* test(ui): clear unused texture updates in redraw regression
* test(ui): inspect grouped overlay paths independent of animation
* fix(ui): address wide-angle mesh cache review feedback
Repaint after panel edits so overlays catch up, share the proxy rect
between render and overlays, document the dirty invariant, and make the
overlay test measure its own baseline instead of hard-coded layout.
* Remove unused colour format import after integration
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Each new coverage tile zeroes 16 KB. The parallel path of
CoverageMap::accumulate did that on the calling thread while building
its work list, so a large dab's new tiles ran one at a time. Create
them inside the parallel section instead.
coverage_bench gains a large-dab line (3457x3457, what a soft 2000 px
tip covers). big_dab_bench's "accumulate" figure is relabelled: it is
a cold first push minus a warm rasterize, not accumulate alone.
Refs #11339
* Skip compositing for geometric measurements
* Preserve simple PDF shapes and glyph source ranges
* Preserve text and shapes in PDF export (#10269)
- Export topmost simple text layers as vector glyph outlines with selectable text
- Embed ActualText unicode span and invisible text operators (mode 3 Tr) for 100% extraction
- Support stroked shapes in vector PDF export (centered solid strokes, dashes, line caps/joins)
- Retain glyph style indices in text rendering outlines
* Drop a product name from the glyph outline doc comment
* PDF export: keep path text and synthetic bold raster
* Fix PDF Unicode selection and respect font embedding permissions
* Render the text cache in PDF raster fallback fixtures
* Fix PDF vector selection eligibility and finite text matrices
* Preserve upper PDF vectors and reject overflowing curve controls
* Fix text shape import in PDF regression test
* Cover PDF Unicode map blocks and leading selection whitespace
The linked-layer path (no copy, the link group's box) applies only when the
layer's own pixels are the target; an unlinked layer mask targeted on a linked
layer is copied as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no criteria kept yet (the row's first draw, e.g. after ui.set while the
panel was closed), a filter that is on counts as applied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A kept clone of a paused action's continuation could reopen a scope that had
already resumed and finished; every clone now shares one resumed mark. A dialog
paused on a document target (Quick Mask or an alpha channel) and switched to a
layer target now checks the layer that was active at the pause.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured in the reference application: Ctrl+Alt+T on a linked layer without
a selection makes no copy; the box frames and moves the whole link group, and
History shows one Free Transform. An unlinked layer still gets its copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #10396 Esc hides the crop frame until the next press (#10370), so the
test expects no frame after Esc instead of a new default one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With #10965 an action play is one history entry, and a play that leaves the
document unchanged adds none (#10944). The Current Layer fill from the uniform
layer alone rewrites the same pixels, so the test expects an entry, and checks
undo and redo, only when the document changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured in the reference application: Duplicate and Layer via Copy of one
linked layer put the copy in the original's link group. Copies of several
layers of one group still get a group of their own, and a copy that is the
only one from its group among several copied layers is still unlinked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged with the ours strategy so GitHub closes#6647 as merged. Everything
it carries is either on main already (via integration batch #10145) or,
for the later contributors entry, left out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(layers): refuse to ungroup the sole empty layer group (#11118)
* fix(layers): document the ungroup guard and cover the valid empty-group case
Review follow-up: docstring on ungroup() and a control test showing an empty group that has sibling root layers still ungroups (the group simply disappears).
---------
Co-authored-by: Hartmut Nörenberg <hnoerenberg@users.noreply.github.com>
* PSD: keep stylistic sets in Txt2 for PhotoCraft-native documents.
needs_txt2 now covers Txt2-only OpenType flags (ss01–ss20 and non-default TXT2_FEATURES). Fresh documents get a framed point-text object with full style runs so Set 1/5 round-trip on save/reopen (#10308).
* Txt2: seed fill colour and font list on PhotoCraft-native blocks.
Fresh style runs get a /53 paint dict, and an empty font list gets a CoolTypeFont entry so index 0 resolves (CodeRabbit on #10313).
* Txt2: tag SimplePaint and ensure numbered /0 on legacy blocks.
CodeRabbit: write /99 /SimplePaint on fresh /53 dicts, and create the numbered resource dictionary before pad_frames when a legacy block lacks it.
* Txt2: treat a missing stylistic-set mask as 0 when comparing styles
A style run without key 85 used to agree with any ssNN mask, so enabling
ss01 could leave the file's runs in place on save. Align with import's
default of 0. Also restore the G8 gaps note after rebasing onto main.
* style(io): rustfmt psd_kerning stylistic-sets test
* Txt2: keep Photoshop /0 stable when seeding fonts and frames
Only seed a CoolTypeFont list and inject /0/8 when the block is
PhotoCraft-native or already had those keys / needs new frames, so
unedited Photoshop Txt2 saves stay byte-identical (#2374).
* style(text): rustfmt oversized-template assert
* PSD: keep the Txt2 scaffolding to PhotoCraft-native blocks
A file's own Txt2 block (any Photoshop era) is written as before: no
numbered /0 or /0/8 frame list is injected, a layer without a template
object gets its stub, a run without /53 isn't given one, and no
minimal style base is used. Only a block PhotoCraft creates itself
(prev.is_none()) gets fresh point-text objects, minimal runs, a seeded
font list and a frame list.
Fresh objects now write one /5 paragraph run per paragraph (UTF-16
lengths, checked conversion), as edited objects are refitted.
Tests: Photoshop blocks with an empty or missing /0 get no native
scaffolding; the oversized-template test is back to main's assertion;
fresh paragraph runs; the PSD round trip checks ss01 and ss05 on every
run.
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
- #9979: ui.dialog.set can no longer rewrite an open field picker's __colorPicker
marker (its OK went to the Crop shield instead of the field), and Preferences Apply
waits for an open colour picker as OK does, so it never saves a previewed colour.
Both have regression tests that fail without the fix.
- #9182: the stress test's PC_BUILD_ROUNDS / PC_BUILDS_PER_DEVICE fall back to the
default for 0, which ran no builds and passed.
- #10837: the ROADMAP entry no longer says a Latin hint rules windows-1251 out; the
hint settles a word-initial rare letter and breaks exact ties.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The position checks of the codepage guesser (letters after a character, a closing !,
the Cyrillic run around a letter) rescanned the rest of the name from every non-ASCII
character, so a hostile .atn or PSD with a 64 KiB step name of one letter kept Load
Actions busy for about three minutes. Each is now found once per name; the three
measured cases take 0.03 s together, and a regression test covers them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Toolbar: Alt/Option-click cycles the flyout tool slot.
Matches Photoshop: plain click keeps the shown tool; Alt-click advances through the slot without opening the menu (including tools Shift+letter skips).
* Toolbar: don't fit-on-screen on rapid Alt-clicks through Hand.
Alt-click only cycles the flyout slot; exclude Alt from the Hand/Zoom double-click handlers and cover inactive-slot cycling.
* Toolbar: ignore Hand/Zoom double-click after an Alt-click.
egui pairs a plain click with a prior Alt-click as a double-click; remember the previous click's Alt so fit-on-screen and actual-pixels only run for two plain clicks.
* Toolbar: read click modifiers from the slot's pointer release.
Match DesignCraft/EffectCraft: bind release mods to the tool rect, treat any modifier as blocking Hand/Zoom double-click, and harden the Alt-cycle test by clearing i.modifiers before release.
* Toolbar: cover 1/2/3-tool Alt-click cycles and hidden-tool slots.
Addresses ReviewerA on #10301: next_slot_tool wrap cases for short flyouts, and visible_sections so Edit › Toolbar hidden tools shrink the Alt-click cycle.
* style(ui): rustfmt after rebase onto main
* Toolbar: next_slot_tool never panics on an empty slot
Index with checked arithmetic and get() (house rule: no panics on
input-derived indices); an empty slot returns the current tool. Test
added.
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Main now hides the frame after a committed crop (`hide_frame` /
`show_frame`, #10642) on the same `dismissed` state this branch added,
and keeps it hidden when the Crop tool is picked again, as measured in
Photoshop. Esc now uses that state: `dismiss` is `cancel` then
`hide_frame`, so after Esc picking the tool again also keeps the frame
hidden, and only a press brings it back. The Esc test follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>