Cursor::precise listed every Pen state but PenJoin (added later, #794), so with Use Precise
Cursors on the Pen still showed its join badge over another open path's end.
PenJoin is a crosshair too under the preference.
The Pen join test now also checks the pointer over the other path's end is the crosshair with
the preference on (and PenJoin with it off, as before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
shapes::polar_grid drew at least one radial divider (`radial.max(1)`), so radial 0, which
the command and the Polar Grid tool (Left arrow while dragging) accept, still drew a spoke.
Zero now draws none; the loop never divides by it.
The test checks 0, 1 and 2 radial dividers with no concentric ones give 1, 2 and 3 paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
Reversal read the midpoints after Gradient::reverse had reordered the stops, so each stop
took the wrong segment's midpoint: red-to-blue with its midpoint at 20% reversed to a 50%
midpoint instead of 80%, moving the transition.
Gradient::reverse now mirrors each segment's midpoint with it, so a reversed gradient is the
same ramp the other way round; paint.editGradient's own (wrong) pass is gone, and the SVG
importer's reflect spread, which had a correct copy of the same mirroring, uses it too.
Tests: two- and three-stop gradients with off-centre midpoints sample the same reversed (t vs
1 - t) and come back after two reversals; paint.editGradient reverse turns a 0.2 midpoint into
0.8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
The menu item sends no artboard, so the engine fell back to the first one even with another
active in the Artboards panel.
The frontend fills in the active artboard (the view's, as the Artboards panel and navigator
show it) for select.allOnArtboard, as it already did for Paste in Place / in Front / in Back
(#693); the two now share one block, and the index is kept within the artboards. An explicit
artboard still wins; the engine is unchanged.
The test makes artboard 2 active, then 1, and invokes the menu item each time, then passes
artboard 1 explicitly with artboard 1 active.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
`run` reported "Broken pipe" (on Windows "The pipe is being closed") and exited 1 when the
reader of its stdout went away, unlike `commands` and the other subcommands, which end quietly.
Its step printer now treats a closed pipe as the end of output, not an error: the remaining
steps still run (an export is still written), unprinted, and the run exits 0. Other write
errors and failing steps still fail.
The test runs document.json, a rectangle and an SVG export with stdout closed: status 0, empty
stderr, the SVG written; `run --cmd not-a-command` still fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
The desktop app's ui.render (MCP `screenshot` when connected) always rendered the first
artboard and ignored `artboard`, which the headless backend honours.
It now renders the 0-based `artboard` (the first without one, an error for one that doesn't
exist), as headless does. control-protocol.md and the control.rs list show the parameter.
The test drives ui.render through the control channel on a 64 x 64 and a 96 x 48 artboard:
no index and 0 give 64 x 64, 1 gives 96 x 48 (48 x 24 at scale 0.5), 2 is an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
add_text passed `color` to text.create only, and text.createInPath built its own character
style without a colour, so type on a path and path-based area type took the current fill.
text.createInPath now uses the same new-type style as text.create (size, font, style, color,
else the current fill), and add_text sends `color` on both routes.
The MCP test draws a blue rectangle (the current fill), turns it into on-path and area type
with color #ff0000, and reads red back through inspect_document.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
With every layer hidden or locked, the insertion layer fell back to the top layer anyway, so
drawing a shape, typing, pasting, making a graph, a flare, a grid, trim marks or a symbol put
the art on a hidden or locked layer: invisible, or unselectable by Select All. Illustrator
refuses to draw on a locked or hidden target layer (public forum answers, e.g.
https://community.adobe.com/t5/illustrator/can-t-draw-a-new-object-in-my-document/m-p/11107182).
DocState::target_parent is insertion_parent for new art, failing with "the target layer is
locked or hidden" when it would be a layer that isn't editable; those creation paths use it.
Another layer that is shown and unlocked still takes the art, as before. The message is in the
de, es, fr, it, ja, ru and uk catalogs. File > Place is left as it is (another branch reworks
it).
The test hides, then locks, the only layer: an ellipse, a polar grid, text and a paste each
fail with the message and leave the document unchanged; a new layer then takes a rectangle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
Character and paragraph styles checked a new attribute against the keys its base style
serializes, and the JSON leaves out Auto leading and kerning, no mojikumi and other defaults.
So charStyle.new / setAttrs refused leading or kerning, paraStyle.new refused mojikumi, and a
style captured from text with explicit leading failed to apply to Auto-leading text, to
duplicate or to come back out of a library.
The check now also accepts every field name the style type deserializes (read from serde's
derive), still refusing unknown keys and the file-only justify_auto.
The test creates and edits styles with kerning, leading, small caps, mojikumi and direction,
applies a captured leading style to fresh text with and without Clear Overrides, duplicates it,
uses it from a library, and checks `nope` and `justify_auto` are still refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
# ROADMAP.md
Asset Export, File › Export, Save for Web and Object › Rasterize drew
each shape with its own smoothed edge, so where two shapes met edge to
edge (Divide, Shape Builder, abutting art) both half-covered the pixels
on the line and the background showed through as a thin ghost border.
With Art Optimized (the default) these now supersample, as the option's
name in Illustrator says: the art is drawn with hard edges at four times
the size (three or two times for large images, within a 40-megapixel
budget and the renderer's 65,535-pixel side) and each block averaged
into one pixel. Pixels shared by two shapes are fully owned by one or
the other at the finer size, so no seam is left, and the outer edges
are still smooth. The canvas keeps shape-by-shape smoothing for speed;
None and Type Optimized are unchanged.
RenderOptions::supersample turns it on (exports and Rasterize set it).
A render test checks two triangles sharing a diagonal: a seam with
shape-by-shape smoothing, none supersampled, and the factor's limits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The headless test taken from #1015 set `RawInput::modifiers`, which egui
0.36 doesn't have, and clicked left of the spinner (the right-aligned
label's text ends before its box). Modifiers now come as
`ModifiersChanged` events, as the Appearance panel tests send them, and
the click is placed from the field's text. It also checks Ctrl/Cmd steps
a tenth. It fails with the spinner ignoring modifiers (15 pt for 24).
rustfmt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR kept a field's integer kind only while its value had been an
integer: typing 2.5 in Columns and then 3 sent 3.0, and the mosaic fell
back to 10 × 10 again. A whole number typed in any command dialog is now
an integer (`typed_number`) whatever the field held before; commands
that take any number read integers as well, and a fraction stays one.
Test: Object › Create Object Mosaic… opened from its menu item, 3 typed
over Columns and Enter makes a 3 × 10 mosaic (it made 10 × 10 before,
#1000).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same checks in a third of the code; the corners destructure into the
four points instead of being indexed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PR added `checked_anchor_from_json` next to the lenient
`anchor_from_json` and dropped path.rs's import of the latter, which
`path.appendAnchor` still called, so the engine didn't compile.
`anchor_from_json` is now the strict parser itself (handles through
`finite_numbers`), used by path.create, path.setAnchors and
path.appendAnchor alike: the Pen's appended anchor with a malformed
handle fails too instead of losing the handle. Tests: a non-boolean
`closed` and a malformed appended handle change nothing. rustfmt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`text.reshapeArea` read its anchors with the lenient `parse_refs`, so a
malformed reference among good ones moved only the good ones. It now
uses `checked_refs` as well, and `parse_refs` (no other user) goes.
Test: a mixed list fails without moving anything.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rejected Stroke panel edit with nothing selected leaves the next
object's stroke as it was; an accepted one sets its weight and dashes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `finite_numbers::<N>` (cmd/mod.rs) reads an array of exactly N finite
numbers, nothing dropped or padded; `rect` uses it, and the rect cut,
Free Distort and anchor handles reuse it in the PRs stacked on this one.
- `at` is parsed once (`at_param`) instead of point_param plus a second
shape check that indexed the params; `at: null` means no position, as
null does for the other optional params, and gets a test.
- rustfmt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eight raster effects on the pixel-filter pipeline
(vectorcraft_effects::pixel::brushstrokes), under a new Effect › Brush
Strokes submenu between Blur and Distort in the Raster Effects section:
- Accented Edges (edgeWidth pt 1..14, default 2; edgeBrightness 0..50,
38; smoothness 1..15, 5): the edges of the softened object painted
towards white chalk at high brightness or black ink at low brightness;
Smoothness also leaves the faint edges out. The shape stays as it was.
- Angled Strokes (directionBalance 0..100, 50; strokeLength pt 3..50, 15;
sharpness 0..10, 3): light areas in right diagonal strokes, dark areas
in left diagonal ones; 0 makes every stroke a left diagonal, 100 every
stroke a right diagonal. Sharpness brings detail and bristles back.
- Crosshatch (strokeLength pt 3..50, 9; sharpness 0..20, 6; strength
1..3, 1): the detail kept under pencil hatching along both diagonals,
Strength passes of it, the edges roughened.
- Dark Strokes (balance 0..10, 5; blackIntensity 0..10, 6;
whiteIntensity 0..10, 2): short strokes towards black in the dark
areas, long strokes towards white in the light areas.
- Ink Outlines (strokeLength pt 1..50, 4; darkIntensity 0..50, 20;
lightIntensity 0..50, 10): black outlines over the edges, ink hatching
in the shadows, shadows darkened and highlights lightened.
- Spatter (sprayRadius pt 0..25, 10; smoothness 1..15, 5): each pixel
shifted by noise up to 0.4 x the radius; Smoothness makes the grains
larger and smoother.
- Sprayed Strokes (strokeLength pt 0..20, 12; sprayRadius pt 0..25, 7;
strokeDirection Right Diagonal, Horizontal, Left Diagonal or Vertical):
strokes along the direction, scattered by up to 0.3 x the radius.
- Sumi-e (strokeWidth pt 3..15, 10; strokePressure 0..15, 2; contrast
0..40, 16): wet strokes, ink bleeding from the darks along paper
fibres, more ink with pressure and an S-curve contrast for rich blacks.
It works in value only: hues and saturation stay, and so does the
object's shape (no fringe in the transparency).
A stroke averages the colour along a segment through the pixel (16
samples at most, the art softened first when they lie more than a pixel
apart); strokes lie in rows around the object's centre, each row
starting at random and each stroke shifted along itself, with streaked
noise for the bristles. Everything is in document space, so the effects
look the same at any zoom or resolution, move with the object and draw
the same every time; outsets and reaches are exact. On a 2000 x 2000
raster in release the slowest settings take 0.3-2.0 s (Crosshatch at
50 pt with 3 passes the most).
Shared helpers instead of copies: edges::sobel (Glowing Edges now uses
it, unchanged), distort::displace and texture::Tones are pub(super).
Catalogue rows with parameter docs, menu entries, Sprayed Strokes'
direction dropdown, translations (de, es, fr, it, ja, ru, uk, zh-hans,
zh-hant; menu labels in cs and pt-br), tests (parameters and clamping,
outsets and reaches, each filter and option showing, chalk and ink on
the edges only, shadows and rich blacks, Sumi-e keeping the shape and
hues, hatching keeping the tone, strokes smearing along their direction,
Spatter's radius, resolution and move invariance, canvas, multithreaded,
PDF and SVG, the command sweep with junk parameters, the menu) and
ROADMAP.
Behaviour from public documentation only:
https://helpx.adobe.com/photoshop-elements/using/brush-stroke-filters.htmlhttps://www.computerhope.com/jargon/p/photoshop-sprayed-strokes.htm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, hardware, UI and localization parity docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>