Commit Graph
6279 Commits
Author SHA1 Message Date
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 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
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
BFlat 127b1e3898 Merge pull request #4432 from dotCooCoo/fix/star-ctrl-holds-shown-inner-radius
Star tool: Ctrl/Cmd holds the inner radius the star shows when the key goes down
2026-10-29 12:15:12 +07:00
BFlat b0909040bc Merge pull request #4430 from dotCooCoo/fix/escape-cancels-more-tool-drags
Tools: Escape cancels an Anchor Point, Mesh, Shape Builder or Measure drag
2026-10-29 12:14:56 +07:00
BFlat bda4305ffe Merge pull request #4519 from mockydevs/fix/3575-native-coordinates
fix(format): remove what a native file has beyond the canvas (#3575)
2026-10-29 12:14:37 +07:00
BFlat 2b7e8ca7a0 Merge pull request #4515 from mockydevs/fix/3587-native-format-memory-budget
fix(format): read a native document within an object budget (#3587)
2026-10-29 12:14:19 +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 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
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
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 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 7d04096840 Transparency: Make Opacity Mask is off for a targeted layer in isolation mode 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
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
bflatastic 0362ae5453 Object: import choice in object/arrange.rs (#4299 made the imports explicit) 2026-10-29 11:20:07 +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 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 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
dotCooCoo acc5e53c91 Envelopes: a pixelated CMYK image with its own grid mask is written without /Interpolate
PDF export writes a CMYK image whose opacity mask is a gray image on its
own pixel grid with the mask as the image's soft mask. That path always
asked viewers to smooth the image, so a pixelated piece of an envelope
with Anti-Alias off was still written with /Interpolate when it had such
a mask. It now takes the pixelated flag as images without a mask do, and
caches the smoothed and pixelated versions apart.
2026-10-29 11:07:58 +07:00
dotCooCoo ab69d8f6d9 Envelopes: Anti-Alias and Preserve Shape Using change how distorted images draw
Envelope Options stored Anti-Alias and Preserve Shape Using, but nothing that
draws an image read them: a distorted image was always smoothed and always
cut into clip groups.

- With Anti-Alias off, each piece of a distorted image is marked pixelated
  (saved as `pixelated`). The renderer draws it with nearest-neighbor
  sampling and without mips, SVG export writes `image-rendering: pixelated`,
  and PDF export writes it without /Interpolate.
- With Preserve Shape Using Transparency, each piece is the image with a
  white opacity mask in the piece's shape instead of a clip group. The
  renderer paints an image whose mask is one plain white path straight into
  that path, as it does for an image in a clip group, when the path lies
  inside the image's frame.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:07:57 +07:00