Dragging a direction handle with Direct Selection or the Anchor Point tool
sent the pointer position straight to path.setHandle: Shift did nothing and
the handle never snapped, while the Pen already constrains the handles it
pulls out.
guides::snap_handle places a dragged handle: with Shift at a multiple of
45° from its anchor (the Pen's constraint, keeping the dragged length),
otherwise snapped as a drawn point is (smart guides, the grid, pixels).
Both tools use it and show the smart guides while the handle moves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rotate, Scale, Move and the other form dialogs opened with no field
focused, so a typed value went nowhere and Enter applied the default (a
Rotate by 0 degrees). The frame a dialog opens on (after the window's
measuring frame), its first text or number field takes the keyboard
focus with its text selected, so typing replaces it and Enter applies
it, as in the reference app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the paint owner, as a dropped swatch does. The Asset Export panel's drop zone uses
the shared art_drop too. Note the new drag and drop in the ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Art dragged off the canvas and released over the Swatches panel becomes
a pattern swatch of a copy of it (object.pattern.make without entering
pattern editing; the art stays), and over the Brushes panel an Art brush
(brush.new, as the panel's New Brush button makes). A brush dragged out
of the Brushes panel (PanelDrag::Brush, with a preview chip) is applied
to the path it is dropped on (brush.apply). The art drop zone helper
moves to widgets, shared with the Symbols panel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
artboard.move with copy and artboard.duplicate now make the copy with one helper: a
new id that can't overflow and a name of its own (`<name> copy`, then `<name> copy 2`…,
so two copies of one artboard no longer share a name). `moved` lists the copies a copy
moved rather than the originals it left. Test it with and without moveArt, and note
Alt-drag in the ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Alt-dragging an artboard moved it like a plain drag. artboard.move now
takes copy: it leaves the artboard (and the art it carries) where it
is and moves a copy, named "<name> copy", with copies of that art. The
Artboard tool previews it while Alt is held, as "Duplicate Artboard"
(Alt pressed or released mid-drag switches between copy and move),
and makes the copy the active artboard, as in the reference app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in Illustrator, double-clicking an artboard's number goes to it (fits it in the
window) and double-clicking the name renames it. A row's Options button chooses the
Artboard tool on that row's artboard, clicking a row moves the Artboard tool to it
while it is chosen, and an artboard clicked with the Artboard tool becomes the active
one the panel highlights. Note it in the ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel kept its own selection (a pstate nothing else read), so a
click only highlighted the row: the status bar navigator, Fit Artboard
in Window and Actual Size stayed on the previous artboard. The panel's
selection is now the view's active artboard (View::artboard): a click,
Move Up/Down and New Artboard set it, and the highlight follows the
navigator, per document, as in the reference app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Illustrator's Alt-click on a layer's eye hides the other layers and, once they are
hidden, shows every layer again; the lock column does the same with Lock Others and
Unlock All Layers. Reuse the panel menu's commands (one undo step each, named as the
menu items) instead of toggling the row's siblings, so a sublayer's or an object's row
stands for its top-level layer. Document it in the ROADMAP and docs/mcp.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the Layers panel an Alt-click on an eye or lock now applies to the
other items beside that row (the other layers for a layer) in one
object.setProps step: it hides or locks them while any of them is shown
or unlocked, else shows or unlocks them all, as in the reference app. A
plain click still toggles only that row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine kept its own copy of Document::next_layer_name after #353; the
document's now counts sublayers too, so the engine uses it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new sublayer was named "Layer {node_count}", counting every object, so
an empty document's first sublayer was "Layer 1" like its parent and a
document with five objects got "Layer 6". Top-level layers counted only
top-level names and could repeat a sublayer's. Document::next_layer_name
now counts every layer, sublayers included, and skips any layer's name;
layer.newSublayer uses it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Line Segment family (Arc, Spiral, Rectangular Grid, Polar Grid)
ignored Space while drawing, as the shape tools did before #302. Both
tools now share shape::space_moves. A headless canvas test presses Space
for real in the middle of a Rectangle and a Pen drag: the shape and the
anchor move and the view doesn't pan. Adds the ROADMAP entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shape tools (Rectangle, Rounded Rectangle, Ellipse, Polygon, Star,
Line Segment) ignored Space during a drag. While Space is held once the
drag has begun, the drag's start now moves with the pointer, so the
shape being drawn moves at its current size; letting go of Space resumes
sizing from there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Pen's Auto Add/Delete had its own copy of the anchor and segment hit
tests and of the path.insertAnchor / path.removeAnchor actions the Add
and Delete Anchor Point tools build. draw2 now exposes anchor_in /
segment_in (the hit tests over a given set of paths) and insert_anchor /
remove_anchor, which both tools use. Adds the ROADMAP entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Between paths, the Pen now adds an anchor where a click lands on a
segment of a selected path and deletes the anchor a click lands on,
with the add and delete cursors while hovering, as in the reference
app. The ends of a selected open path still continue it. Shift held,
or General > Disable Auto Add/Delete (a stored preference nothing read
until now, passed to tools as ToolContext::auto_add_delete), starts a
new path instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While drawing, a click on the last anchor appended a second anchor at
the same spot, keeping the old outgoing handle, so the path got a
zero-length curve that showed as a spike. Now a click there retracts
the anchor's outgoing handle (the next segment leaves it as a corner)
and a drag pulls a new outgoing handle out on its own, with a convert
cursor (^) over it, as in the reference app. A one-anchor path no
longer closes on itself when its anchor is clicked again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Pen ignored Space during a drag, so the anchor being placed stayed
where the button went down and its handles swung round it. Space held
now moves the anchor, handles and all, as far as the pointer moves
(the handle Alt kept moves along too); releasing Space goes back to
shaping the handles round the new place, as in the reference app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pressing Alt while dragging out a smooth point set its incoming handle to
the anchor itself, so the segment just shaped turned straight. The Pen
now remembers the incoming handle it last previewed and keeps it while
Alt is held, so only the outgoing handle follows the pointer: a corner
with curves on both sides, as in the reference app. Alt held from the
start of the drag still gives an outgoing handle only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rows at every depth highlight on click (Shift range, Ctrl toggle) and make
their layer current; selection squares select a row's art and drag the
selection to another row; eye and lock on every row with drag-down-the-column
in one undo step and Ctrl-click for Outline; Alt-click triangles; drag and drop
rows above, below or into layers and groups with a drop line (Alt copies);
Delete key; the full panel menu, Layer Options and Panel Options dialogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Art in a sublayer is now selected as objects (clicks, marquee, Select All, the
Select menu), sublayers select in their own colour, and hidden, locked or
template sublayers block clicks. Layer Options gain Preview (per-layer outline)
and Dim Images. New and fixed layer.* commands for the Layers panel: setCurrent
and highlight (rows at any depth), selectAll, move (reparent with cycle and
nesting checks, Alt copies), delete/duplicate rows, merge, flatten, collect,
release to layers, reverse, template, hide/outline/lock others and their
show/preview/unlock all, locate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The » at the top of the right dock was only painted. Its header strip is
now a click target (hover highlight, "Collapse to Icons" tooltip) that
runs the new window.collapseDock UI command: the Properties | Layers |
Libraries group hides and its three panels become icons at the top of
the icon column, under a « strip ("Expand Panels") that expands them
again. While collapsed, those icons and window.panel pop the panel out
next to the column like the other icon panels (dock-width flyout, Layers
and Properties given a height that keeps them on screen); expanding with
one popped out shows its tab.
The flyout now follows the icon column's real left edge (recorded each
frame) instead of assuming a 300-point dock, so it also tracks a resized
or collapsed dock. The collapsed state is kept with the UI preferences
and in user workspaces; the built-in workspaces expand the dock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On hybrid-graphics laptops (an integrated GPU driving the display plus a
discrete one), egui-wgpu's default power preference is HighPerformance, so
VectorCraft rendered on the discrete GPU and its frames were copied to the
display through the integrated one. On an Asus ZenBook with Intel Iris + Arc
the window flickered whenever the mouse moved over it (#306, #305).
The canvas is rasterized on the CPU (vello_cpu) and the GPU only composites
the canvas texture and draws the UI, so the integrated GPU is plenty. The
desktop app now asks wgpu for the power-saving adapter by default.
Single-GPU machines are unaffected.
- Preferences > Performance > Graphics Processor (`gpuPreference`:
powerSaving, the default, or highPerformance), with a note that it
applies after a restart. The app reads it from the saved preferences
before the window opens. WGPU_POWER_PREF still overrides it.
- Help > About and `ui.inspect` (`graphicsAdapter`) name the adapter in
use, and it is logged at startup, for future GPU bug reports.
- Docs (README, docs/development.md) and ROADMAP updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The font database made one style per font file, so a variable font
such as Figtree[wght].ttf listed only its default instance (Light).
Faces now also yield their fvar named instances, each a style of the
family named by its subfamily name (the default instance is the face
itself; unnamed or duplicate instances are left out; axes and
instances read are capped). The system font scan reads the fvar table
beside the name table, so installed instances are listed before the
file is loaded and found by name and by PostScript name.
An instance face carries its axis settings: skrifa draws its outlines
and reads its metrics and advances at that location, harfrust shapes
with a ShaperInstance, so layout, the canvas, Create Outlines and hit
testing all use the instance. PDF export embeds it through krilla's
Font::new_variable, SVG @font-face through subsetter's
subset_with_variations: both write a static font of that instance.
Tests build a small variable font in code (test_fonts, via
write-fonts; no font file is committed) and check listing, resolving,
glyphs and advances, PDF and SVG export and damaged fvar/gvar tables;
Bahnschrift and Segoe UI Variable are checked where installed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the in-window menu bar (Windows and Linux) each top-level menu needed
its own click: with File open, moving the pointer onto Edit or Object did
nothing. Now, as on a native menu bar, while one menu is open a moving
pointer that reaches another title opens that menu instead (ported from
PhotoCraft). A pointer resting on a title doesn't switch, and a tall menu
that covers the bar doesn't switch to the title underneath it. The native
macOS menu bar is unchanged.
Also adds a regression test for #292: the Simplify, Offset Path, Move,
Rotate, Split Into Grid and Average dialogs are 366 pt wide in a 1600 pt
window (already fixed by #239).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A live rectangle kept every transform in its `xf`, so a non-uniform scale
(a bounding-box drag, Object > Transform > Scale) stretched the rounded
corners built in untransformed space into elliptical arcs. Scale Corners
off divided the radii by the mean scale, which only compensated uniform
scales.
LiveShape::transform now folds a scale along the rectangle's own sides
(any rotation or reflection, no shear) into w/h and keeps only rotation,
reflection and position in `xf`, so the radii are document lengths and the
path is regenerated with circular corners. Radii scale by the mean scale
(sqrt|det|), which keep_corners undoes for Scale Corners off. Shears and
plain moves keep composing into `xf` as before, so files saved with a
scale in `xf` keep their exact geometry until next scaled or rotated.
A corner-widget drag starts from the radius as drawn (clamped to fit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A regression test for non-ASCII paths. It saves to a Japanese file name, with
full-width brackets and a middle dot, inside a Japanese folder. It checks the
document's title, reopens the file with its Japanese text, and exports SVG, PNG
and PDF to Japanese names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file with several artboards writes, on each page, the art of its
neighbours that reaches the page's box, shifted by its own artboard
spacing. Kept, those copies drew over the art of the page they came from
(a hologram over its card's stripes and title). With several pages, art
wholly outside a page's box now stays with the page it belongs to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Art dragged off the canvas and released over the Symbols panel's list
becomes a new symbol (symbol.new with the dragged ids), and a symbol
dragged out of the panel (PanelDrag::Symbol) is placed on the canvas
centred where it is dropped (symbol.place), showing its thumbnail at
the pointer on the way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Selection tool's double-click on text only switched to the Type
tool, which then had nothing being edited, so typing went to the tool
shortcuts (X swapped the text's fill and stroke). After such a switch
the canvas passes the double-click on to the Type tool as a click, which
starts editing the type with the caret where it was clicked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
egui reports the character a key types, so Cmd+Shift+[ arrives as
Cmd+Shift+{ (], / likewise as }, ?) and Shift+` as the text ~, and these
default shortcuts never matched: Bring to Front, Send to Back, Search
Commands and the Curvature tool. Command chords now also take the
shifted character, single-key tool and command shortcuts also look up
Shift+<typed text>, and the Keyboard Shortcuts editor records such a
chord as its key (Cmd+Shift+], not Cmd+Shift+}), so conflicts show.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The path rebuilt through the glyphs ended where the last glyph ends, so
rounding in the spacing could push that glyph past the end, where type on
a path stops ("OFFICIAL ISSUED" lost its D). The path now runs an em
further.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apps write type on a path one glyph at a time, each placed and turned along the
curve. The importer joined glyphs into a line only while they kept the first
glyph's direction. Type on a gentle curve was flattened into straight lines, and
type on a tighter curve fell apart into one object per glyph.
A glyph that starts about where the previous one ends, turned a little from it
(under about 20°), now continues the line. A line whose baseline turned more than
about 3° becomes type on a path: its path is a smooth curve (Catmull-Rom as cubic
Béziers) through the glyphs' baseline origins and the end of the last glyph.
Spaces between words are measured along the last glyph. Straight type, rotated or
not, imports as before.
Test: "ARCHING" written glyph by glyph along an arc imports as one
type-on-a-path object with that text and a curved path; slightly rotated straight
type stays point type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Held Space always panned, whatever else was held, so the temporary Zoom
tool keys did nothing. Held Space is now the Hand tool and Cmd+Space the
Zoom tool (Alt: out), whatever the active tool, for the press and the
cursor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The canvas always switched to the Selection tool for a Cmd press with a
non-selection tool. The UI state now remembers which of Selection,
Direct Selection and Group Selection was used last, and the temporary
switch uses it, so Cmd-dragging an anchor while drawing with the Pen
after using Direct Selection moves that anchor, not the whole path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dragging a bounding-box handle of area type with the Selection tool now
resizes the type area: the text reflows at its own size (threads too)
instead of being scaled, and History says "Resize Type Area". With other
objects selected they scale as before while the area type resizes its
frame. Direct Selection picks the frame's corners and edges and reshapes
the area ("Reshape Type Area", new command text.reshapeArea), and the
selected frame's anchors and outline show. Area Type Options gains Width
and Height. Point type, rotation, Object > Transform > Scale, the Scale
tool and the Transform panel still transform the type.
Root cause: every handle drag previewed object.transform, which folds the
matrix into the text's xf (TextObject::transform), so area type scaled its
glyphs; and Direct Selection's anchor hit test only looked at paths, so the
frame's corners could not be picked. object.transform now takes
typeAreas, which reshapes the frame in text space (xf^-1 * m * xf), and
the tools use it and text.reshapeArea.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In vertical type every character now sits in its own cell down the column:
"10" and "22" in 「10月22日」 stand side by side in one em, centred on the
column, and 月/日 start right after them. Justified (Justify All) columns
no longer split a tate-chu-yoko block, and tracked or justified upright
characters stay on the column's centre line instead of drifting across it.
Root cause: in 0.3.1 (the reporter's build) upright glyphs were placed by
the font's ascent rather than the ideographic em box, so a font with a tall
ascent (Noto Sans CJK JP, 1.16 em) drew each upright character ~0.28 em
down into the next cell, onto the sideways digits; #208 (after 0.3.1)
replaced that with em-box placement and tate-chu-yoko, which this commit
locks in with regression tests. Two defects of the same kind remained:
Justify All added its inter-character gap inside a tate-chu-yoko block
(the block's second digit slid down the column, away from the first), and
upright glyphs turned about the middle of their advance, which tracking and
justification lengthen, instead of the middle of their own cell.
Tests: the issue's layout with bundled glyphs (cells one after the other,
every ink inside its cell and no overlaps, 2-3 digit blocks one em and
centred, single digits and 2026 on their side advancing by their length,
spaces), the issue's string with craft-fonts' Japanese face (skips without
craft-fonts), Justify All with a block, tracking.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tool chose scale, distort, perspective or shear from the modifiers at
the press only, but a Cmd press on the canvas is the temporary Selection
tool, and the documented gesture is to start dragging and then hold Cmd.
A handle drag now follows the modifiers held during the drag; when the
operation changes, the interaction restarts so the undo step is named
after what was done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The navigator's buttons were painted only and its number was always 1,
and Fit Artboard in Window and Actual Size always used the first
artboard. The per-document View now keeps the navigator's artboard; the
new view.goToArtboard UI command (an index, or first / previous / next /
last) sets it and fits it in the window, and the four buttons call it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A double-click on a tool button runs tool.options, which only knew a few
tools, so the common double-clicks did nothing: the Hand tool now fits
the artboard in the window, the Zoom tool shows 100%, and Rotate, Scale,
Reflect and Shear open their Object > Transform dialog (when something
is selected, like the menu items).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The export tool's description, the docs' open_file and export rows and the
CLI's run/convert usage listed only some formats. The CLI usage now prints
the writable extensions next to the readable ones, both generated from the
format table (export_extensions), the export tool points to its generated
format enum, and the docs name EPS, DXF, EMF, WMF, SVGZ and templates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It ran document.save, which keeps the document's own file and format (or
takes the format the path's extension picks), but described itself as a
native .vectorcraft save. The description now says so, with the formats
generated from SAVE_FORMATS, and points to export for the others.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A type node's fill, stroke and strokeWidth now come from its first run, as
paint.proxies and its strokeOptions already did; fills and strokes of the
type object itself are reported apart as objectFill, objectStroke and
objectStrokeWidth.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.exportSelection (the MCP export tool's `selection: true`) now
replies with `format` and the encoder's `warnings`, the same list a
whole-document export gives: lossy WebP written lossless, blend modes left
out of SVG Tiny 1.2, PDF layers at 1.4 and the rest. A selection export
that approximates nothing replies with an empty list.
Root cause: export_selection encoded with `encode`, which drops the
warnings encode_with_warnings returns, and its reply carried only path,
bytes and bounds. The params doc and docs/mcp.md now describe the reply.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rulers were only painted. A drag from the top ruler now previews a
horizontal guide and adds it with guide.add where the button is released
on the canvas; the left ruler makes a vertical one. Released anywhere
else, nothing is added. Guides are shown when one is made this way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japanese covered 36 menu labels after #145; every other label stayed
English. ja.tsv now holds all 556 interface menu labels, in menu order,
including both readings of every toggle, plus the labels added since: the
Language menu's Automatic and the canvas context menu's. #145's 36 entries
are unchanged. Kept in English, as for Czech: the product name, language
names, OpenType, the built-in workspace names and the bracketed perspective
grid presets.
The Czech coverage test becomes menu_catalogs_translate_every_menu_label,
run for Czech and Japanese. ROADMAP and docs/development.md say both cover
every menu label.
Ported by the maintainers from the Rust table of the original PR onto the
catalog framework (#205): the placeholder labels of recent-file, recent-font,
user-preset and user-library slots are left out (the menus show the names).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The command palette caches its entries per language and plug-in set
(#243), but each entry also shows its shortcut: after editing shortcuts
the palette kept showing the old ones until the language changed. The
cache key now includes the shortcut editor's generation. From #242.
Co-authored-by: WildfootW <wildfootw@wildfoo.tw>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>