Commit Graph
3777 Commits
Author SHA1 Message Date
dotCooCoo dfa9f883c0 Fix Drop Shadow Darkness in source color space 2026-10-29 14:36:41 +07:00
dotCooCoo e31e04e545 Part of #3137: Color Guide takes its base from colors chosen in Color, Swatches and the Color Picker
The Color Guide kept the base color it took when the panel first showed,
and only the base chip changed it. In a new document the current fill is
white, so every harmony and variation was white or gray until the base
chip was clicked with another current color.

A color chosen in the Color panel (spectrum, sliders, fields, chips and
Recent Colors), in the Swatches panel or a swatch library panel, or with
OK in the Color Picker for a fill or stroke now becomes the guide's base
color, and the guide shows its harmonies. A color applied from the guide
itself leaves the base as it is, and so does selecting artwork. The base
chip (Set base color to the current color) still makes the current color
the base. A new base clears the cells selected for Save Colors as
Swatches.

The base moves from egui's temporary memory to UiState::color_guide_base,
which is not saved, so the Color panel, the Swatches panels and the Color
Picker can set it.
2026-10-29 14:36:35 +07:00
NetterSilverandClaude Haiku 5.5 520f390c9c Map hyphenation exception break points to the original word's chars
Found while testing crates/text/src/hyphen.rs:74-90 (parse_exception)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:36:33 +07:00
dotCooCoo c1c07efc95 Blob Brush: keep the first Shift axis without moving earlier points
Squashed from #4525 onto main.
2026-10-29 14:36:32 +07:00
dotCooCoo bb869dd9df Swatches: global swatch edits also update graph designs and the art of brushes
Squashed from #4489 onto main.
2026-10-29 14:36:04 +07:00
dotCooCoo b9f9099d91 Flare: preview options and restore defaults with Alt or Option
Squashed from #4446 onto main.
2026-10-29 14:35:57 +07:00
bflatastic efc1501ac9 Path: Object › Path › Smooth smooths the selected paths with a slider and Auto-Smooth (#4115) 2026-10-29 13:51:51 +07:00
Don Omondi 1196d9a335 fix(eps): import bool_or after main's explicit command imports
Main replaced the glob import of the command helpers in fileio/eps.rs
with an explicit list; the EPS Save path's selectedOnly check uses
bool_or, so it is added to that list.
2026-10-29 13:00:49 +07:00
Don Omondi c6070f633e Support original-format EPS Save and Save As 2026-10-29 13:00:47 +07:00
dotCooCoo 95ce4d5cb4 Canvas: a pan or zoom-out fills the uncovered area with low-resolution art
Squashed from #4256 onto main.
2026-10-29 13:00:47 +07:00
bflatastic 828d784c8c Shape Builder: import replace_leaves in buildcmds/shape_builder.rs (#4299 made the imports explicit) 2026-10-29 13:00:46 +07:00
dotCooCoo f30819b7eb Shape Builder: a merge replaces only the paths it uses, where they are
Squashed from #4094 onto main.
2026-10-29 13:00:46 +07:00
Muhammad Saad 18e916ddf5 Pattern Brush: decide straightness from the track, not from samples (#3926 review)
Messhias is right that a sampled check cannot promise a run is straight, and my
own write-up overstated what the grid did: I said the fit was "used only where
it reproduces the real map", which five sample positions cannot establish. A
bend narrower than the sample spacing passes between the samples, and the grid
then approves an affine that is simply wrong.

It is not a theoretical gap. A zigzag of amplitude 4 pt and half-period 20 pt,
turning 11.3 degrees (under CORNER_DEG, so the vertices are not corners and
their tangents blend flat), lands its y = 0 vertices exactly on the five sample
positions. All 25 grid points then agree with the affine to the last digit while
the art between them is thrown 4.000 pt off, against the 0.0613 pt tolerance the
grid believed it was enforcing: 65 times over, measured, not estimated.

`Track::is_straight(s0, s1)` reads the answer off the track instead. A bend in
the span is two segment directions that differ, so no bend can hide in it
whatever its size. It also checks the vertex tangents, because `frame` blends
them across a segment unless the vertex is a corner, so a neighbour outside the
span can bend the tangent inside it: directions alone would call `d, d, e`
straight over the first two segments and be wrong. It fails closed on a closed
track's seam, a non-finite bound and a track with no segments.

`AlongMap::is_linear` covers the other half: Between Guides bends the arc-length
map itself, which no amount of looking at the track would reveal.

The grid stays, demoted to a second opinion that can reject and never admit, and
`affine_fit`'s doc comment now says that rather than claiming to be the gate.

Tests. `a_bend_shorter_than_the_sample_grid_is_still_a_bend` builds the zigzag
above, asserts the sampled check accepts it, measures how far out the approved
affine is, and then asserts the track-based gate refuses it; end to end through
`bend` it asserts the aliased bend is subdivided while a genuinely straight run
of the same span keeps the tile's four anchors. Restoring the sampled gate reds
it with `the aliased bend is subdivided, not affine-mapped: 4 anchors`, so the
old code really did lay a flat tile along a waving track.

`straightness_is_read_off_the_track_not_sampled` covers the predicate directly:
collinear segments and their extrapolation, a 0.2 pt detour, a span around it
and a span before it, the blended-tangent case above, the closed seam and a NaN
bound. Writing it caught a bug in my own guard: `f64::min` discards a NaN rather
than propagating it, so `is_straight(NAN, 10.0)` was reporting straight until
the finiteness check moved ahead of the min/max.
2026-10-29 13:00:45 +07:00
Muhammad Saad 955660bee0 Pattern Brush: make the straight-run route match warp_node exactly (#3926 review)
FredApps noted a real divergence: the affine route kept whatever the tile held
(it only required bounds) while warp_node drops every node kind but paths,
groups, layers and compound paths, and it did not clear `live` on paths. For a
tile of paths and groups the result was the same, so nothing was visibly wrong,
but "this changes the curves and nothing else" was a wider claim than the code
made: a symbol instance arrived untouched on a straight run and vanished on a
bend.

affine_node now mirrors warp_node's walk through `prepared` and applies one
transform at the end: the same kinds are kept and the rest dropped, `live` is
cleared on every path, an empty path or a group left with no children is
dropped, and strokes are scaled by `width_scale` rather than by the transform's
own factor. The two routes now differ in the tile's curves and in nothing else,
which is what the change was always meant to be.

Test: a_straight_run_keeps_the_same_node_kinds_as_a_bend puts a symbol instance
beside a path in one tile and asserts both routes return the same kinds. On the
old code it reds with `left: ["group", "path", "symbol"] right: ["group",
"path"]`.
2026-10-29 13:00:45 +07:00
Muhammad Saad d1b9bc2b3d Pattern Brush: keep the tile's curves on a straight run (#3926)
Bending flattens every path it maps: warp_path runs kurbo::flatten and
emits SubPath::polyline, so a circle tile is drawn as a polygon. That
shows in the live stroke, not only after Expand Brush Strokes, and it
happens even on a perfectly straight path where the tile needs nothing
more than an affine.

Bending is only non-linear where the track bends. On a straight run the
arc-length map and the normal offset are both linear, so affine_fit
derives the affine from three corners of the art and then checks it
against a five by five grid, including those corners. Within the
tolerance the flattening would have used, the art is transformed
instead, which keeps its Beziers. Outside it, nothing changes: the old
path runs and the result is what it was.

Strokes are scaled by the same factor the bent path uses rather than by
the affine's own, so curves are the only thing this changes.

Corner tiles already took this route, which is why they kept their
curves while the tiles between them did not.

Two existing tests moved with it, both because they read anchor counts
that only held while straight runs were flattened:

  pattern_places_corner_tiles told corner tiles from side tiles by
  "four anchors", true only because the side rectangles arrived as
  polylines. It now tells them apart by a kept handle, which is what
  actually distinguishes an ellipse from a rectangle, and additionally
  asserts the side tiles keep their own four anchors.

  pattern_output_is_capped_across_subpaths_not_only_per_run counted on
  sixteen tracks passing the #3594 anchor budget. A tile is about four
  times cheaper in anchors now, so the budget engages later; the test
  derives how many tracks it needs from what one costs.
2026-10-29 13:00:44 +07:00
krosk93 7a79935c10 i18n: add Catalan (ca) localization
Squashed from #4453 onto main.
2026-10-29 13:00:44 +07:00
Davit MatchakhelidzeandClaude Opus 5.5 a8006cfade i18n(ka): Georgian for the batch dialog, PDF presets, dimension options and swatch names
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-29 13:00:43 +07:00
Davit MatchakhelidzeandClaude Opus 5.5 7a04b41fcd i18n(ka): Georgian wording fixes for 10 rows
Bullets and Numbering typo (დანომრვა → დანომვრა); select/selected = მონიშვნა;
Path = ტრაექტორია; solid colours = მყარი ფერი; Tint = ტონირება; screenshot =
ეკრანის ანაბეჭდი; count-neutral Result(s); Include Objects On Canvas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-29 13:00:43 +07:00
Kirill M 5ea6a0e544 i18n(ru): 363 rows the Russian catalog was missing
Rows the other catalogs already had: new-document presets, color
harmony rules and swatch names, arrowheads, the keyboard shortcut
editor, and the print, PDF and Save for Web options. The workspace
names, OpenType and the perspective grid presets stay in English as
MENU_KEEP_AS_IS requires. Written from the English meaning by a native
speaker.
2026-10-29 13:00:42 +07:00
Michael Holzheu 44ef4ab4bc Fix canvas pointer mapping under subpixel dock layouts
Squashed from #4292 onto main.
2026-10-29 13:00:42 +07:00
dotCooCoo 6923939460 Document Color Mode: also converts graph designs, graphic styles, opacity masks and brush art
File > Document Color Mode and document.open {colorMode} converted the
colors of the layers, symbols, pattern tiles and swatches only. Graph
designs, graphic styles, the art of opacity masks, the tile that
pattern editing's Cancel puts back and the art of brushes kept their
old color model (only a tint of a global swatch in a graphic style or
a mask followed the swatch), so a CMYK document could still apply,
place or print RGB colors from them.

These now convert with the rest, in the same undo step: their solid
colors, gradient stops, text runs and mesh points, nested masks
included. A Hue Shift brush's key color takes the converted color of
the art color it matches, a tint of a global swatch included. The
brush library is stored again only when one of its colors changed. A
tint of a global swatch in any of these places becomes the converted
swatch's color at its tint, as in the layers. The reply's changed
count covers the colors of the layers, symbols, graph designs, pattern
tiles and opacity masks; the colors of graphic styles and brushes
convert without being counted.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 13:00:40 +07:00
dotCooCoo 9a71c35261 Tests: each timed refused-reconnect run starts from a closed connection
remote_call_reports_nothing_sent_when_reconnect_is_refused set up the
closed connection once, so only its first run went through the
stale-connection probe. Each run now sets up its own closed connection
before the timed call.
2026-10-29 13:00:39 +07:00
dotCooCoo e02b43b6be Tests: the MCP remote-backend tests pass on a loaded machine
The vectorcraft-mcp tests of the remote backend in tests.rs gave a fake
app 2 or 5 s to accept a connection or to read a request, and waited
2 s for a connection to end or a line to arrive. When a busy machine
held the client's thread for longer, the fake app gave up and the test
failed. These waits now use WAIT (20 s).

remote_connect_fails_fast asserted that a refused connect returned
within 5 s, and remote_call_reports_nothing_sent_when_reconnect_is_refused
asserted that the call returned within 2 s. On Windows a refused local connect
ends at the backend's own 800 ms connect timeout. A connect without
that timeout takes about 2 s. The 5 s bound did not tell the two apart,
and the 2 s bound left 1.2 s for any pause of the thread. Both tests
now bound the connect by FAILED_CONNECT (1.5 s) and run it up to five
times, until one run is under the bound. The second test checks the
error of every run.
2026-10-29 13:00:38 +07:00
dotCooCoo 6c781864d1 Shaper: a press checks every selected original shape and keeps objects in front of its group
While an opacity mask or a pattern is edited, a press on a selected Shaper
original shape now moves it when it is not the first selected object or
shares the selection with shapes of another Shaper Group. An object in
front of the Shaper Group at the pointer is selected instead.
2026-10-29 13:00:37 +07:00
dotCooCoo 38542e02ba Shaper: the Selection tool moves a selected original shape while a mask or pattern is edited
While an opacity mask or a pattern was edited, a Shaper double-click
selected a Shaper Group's original shape without isolating the group's
original shapes. A Selection tool press on that shape then selected and
moved the whole Shaper Group over the art the group shows, and started
a marquee over a part of the shape the group does not show.

A press on the selected original shape now holds it for a move, and
the pointer shows the move cursor over it.
2026-10-29 13:00:37 +07:00
dotCooCoo edc2602ae4 Shaper: Shaper Group edits keep the opacity mask and pattern editing layers isolated
While an opacity mask or a pattern was edited, a Shaper scribble and
Object › Shaper › Release and Expand ended isolation, and a Shaper
double-click on a Shaper Group isolated the group's original shapes.
Each replaced the editing layer's isolation while the document stayed
in mask or pattern editing. A click on the Shaper Group's mode widget
did the same through object.isolate.

A scribble, Release and Expand now keep the editing layer isolated in
these modes, and end isolation as before outside them. A double-click
with Shaper selects the original shape under the pointer without
isolating the group's original shapes, and the mode widget does not
show while a mask or a pattern is edited.

Docs: docs/shaper.md, docs/target-app-parity.md.
2026-10-29 13:00:36 +07:00
dotCooCoo ca137eb54d i18n: Enter Isolation Mode's errors show in the interface language
object.isolate's errors "type objects can't be isolated", "the object
is hidden or locked" and "missing id" had no row in any catalog, so the
status bar showed them in English in every language. The same was true
of layer.target's "the object is outside the isolated group", the other
isolation-mode error in the commands.

Each of the four messages now has an @msg row in every catalog that
translates all status and error messages and in Bulgarian, Danish,
Indonesian and Dutch. layer.target also gives "the object is hidden or
locked", and several other commands that take an id also give "missing
id", so the same rows translate those errors.

Docs: docs/target-app-parity.md.
2026-10-29 13:00:35 +07:00
bflatastic 06cad0ba44 Live Paint: import the builder helpers live_paint.rs now uses (#4299 made the imports explicit) 2026-10-29 13:00:34 +07:00
dotCooCoo 24c6fb756a Live Paint: test the face under the point before the lock check; state the marquee's 3-pixel threshold in the docs 2026-10-29 13:00:34 +07:00
dotCooCoo fa7df461bc Live Paint: a Live Paint Selection marquee selects the faces and edges it touches
The Live Paint Selection tool acted only on the press, so a drag
selected at most the one face or edge under the pointer. The reference
app selects every face and edge a marquee touches, partly enclosed ones
included, and a Shift-drag adds them to the selection or removes them.

A drag of 3 pixels or more now draws a marquee. On release, select.set
selects the faces and edges it touches, or select.none runs when it
touches none. With Shift held at the press, select.toggle runs with
what it touches, and nothing runs when it touches none. A face that holds the whole marquee counts. Select Fills off
leaves faces out, and Select Strokes off leaves edges out.

A click now changes the selection on release instead of on the press,
so Escape between the press and the release, which also removes the
marquee, leaves the selection as it was. The tool no longer highlights
or selects the faces and edges of a Live Paint group that is locked, on
a locked layer or on a locked artboard, or that is outside the isolated
art in isolation mode.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 13:00:33 +07:00
dotCooCoo 996e4a8258 Tests: the nested culling tests require a symbol art count above zero 2026-10-29 13:00:26 +07:00
dotCooCoo bdb859f0a1 Docs: art_measured's comment does not link crate-private methods 2026-10-29 13:00:26 +07:00
dotCooCoo 8dd9b962b8 Tests: the nested-instance culling tests count the symbol art a frame measures
The two nested_instances_..._culled_in_bounded_time tests in the
renderer's tests checked that a frame with 2^30 differently placed
symbol instances finishes within 30 s of wall-clock time. On a loaded
machine the frame took longer than that, and both tests failed. The
time bound also passed when the frame did far more work than it should:
with the 4,096 limit on measured placements raised to 65,536, the frame
measured symbol art 131,118 times and finished in under 2 s.

SymbolBoxes now counts how many times measure_art runs a measure on
symbol art (art_measured), and FrameStats::symbol_art holds the count
for the frame's symbol boxes. The tests assert that the frame measures
symbol art at most 20,000 times. It measures it 8,234 times, and 4,160
times when each instance has a Transform effect of its own. The wait
for the frame is 300 s, as a guard against a frame that never ends.

A new test in vectorcraft-doc checks that each symbol's art box is
measured once however many instances of it are measured.
2026-10-29 13:00:25 +07:00
dotCooCoo 6d97ffa700 Tests: tests that depend on wall-clock time or font loads pass on a loaded machine
On a machine busy with other builds, four tests each failed once and
passed when run again alone.

The two render tests that count the rasters a drag moves draw content
painted with a pattern or showing a symbol, whose raster key holds the
font generation. The fonts change even while a test holds
fonts_steady(): the first lookup by family name catalogs the installed
fonts, and resolving a cataloged family or a fallback character loads a
face. When that happened between the two frames of a drag, the count
was 0. The new with_fonts_unchanged runs a step up to 100 times, until
the generation is the same after a run as before it, and the two tests
take their frames from it. The second render test,
a_moved_group_draws_the_shadow_of_the_instance_art_in_it_while_interacting,
was not seen failing but has the same fault.

The screenshot test's allow_due moved the capture's due time but not
its 8 s wall-clock deadline, so on a slow run the reply was the timeout
error. allow_due now also sets the deadline to f64::MAX; the two timeout
tests set it in the past themselves.

The persistent control server test gave the server 2 s to wait for
each request, 120 ms to read a line and 150 ms to write a reply. It now
gives the line and the reply 1 s each and the wait 30 s. The handler
sleeps 1.25 s, longer than both budgets, so a deadline kept from the
first request still runs out.

The idle expiry test read until the server closed the connection after
its 150 ms idle budget, and the client gave up after 3 s, so a pause of
about 3 s in the server's thread failed it with TimedOut. Every test in
control_server/tests.rs that waits for the server to reply, to close a
connection or to return its slot now waits up to WAIT (20 s), and
line_timeouts() gives the wait for a request and the reply 30 s. The
budgets under test stay 120 and 150 ms (1 s in the persistent test).
2026-10-29 13:00:25 +07:00
dotCooCoo f801605213 Brushes: Auto-Sliced corners keep the stroke of a squeezed side tile
On a corner whose legs are too short for the side tile, the tile is
squeezed along the path but its stroke keeps its width. The mask round
each slice was squeezed with the tile, so it cut off the stroke at the
slice's far end. Its margins along the tile now grow by as much as the
tile is squeezed.
2026-10-29 11:22:56 +07:00
dotCooCoo 1c4894b2f7 Brushes: Pattern Brush Options' OK leaves corner tiles that were not set
OK used to save None for a corner tile the dialog showed as None because
the brush had no art or source for it, so that corner stopped drawing the
other kind of corner's art once the brush got some. It also copied the
art a corner borrows from the other kind of corner when that corner kept
its art. Now OK saves a corner tile's choice only when it was set from its
menu or with `ui.dialog.set`, or when the art it borrows goes away.
2026-10-29 11:22:56 +07:00
dotCooCoo d4a30cc084 Brushes: Pattern Brush Options' corner tiles can be None or made from the side tile
Pattern Brush Options has a menu below the Outer Corner Tile and the Inner
Corner Tile. Each can be the brush's own corner art (Original), None, or one
of four tiles made from the side tile at each corner of the path:

- Auto-Centered stretches one side tile around the corner, centered on it.
- Auto-Between bends a side tile on each side into the corner, the two
  meeting on the corner's diagonal.
- Auto-Sliced lays a side tile on each side past the corner and masks it
  at the diagonal, so the two meet like a mitered frame.
- Auto-Overlap lays a side tile on each side past the corner, overlapping.

The tiles are straight at the corner and follow the path towards the side
tiles, so they meet a curve that runs into the corner. A side tile taller
than the corner can reach is stretched along until the two sides meet. On a
short path, a corner's tile is squeezed along so that it covers at most the
path up to an open path's end and half way to the next corner, and its two sides still meet on the
corner's diagonal.

The definition stores the choice as `outer_source` and `inner_source`
(`none`, `autoCentered`, `autoBetween`, `autoSliced`, `autoOverlap`), which
`brush.options` sets. Corner art wins over a source. A brush saved without
a source draws as before: a kind of corner without art takes the other
kind's art. None leaves that kind of corner empty.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:22:56 +07:00
dotCooCoo 3ea2bba6e3 Blend: Escape drops the Blend tool's first pick
After a click picked the first object with the Blend tool, Escape did
nothing: the pick stayed, and the next click on another object blended
the two. After a blend was made, Escape did not stop the next click
adding to it either.

Escape now drops the object picked first and stops the next click
adding to the blend just made, as a click on empty canvas does, so the
next click picks a new first object. The document and the selection
stay as they are, and no undo step is added.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:22:56 +07:00
bflatastic 1924ddd1dd Properties: one filter leaves out Lock and Isolate Group while they're off (keeps show() within the cognitive complexity lint #4299 added) 2026-10-29 11:22:51 +07:00
dotCooCoo 7d04096840 Transparency: Make Opacity Mask is off for a targeted layer in isolation mode 2026-10-29 11:20:08 +07:00
dotCooCoo dfe201dec3 Transparency: isolation mode and opacity mask editing can't be entered from each other
Enter Isolation Mode worked while an opacity mask was edited, and Edit
Opacity Mask and View Opacity Mask worked in isolation mode, so each
mode replaced the other's isolated art. In the reference app, isolation
mode can't be entered while a mask is edited, and mask editing can't be
entered in isolation mode.

object.isolate and perspective.editText, which shows type in isolation
mode, are now off while an opacity mask is edited, and a call returns
an error that ends "stop editing the opacity mask first". In isolation
mode transparency.editOpacityMask and transparency.viewOpacityMask are
off, and transparency.makeOpacityMask refuses one target, an object or
a layer, which it would take into mask editing; a call returns an error
that ends "exit isolation mode first". Make Opacity Mask with two
objects still masks one with the other. Pattern editing mode does not
count as isolation mode for these commands.

Object > Perspective > Edit Text, the canvas context menu and the
Transparency panel's menu and Make Mask button follow the commands'
enablement. A click or Alt-click on the mask thumbnail in isolation
mode shows the error and does not edit the mask. While a mask is
edited, the Contextual Task Bar leaves out Isolate, and the Properties
panel's Quick Actions leave out Isolate Group.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:20:08 +07:00
dotCooCoo e251bbf159 Contextual Task Bar: the Fill and Stroke chips show the proxies' paints
The chips drew the first selected object's fill and stroke. They now show
the paints the Fill and Stroke proxies show, a group's painted art
included, and a "?" when the selected objects' paints differ. A click on
either chip shows the Swatches panel through `window.panel.activate`, so
it also appears when the panel is floating, closed or in an expanded dock
column; an open flyout stays open. The progress doc describes the
reference app's bar in its own words.
2026-10-29 11:20:08 +07:00
dotCooCoo 93bbe36bc9 Contextual Task Bar: a Stroke chip beside Fill picks the stroke color
The task bar's Fill chip had no Stroke counterpart. A Stroke chip now sits
beside it, drawn as a ring with the selected object's stroke paint, as the
Control bar's Stroke chip is. A click on it brings the Stroke proxy forward
and opens the Swatches panel, as a click on the Fill chip does for the
Fill proxy. The chip is left out with the Artboard tool, as Fill is.

The bar's width estimate, which centers the bar under the selection, grows
by the chip's width. The drag test's first square moves 20 pt right so its
bar still starts inside the canvas.
2026-10-29 11:20:08 +07:00
bflatastic 0362ae5453 Object: import choice in object/arrange.rs (#4299 made the imports explicit) 2026-10-29 11:20:07 +07:00
dotCooCoo 02f2dd8c5a Object: Unlock All and Show All with Shift+Alt act only within the selected object's group
Squashed from #4439 onto main.
2026-10-29 11:18:38 +07:00
dotCooCoo a8c166a957 Selection tool: Escape cancels a move, scale, rotation, marquee or widget drag
Escape during a Selection tool move or Alt-drag copy, a bounding-box
scale or rotation, a marquee, or a drag of a corner widget, a pie widget
or the brackets of type on a path did nothing, and the release then
applied the drag. Only a ruler guide, mirror axis or grid repeat drag
and the loaded text took Escape.

Escape in any of these drags now cancels it, the way Direct Selection
cancels its drags: the art returns to its state at the press, an
Alt-drag leaves no copy, no undo step is added, and the release does
nothing.
Outside a drag Escape works as before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:18:38 +07:00
dotCooCoo bbf01223cb Snap to Glyph: the x-height guide stays on the glyphs' x-height
Character Rotation turned a glyph's baseline shift and scaling with the glyph, and the Skew path effect slants a glyph's height away from the path's normal, so the x-height guide moved off the glyphs. Each positioned glyph now carries the transform that removes its Character Rotation (PositionedGlyph::unturned), and the guide goes through the glyph's x-height point placed without it, measured across the baseline. Snap to Glyph also takes Smart Guides being on in the tools, as View › Snap to Glyph already does in the engine.
2026-10-29 11:18:38 +07:00
dotCooCoo f87b8af62b Snap to Glyph: points land on glyph anchors and line up with x-height, character widths and angles
With View › Snap to Glyph and Smart Guides on, a point drawn near a single
line of live type now also lands on the anchor points of its glyphs and lines
up with its x-height (raised or lowered by Baseline Shift) and with the edges
of each character's advance width along the baseline (proximity guides). In
rotated type the baseline, x-height and proximity guides run at the type's
angle, and type of one glyph also has angular guides along the glyph's slanted
straight segments; a segment that Shift keeps to its angle slides onto them.
A paragraph shows glyph bounds only, so its baselines are no longer guides.

FontFace::x_height gives a face's x-height in font units.
2026-10-29 11:18:38 +07:00
bflatastic b97098367a Render: import the shape-mask helpers in renderer/nodes.rs and blobs.rs (#4299 made the imports explicit) 2026-10-29 11:18:37 +07:00
bflatastic 103ec3050f Envelopes: import Color and PreserveShape in live/expand.rs (#4299 made the imports explicit) 2026-10-29 11:16:30 +07:00