Commit Graph
6301 Commits
Author SHA1 Message Date
BFlat f0d2cbf432 Merge pull request #4094 from dotCooCoo/fix/shape-builder-merge-keeps-unused-objects
Shape Builder: a merge replaces only the paths it uses, where they are
2026-10-29 13:49:09 +07:00
BFlat 16368083e8 Merge pull request #3954 from saad-mughal435/fix/pattern-brush-keeps-curves
Pattern Brush: keep the tile's curves on a straight run (#3926)
2026-10-29 13:48:56 +07:00
BFlat 0a9d791e33 Merge pull request #4453 from krosk93/i18n/catalan-localization
i18n: add Catalan (ca) localization
2026-10-29 13:48:33 +07:00
BFlat 2df6ab5b91 Merge pull request #4374 from devius/ka-new-strings
i18n(ka): Georgian for the batch dialog, PDF presets, dimension options and swatch names
2026-10-29 13:48:13 +07:00
BFlat 4f1a1bcad1 Merge pull request #4327 from devius/ka-wording-fixes
i18n(ka): Georgian wording fixes for 10 rows
2026-10-29 13:48:00 +07:00
BFlat 814d85c8db Merge pull request #4320 from JaffarSk24/i18n-ru-catalog-gaps
i18n(ru): 363 rows the Russian catalog was missing
2026-10-29 13:47:43 +07:00
BFlat 865986c629 Merge pull request #4540 from storytold/land/4292
Fix canvas pointer mapping under subpixel dock layouts
2026-10-29 13:47:04 +07:00
BFlat df74fa3f0b Merge pull request #4520 from mockydevs/fix/gpu-start-tests-spawn-race
test(app): keep a child's start from holding the GPU start file's lock
2026-10-29 13:46:29 +07:00
BFlat 28ed176ea0 Merge pull request #4500 from dotCooCoo/fix/document-color-mode-converts-all-art
Document Color Mode: also converts graph designs, graphic styles, opacity masks and brush art
2026-10-29 13:46:16 +07:00
BFlat 74b808475c Merge pull request #4480 from dotCooCoo/fix/portal-save-response-race
Save As: on Linux an older portal's answer to the save dialog is no longer lost
2026-10-29 13:45:57 +07:00
BFlat 8558ce3031 Merge pull request #4478 from dotCooCoo/fix/mcp-remote-tests-load-waits
Tests: the MCP remote-backend tests pass on a loaded machine
2026-10-29 13:45:47 +07:00
BFlat a389fbb116 Merge pull request #4464 from dotCooCoo/fix/shape-builder-source-no-isolation-in-mask-editing
Shaper: Shaper Group edits keep the opacity mask and pattern editing layers isolated
2026-10-29 13:45:29 +07:00
BFlat 3c54a4a1cb Merge pull request #4457 from dotCooCoo/fix/isolate-error-catalog-rows
i18n: Enter Isolation Mode's errors show in the interface language
2026-10-29 13:45:13 +07:00
BFlat 04e6699f04 Merge pull request #4456 from dotCooCoo/feat/live-paint-selection-marquee
Live Paint: a Live Paint Selection marquee selects the faces and edges it touches
2026-10-29 13:44:54 +07:00
BFlat ced45272f8 Merge pull request #4452 from dotCooCoo/fix/culling-tests-bound-work-not-time
Tests: the nested-instance culling tests count the symbol art a frame measures
2026-10-29 13:44:39 +07:00
BFlat ff107f0f96 Merge pull request #4450 from dotCooCoo/fix/load-sensitive-tests
Tests: tests that depend on wall-clock time or font loads pass on a loaded machine
2026-10-29 13:44:24 +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
krosk93 7a79935c10 i18n: add Catalan (ca) localization
Squashed from #4453 onto main.
2026-10-29 13:00:44 +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
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
Don Omondi e94b74eea4 test(app): keep a child's start from holding the GPU start file's lock
The GPU start tests release the start file's lock and take it again.
portal_save's mock_portal starts dbus-daemon meanwhile, and until a child
runs its program it holds a copy of every open descriptor, so the lock
outlived the test's own handle: the next start found the file locked, took
it as another copy of the app running and left nothing out. Parallel runs
of the app's tests failed 12 times in 20.

A test-only RwLock: mock_portal holds it exclusively while it spawns, and
each GPU start test holds it shared for the whole test. 0 failures in 70
parallel runs.
2026-10-29 13:00:41 +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 c0ad2f23c4 Save As: on Linux an older portal's answer to the save dialog is no longer lost
A desktop portal older than 0.9 ignores handle_token and answers on a
request path of its own. save_file subscribed to that path only after
SaveFile returned it. zbus makes three bus round trips to set up that
subscription. A Response that arrived during them was dropped, and the
save waited forever. On CI the test
the_answer_is_awaited_on_the_path_the_portal_returns waited until the
job limit.

save_file now makes one stream for every Response from the portal
before it calls SaveFile, and takes the first Response whose path is
the returned handle and whose sender is the connection that sent the
reply. A Response that comes before the reply stays queued in the
stream.

The mock portal now creates its message stream before it starts its
thread and before the client connects, so a SaveFile call that arrives
before the thread runs is no longer dropped. A new mode sends the
Response before the reply, and the D-Bus tests fail after 30 seconds
instead of waiting without end.
2026-10-29 13:00:39 +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 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 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
BFlat 63e3023a08 Merge pull request #4445 from dotCooCoo/feat/pattern-auto-corners
Brushes: Pattern Brush Options' corner tiles can be None or made from the side tile
2026-10-29 12:17:20 +07:00
BFlat 5262334d17 Merge pull request #4444 from dotCooCoo/fix/blend-tool-escape-drops-first-pick
Blend: Escape drops the Blend tool's first pick
2026-10-29 12:17:01 +07:00
BFlat 0142020357 Merge pull request #4441 from dotCooCoo/fix/isolation-and-mask-editing-exclusive
Transparency: isolation mode and opacity mask editing can't be entered from each other
2026-10-29 12:16:48 +07:00
BFlat e0b572157d Merge pull request #4440 from dotCooCoo/feat/taskbar-stroke
Contextual Task Bar: a Stroke chip beside Fill picks the stroke color
2026-10-29 12:16:31 +07:00
BFlat 3aa8acbc80 Merge pull request #4439 from dotCooCoo/feat/unlock-show-all-within-group
Object: Unlock All and Show All with Shift+Alt act only within the selected object's group
2026-10-29 12:16:15 +07:00
BFlat 0d82d45578 Merge pull request #4438 from dotCooCoo/fix/selection-escape-cancels-drag
Selection tool: Escape cancels a move, scale, rotation, marquee or widget drag
2026-10-29 12:15:57 +07:00
BFlat c79f5ec989 Merge pull request #4437 from dotCooCoo/feat/snap-to-glyph-anchors
Snap to Glyph: points land on glyph anchors and line up with x-height, character widths and angles
2026-10-29 12:15:36 +07:00
BFlat 40aeb56fb4 Merge pull request #4434 from dotCooCoo/feat/envelope-image-options
Envelopes: Anti-Alias and Preserve Shape Using change how distorted images draw
2026-10-29 12:15:25 +07:00