The grid was drawn whenever its `visible` flag was set or a perspective
tool was active, and choosing the Perspective Grid tool (Shift+P) didn't set
the flag. So with the tool chosen, View > Perspective Grid > Hide Grid
(perspective.grid.show) turned the hidden flag on instead of off and the
grid stayed on screen however it was toggled.
Choosing a perspective tool now shows the grid (sets `visible`, view state,
no undo step), and the grid, its widget and the Perspective Grid tool's
handles are drawn only while it is visible. Hide Grid hides it with the
tool still chosen; choosing the tool again shows it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Parity: Object › Path › Remove Anchor Points now runs the new command (`path.removeAnchors`,
the menu's feature name) instead of `path.deleteAnchors`, which deleted the anchors with their
segments and opened closed paths; that stays the Delete key's command ("Delete Anchor Points",
off the menu). No second "Smart" item.
- Geometry: the refit moves to `vectorcraft_pathops::remove_anchor` on the existing least-squares
fitter (fixed end tangents, a golden-section search for where the curve passes the removed
point) instead of a ~1,000-candidate grid search in geom (about 4 ms per anchor, now well under
0.1 ms). Two straight sides stay one straight side (a rectangle loses a corner and becomes a
triangle), as Illustrator does; non-finite input leaves the handles alone.
- The Delete Anchor Point tool (`path.removeAnchor`) uses the same function, replacing its
heuristic handle stretch; the explicit id/anchor and `fit` params of the new command are dropped
as duplicates of it. The command is enabled by direct-selected anchors (`has_anchors`), not a
UI override.
- The Control bar shows an icon button (the Delete Anchor Point icon, tooltip "Remove Anchor
Points") instead of a 204 pt text button; the labels reuse the translated "Remove Anchor
Points"; zh-hant gains "Delete Anchor Points". The Pen test that pinned Alt-click as plain Pen
(Illustrator's Alt over an anchor is Convert Anchor Point) is dropped.
- Tests for the refit, closed paths, straight sides and bad input; docs/mcp.md and ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
path.smartRemoveAnchor stays. Object › Path, the Control bar, the contextual task bar, Properties, and the canvas context menu run it on direct-selected anchors. The Pen no longer uses Alt/Option for this, so that key stays free for Convert Anchor Point.
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>
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>
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>
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>
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>
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 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>
Dragging a bounding-box handle with the Selection or Free Transform tool
now lands the edges it moves on the edges and centres of the other objects
and artboards, with the guide line, as a move already did. A proportional
scale snaps the closer axis only; a turned bounding box doesn't snap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013j16ENkbsNYyHSCDhxYkYR
Japanese full-width punctuation is half a mark and half a space (an
opening bracket's space before it; a closing bracket's, comma's or full
stop's after it). Set solid, 」「 or 。」 show a full em of air between
the marks, and a line ending in 。 stops half an em short of the edge.
- `ParaStyle.mojikumi` (Paragraph panel › Mojikumi): None, or Line-end
Punctuation Half Width, which (JLREQ 3.1.4) lets consecutive
punctuation share one half-em space — a closing mark followed by
punctuation loses the space after it, an opening bracket after another
the space before it — and sets a closing mark ending a line half
width. Only full-width marks (advance an em) are touched, never inside
a tate-chu-yoko block; vertical type the same way down the column.
- New type (`text.create`, Area/Path Type) takes Line-end Punctuation
Half Width, the usual default of Japanese layout; documents from
before it and imported text keep None (written only when set, so
older files and older apps are unaffected). `text.setFormat
{mojikumi: "none"|"lineEndHalf"}`.
- Builds on #344: on justified lines the room it frees is spread
between the characters.
- Catalog rows in ja and zh-hant.
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>
Justify (last line left, centred or right) widened only word spaces, so
a Japanese line, which has none, stayed set solid and the right edge of
a justified Japanese paragraph was ragged by up to almost a character
(only Justify All spread the characters). Following JLREQ 3.8, the room
left on a line now goes to the gaps next to a CJK character, shared
with any word spaces:
- not inside a Latin word or a tate-chu-yoko block, nor next to a tab;
- lines without CJK characters are justified as before;
- vertical type too (the same code, in line space).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upright glyphs in vertical type advanced one em (or their horizontal
advance, if wider) and turned about a fixed point 0.38 em above the
baseline. Fonts that carry vertical metrics now place them as they ask:
- `FontFace::vertical_glyph`: a glyph's advance down the column (`vmtx`)
and the height of its vertical origin (`VORG`, else the glyph's top
plus its top side bearing, as in TrueType fonts); `None` without
`vhea`/`vmtx` or with values outside a few ems.
`FontFace::ideographic_centre`: the em box centre from an ideograph's
cell, else 0.38.
- The layout advances an upright glyph by its vertical advance and hangs
it from its vertical origin; tate-chu-yoko blocks and point type's
anchor use the font's em box centre. Fonts without vertical metrics
are laid out exactly as before.
- Hiragino's tables give the values the layout assumed (one em, origin
0.88 em), so Japanese vertical type in it renders pixel for pixel the
same; this is the base for proportional vertical metrics (`vpal`) and
for fonts whose em box sits elsewhere.
- `test_fonts::vertical_font`: Source Sans 3 with `vhea`/`vmtx` (and
optionally `VORG`) built in code, one glyph 1.4 em tall hanging from
1.1 em; tested with and without `VORG`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Character and Properties panels' font menu was a plain searchable
list of names. With hundreds of installed families (and Japanese names
that say little to someone who can't read them) a name isn't enough.
- Each row shows the family's name and a sample of the selected text set
in it (a generic sample when the font lacks its characters). Samples
are rendered off the UI thread, a few per frame, and only for the rows
in view (the list is virtualised).
- The row under the pointer or reached with ↑/↓ previews its font on the
selected text, as one live interaction with nothing in the history; a
click or Enter applies it as one step, Escape or closing the menu puts
the text back. The current family is highlighted and scrolled into
view, also when the document names it in another language.
- Filters by script (Japanese, Latin) and kind (serif/Mincho, sans
serif/Gothic, rounded, script/brush, monospaced, decorative), from the
font's OS/2 classification and, for CJK fonts whose tables seldom say,
its names. `FontTraits`/`FontClass` in the text crate; installed
families get them from the same scan as their names.
- ▸ (or →/←) lists a family's styles; a style row previews and applies
that style.
- ☆ stars a family; the ★ filter shows only the favourites (kept with
the UI state).
- Preferences › Type › Enable in-menu font previews and Font Preview
Size now turn the samples off and size the rows; the in-window Type ›
Font menu (Windows, Linux) shows a sample beside each name.
- Families whose name starts with a dot (the system's own) are hidden.
- Catalog rows for the new labels in zh-hant (complete) and ja.
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>
#249 made the reduced repros of #234 open, but a real file saved by the
reference app (version 30) still fell back to its screen-resolution preview
with "typecheck in `a dictionary`". Running its prolog to the end needed:
- The standard dictionaries (systemdict, userdict, globaldict, statusdict,
errordict, $error) as values in systemdict, so `/globaldict where
{ /globaldict get begin } if` gets a dictionary, not an operator.
SharedFontDirectory names the font directory too. errordict has a
handleerror for prologs that wrap it.
- `dictstack`, `internaldict`, `currentsystemparams` and
`currentuserparams` (with the cache sizes programs `get`), and `cshow`.
- Executable files: `cvx` and `cvlit` set a file's attribute, `xcheck`
reads it, and executing one runs its data as a program, as the prolog
does with `(...) 0 () /SubFileDecode filter cvx exec`.
- SubFileDecode with a count of 0 and an empty end string passes its
whole source through instead of nothing.
- `flushfile` on a filter over the program's data skips that data (the
file's XMP packet). A filter chained from a pending one leaves it
nothing to skip, so the writer's own `VCsrc flushfile` is unchanged.
The interpreter empties its standard dictionaries when it finishes, since
systemdict now holds itself. Both real files tested now import as 15
vector paths in CMYK with no warnings, and round-trip through SVG and PDF
pixel for pixel. The EPS fuzzer exercises the new operators.
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>
Save Selection… asks for a name and keeps the selection in the document (saved with
the file, undoable, at most 25) instead of in session memory keyed by the title.
Each saved selection is listed at the bottom of the Select menu and a click selects
its objects again. Edit Selection… opens a dialog with the list to rename or delete
them, applied together on OK as one undo step (select.editSaved takes `edits`).
The menu stub count in the ROADMAP goes from 16 to 15.
Closes#217. The Japanese catalog now covers every menu string and every
tl! literal, so `complete_menus` is on for `ja` and the tests keep it
complete.
- ja.tsv: about 2,300 rows added after the existing menu rows, which are
unchanged: panels, dialogs, tooltips, the hint bar, document tabs,
status messages and the strings built at run time (section lists,
counts, `@plural` rows). Written from the meaning of the English
labels in the vocabulary Japanese users of vector illustration
software know; product and format names stay in Latin letters, the
trailing "…" is kept, and placeholders appear in both columns.
- Kept in English, as before: the product name, OpenType, the built-in
workspace names and the bracketed perspective preset names.
- tests: the languages that keep those names in English (`cs`, `ja`) are
listed once (`KEEPS_MENU_NAMES`), and the completeness test skips the
kept names for them, so a language can keep them and still be
complete.
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>
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>