Each paragraph has its own alignment, indents, spacing, hyphenation, tab
stops and paragraph style (TextObject.paras; para stays paragraph 0's, so
old files and readers work). Layout, editing (split/merge), threads, the
Paragraph/Tabs/Paragraph Styles panels, ranged text.setStyle/setFormat/
tabs/paraStyle.apply, SVG export/import and the fuzzers follow.
VectorCraft asks for a path where it needs one and goes on with it at
once, so its file dialogs ran on the UI thread. Windows and macOS dialogs
run the window's events meanwhile; on Linux nothing answered the Wayland
compositor's pings while a dialog was open, and Hyprland offered to kill
the window as not responding.
- ui-egui `picks`: every file dialog (open, save, folder, place) goes
through one place. With the host's `start_pick` (desktop Linux) the
dialog shows on a thread of its own; what asked (the command, the
dialog's OK, a panel's or dialog's button) gives up for now and runs
again with the picked path when the dialog answers. Another dialog
waits for the open one.
- The desktop app passes its window as the dialogs' parent on Linux (the
portal can then associate its window, "Unhandled parent window type"
before) and Windows; macOS shows them as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Illustrator's Every-line Composer balances the breaks of the whole
paragraph for left/centre/right aligned text too, not only justified
text. VectorCraft only composed justified area text; ragged text was
always broken greedily.
- Model: ParaStyle.composer (vectorcraft_doc::Composer, re-exported by
vectorcraft-text), Every-line by default like Illustrator, saved only
when Single-line. layout() reads it from the object;
LayoutOptions.composer is now an Option override for tests.
- Composer: the justified gate is gone. Ragged lines never shrink (a
line that doesn't fit ends the node), badness is 100*r^3 with r = the
line's leftover space over a rag zone of 1/6 of the width (finite for
lines without spaces); the last line is free unless shorter than 1/3
of the width (measured in zones of 1/3), so a lone short word on the
last line pulls a word down. Hyphens cost 25 in ragged text (50 in
justified). Knuth-Plass fitness classes with adjacency demerits
(justified 10000, ragged 1000). Lines past the last width change
share one node key, active nodes are capped (4096) and paragraphs
over 200k glyphs break single-line, so work stays bounded; ragged
tries a tolerance of one rag zone, then any fitting breaks, then
falls back to greedy.
- text.setFormat {composer: "singleLine"|"everyLine"}; the Paragraph
panel menu's composer items work with a check mark (Reset Panel goes
back to Every-line).
- Tests: ragged Every-line moves "or" down in "Destroy all creatures
with toughness X or less." where greedy leaves "less." alone; the rag
of long paragraphs is no worse than greedy (sum of squared leftover
and of neighbour differences); Single-line is greedy; the 10k-char
layout budget covers ragged text; command test with undo; native
format round trip (rich_session's text is now Single-line).
No existing test needed changing: the justified tests and
hyphenated_layout_adds_hyphens (ragged, now Every-line) still pass.
Auto alignment (start of the paragraph direction) is ragged; a wrapped
line's Mojikumi flush opening bracket gives its half em back to the
composer, as in the line loop; tests cover Auto in both directions.
Layout of 10k chars, release: justified Every-line 5.6 ms, ragged
Every-line 5.8 ms, ragged Single-line 4.8 ms.
The Color panel shows the out-of-gamut and out-of-web warnings under its
proxy only when the colour needs them, so the panel grew and shrank with
the colour. Dragging on the spectrum's pale top edge, a colour out of
gamut added a warning, the spectrum moved down under the pointer, the
pointer then picked white (above the spectrum), the warning went away,
the spectrum moved back up: the colour flipped every frame.
The panel now keeps room for the warnings its mode can show, so its
height no longer depends on the colour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only real letterspacing (|tracking| > LIGATURE_TRACKING_LIMIT = 50/1000 em)
drops standard ligatures now, and an explicit liga/clig in the character's
features always wins. The OpenType panel's hint follows the same rule.
With the Selection tool, a click (no drag, no modifier) on one object of a
selection of several makes it the key object (select.key); a click on the
key again lets it go. The key object is drawn with a thicker outline,
Align aligns to it (the Align panel shows Align to Key Object while there
is a key, and choosing Selection or Artboard lets it go), and
object.align without `to` aligns to the key when there is one, so the
Control bar's and the Properties panel's Align buttons do too. With a
spacing, Distribute Spacing keeps the key object still and spaces the
others from it, and it reports too few objects instead of indexing an
empty list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Corner widgets only ever showed on live rectangles: CornerWidgets::of bailed
out unless the selection's live shape was LiveShape::Rectangle, and
object.setLiveShape only edited rectangle radii. A star is a plain path, so it
had no widgets with either selection tool.
- geom::corners: the corners of any path (anchors without handles between two
straight sides) and the path with them cut round, inverted round or
chamfered, each corner a circle tangent to both sides, its radius no larger
than takes the cut halfway along its shorter side. Rectangles now cut their
corners with it (bit-identical anchors and layout).
- doc::corners::LiveCorners/set_corners: one model for a live rectangle's,
a live polygon's (new radii/kinds) and any other path's corners (new
LiveShape::Path keeping the uncut outline, plain again once nothing is cut);
radii scale with transforms (Scale Corners) and stay circular.
- object.setLiveShape takes anchor indices for `corners` on any path; polygons
keep a shared radius as their sides change.
- Widgets: Direct Selection on any path, Selection on live rectangles and
polygons; placed on each corner's bisector, the drag follows it, the red
limit outline marks corners at their own limit; the angle preference and
View > Hide Corner Widget apply. Corners dialog and the Properties and
Transform panels' Corner Radius (now on polygons too) work on them.
- Tests for the star, the polygon and a pen path in geom, doc, tools, engine
and ui-egui; docs and ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Control bar's underlined Transform link opens the Transform panel as a
popover under it (reference point, X/Y/W/H, Rotate and Shear with their icons
and angle presets, flips, shape properties, Scale Corners, Scale Strokes &
Effects), as the Stroke link does the Stroke panel. X/Y/W/H stay inline while
the bar has room.
The reference point locator is now nine squares with the outer eight joined
around the box; the old outline was inset off the squares' centres.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reported: Alt-click with the Pen should toggle an anchor between corner and smooth, and the
Control bar had no way to make a corner anchor smooth.
v0.5.0 already has Pen + Alt as the Anchor Point tool (#473) and the Control bar's and the
Properties panel's Convert buttons for direct-selected anchors, the path being drawn included (its
last anchor is selected). The remaining gap: with Direct Selection, the Pen, the Curvature or the
anchor tools, a path selected as a whole shows all its anchors selected, yet the Convert buttons
only appeared once anchors were direct-selected.
- chrome::anchor_controls decides what the Control bar and the Properties panel show: Convert and
Anchors for direct-selected anchors (as before), Convert alone for paths selected as a whole
with a tool that edits anchors (path.convertAnchors already converts every anchor of those);
Remove and Cut still wait for anchors direct-selected, as they act on those only.
- Tests: the Control bar and the Properties panel offer Convert for a whole rectangle with Direct
Selection and the Pen, not with the Selection tool, and smooth converts every corner; Pen + Alt
on the path being drawn makes a smooth anchor a corner (click) and a corner smooth (drag), and
drawing goes on. ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing could be undocked: the dock's tabs only switched panels, the icon
column only popped its panels out next to it, and only tool flyouts
(#437) could float.
- A dock tab, a panel icon or a popped-out panel's title dragged out of
the dock floats that panel inside the app window; the strip right of
the dock's tabs floats the whole Properties | Layers | Libraries group.
- A floating group moves by its title bar (or the strip right of its
tabs); a tab dragged out of it floats on its own; dropped on another
group's title bar or tabs it stacks with that group; dropped on the
dock, which lights up under the pointer, its panels go back where they
live in the dock (tabbed group or icon column). Its x does the same.
- The Tools panel floats by its title bar or grip and docks again on the
window's left edge, which lights up the same way.
- window.panel.float {panel, x?, y?, onto?, group?} and window.panel.dock
{panel, group?} (panel "tools" for the Tools panel) for the control
channel and MCP; every panel menu ends with Float Panel / Dock Panel;
window.panel on a floating panel shows its tab.
- Floating groups and the Tools panel's position are kept with the
preferences and in user workspaces (built-ins dock everything),
sanitized on load, and clamped into a smaller window when drawn.
- One move state in egui temp memory drives every drag, so a tab torn off
keeps following the pointer; per frame the work is a lookup per panel.
- Icon buttons can be drawn for a caller's response (paint_icon_button),
so the column's icons keep their ids when one above them floats.
- Translations (ja, es, zh-hans, zh-hant; cs and pt-br for the commands),
es @msg rows, docs/control-protocol.md and ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The Paragraph panel menu's Burasagari submenu reads None / Regular / Force, as the reference
app's help names them (`text.setFormat` keeps "none"/"standard"/"forced", its scripting names);
Reset Panel goes back to new type's Regular.
- The full-width ,.and half-width 、。 hang too; Latin commas and full stops never do, so Latin
text keeps its breaks and composer with new type's default (tested). The spaces after a hanging
mark stay on its line.
- docs/mcp.md: a Burasagari section. ROADMAP: wording, Last updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paragraph panel menu › Burasagari › None / Standard / Forced (with the East Asian options) and
`text.setFormat {burasagari}`:
- None: a comma or full stop that doesn't fit goes to the next line with the character before it.
- Standard: one that doesn't fit hangs outside the line; one that fits stays inside.
- Forced: one ending an area type line always hangs, and the rest of the line fills the measure.
Only 、 and 。 hang (closing brackets don't), in the width their mojikumi gives them. Horizontal
and vertical. Saved only when not None; new type keeps None. Paragraphs with burasagari are
composed line by line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Discord feedback: Alt with the Pen didn't move a direction handle on its own, and there was no
way to break a path at an anchor and pull the ends apart.
- Pen: Alt held over a handle end or an anchor of a selected path (the one being drawn too) hands
the gesture to the Anchor Point tool, with the convert cursor: dragging a handle moves it alone,
clicking a smooth anchor makes it a corner, dragging an anchor pulls out symmetric handles.
Drawing goes on afterwards. Handles hidden by a partial anchor selection aren't grabbed.
- path.cutAtAnchors cuts at the direct-selected anchors in one undo step: a closed path opens
there, an open one becomes one path per piece, and one anchor of each cut stays selected so a
drag pulls the path apart. It answers {ids} and refuses (no undo step) when only open ends are
selected. path.convertAnchors checks `to`, skips stale anchor refs instead of indexing past
them, and keeps a smooth anchor's handles when converting to smooth.
- SubPath::cut_at and SubPath::smooth_anchor (geom) now back path.split, path.cutAtAnchors,
path.convertAnchor and path.convertAnchors, replacing three copies of that logic.
- Control bar and Properties panel: "Convert:" corner/smooth and "Anchors:" remove, connect
(Join), cut buttons when anchors are selected; Cut Path on the task bar and the context menu.
New original icon dc-anchor-smooth.svg (ASSETS.md row).
- ja, zh-hans, zh-hant and es strings (cs for the context menu item), es @msg rows; docs/mcp.md
section; ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Discord feedback: Image Trace and the other image options were hidden when an image was
selected; copy, paste, Image Trace should work from the Control bar as in Illustrator.
- Control bar, one image selected: Linked File/Embedded, file name, colour mode and PPI, then
Embed (+ Edit Original) or Unembed…, an Image Trace split button (click: Default preset;
arrow: trace with any preset), Mask and Crop Image, in place of Fill and Stroke.
- Control bar, one Image Trace object selected: "Image Tracing", its Preset (choosing another
traces it again), the Image Trace panel and Expand.
- Properties panel: Image Trace (split), Crop Image and Mask in Quick Actions for an image; an
Image Trace section (Preset, Expand, Release) for an Image Trace object. Embed/Unembed buttons
are shared with the Control bar (place::link_buttons).
- New engine command object.maskImage ("Mask"): clips the selected image with a clipping path
on its outline (rotated with it) and selects that path so its handles crop; one undo step.
- widgets::split_button (flat button with a menu arrow).
- Translations for the new strings (es, ja, zh-hans, zh-hant; "Mask" also pt-br, cs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Discord feedback.
- Properties panel: the Appearance section's Fill and Stroke rows now use the
Control bar's widgets: the chips with their chevron and swatch/mixer
popover, the underlined Stroke link that opens the Stroke panel as a
popover (stroke::link, shared with the Control bar) and the weight spinner
with its presets (stroke::weight_field, which also follows the Type tool's
selected characters). With nothing selected the Document view shows them
too, as the Control bar does: they set up the next object drawn.
- Window menu panel keys as in Illustrator: Brushes F5, Layers F7, Align
Shift+F7, Transform Shift+F8, Info Cmd+F8, Pathfinder Cmd+Shift+F9,
Symbols Cmd+Shift+F11, Character Cmd+T, Paragraph Cmd+Alt+T, Tabs
Cmd+Shift+T, OpenType Cmd+Alt+Shift+T (beside the existing Color, Color
Guide, Appearance, Graphic Styles, Stroke, Gradient, Transparency and
Attributes keys). The dock's tabs (Properties, Layers, Libraries) join the
panel shortcut table and the Keyboard Shortcuts editor through
state::all_panels, which window.panel's name lookup and the catalog's
label list use too.
- Alt-wheel (Option on the Mac) zooms about the pointer whatever General >
Zoom with Mouse Wheel says: egui spreads a notch over a few frames, and the
rest of an Alt turn scrolled once Alt was up (also over the control
channel's ui.wheel); the turn's own modifiers now decide. Pinch zoom is
covered by the test too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Properties panel's Font Size and Leading were plain typed fields and the
Control bar had no type controls. All three places now use the shared
editable combo (`widgets::spin_field`: typing, arrow and spinner stepping,
label scrubbing, a preset dropdown in the type unit):
- Character panel: the size and leading grid is shared with the Properties
panel (`character::metrics_grid`), which now shows the font style, Font
Size, Leading, Kerning and Tracking as combos and applies them to the
Type tool's selected characters as the Character panel does.
- Control bar: with type selected or edited, a Character link (the Character
panel in a popover), the font family and style menus and the Font Size combo.
- Presets as Illustrator lists them: sizes 6-72 pt; Leading has Auto first
(`widgets::spin_field_auto`, `text.setStyle {leading: "auto"}`) and then
the size ladder instead of 120% values; Kerning and Tracking -100...200.
- Font Size, Leading, Kerning and Tracking show blank where the selected
text's values differ (`character::shared`); the spinner does nothing on a
blank field instead of jumping to the step size.
Each pick is one `text.setStyle` / `text.setRangeStyle` step, so MCP and the
control channel are unchanged. Translations for the two new strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the Artboard tool chosen, Copy and Cut take its artboard together with
the art fully inside it (when Move/Copy Artwork with Artboard is on), and
Paste brings both back in one undo step: Paste right of the last artboard,
Paste in Place / in Front / in Back where it was, into this or another
document, the art going back into the layers it came from (Paste on All
Artboards pastes the art alone). The clipboard carries the artboard
(Clipboard::artboard); a pasted artboard keeps its name when it is free.
Duplicate Artboards (the Artboards panel menu, and now a row dragged onto
New Artboard) copies the art too: artboard.duplicate {index?, art?} defaults
to the Artboard tool's artboard and moveArt option and reports the copies.
It shares copy_artboard with the Alt-drag copy (artboard.move {copy}).
New commands reachable over MCP: artboard.copy and artboard.cut
{index?, art?}; edit.copy / edit.cut run them while the Artboard tool is
chosen (and are enabled then with nothing selected). Docs (mcp.md), the
tool and panel docs, translations of the new labels and the ROADMAP follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Illustrator documents two drop targets in the Layers panel's bottom bar:
dragging items to Create New Layer duplicates them, to the Delete icon
deletes them. Dropping rows on New Sublayer or Make/Release Clipping
Mask no longer creates a sublayer or clips the row's layer; those
buttons only act on a click, and the new test checks a drop on New
Sublayer changes nothing. The trash and New Layer share one drop loop,
and the drag ghost paints its colour bar and thumbnail with the row's
own helpers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dragging a row shows a dimmed copy of it (colour bar, thumbnail, name)
under the pointer. A row dropped on Delete, New Layer, New Sublayer or
Make/Release Clipping Mask runs that button on the row's layer, as a
click would: Delete removes the row itself and New Layer duplicates it,
as in Illustrator.
Rows dragged onto the trash weren't deleted. The trash took the drop as
a target-circle payload first, and egui's take discards a payload of
another type, so the rows reached the second check as nothing. It now
peeks at the type before taking. The trash is also outlined while rows
or a target circle are over it, like the other drop buttons.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The popover kept its open flag while its anchor was hidden (panel closed, selection changed), so
it came back open the next time the row was drawn. It now remembers the last frame it was open in
and counts as closed after a frame without it, as egui's remembered popups do. ROADMAP: the
Appearance panel's Opacity popup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
egui remembers one open popup at a time, so opening the blend-mode
dropdown or the opacity slider inside the Opacity popup closed the popup,
and the dropdown or slider with it. A new widgets::popover keeps its own
open state and closes only on Escape, its anchor, or a click outside it
and the popups it opened. The Opacity popup and the Stroke popover (which
holds dropdowns too) use it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The pt ladder stays 0.25 … 10, 20, 30 … 100 pt (the dropdown's documented 20/30/40 pt steps).
- Feet, yards and meters returned the pt ladder converted as if it were in that unit (a 100 m
stroke); they now get the pt ladder in points.
- weight_presets returns a fixed array (no allocation per frame); the pica ladder is stored in
points; 0.325 in is 0.375 in (the eighths ladder).
- Fields keep three decimals in points, pixels and picas (595.276 pt) and show four in mm, cm,
inches and larger units (8.2677 in, 0.0078 in).
- ROADMAP: the Stroke panel's per-unit presets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Stroke panel weight dropdown converted one pt-based preset list into
whatever Units > Stroke was set to, which produced ugly rounded values like
0.088 mm, 0.265 mm, 10.583 mm from the pt ladder. Each unit now has its own
native ladder (observed from Illustrator's running dropdowns and typed in),
converted to pt once at use. Points, Millimeters, Centimeters, Inches,
Pixels and Picas each get Illustrator's actual values; the exotic Unit
variants (Feet & Inches, Meters, Yards, Feet) fall back to the pt ladder.
Picas round-trip to exact pt weights (1 pt, 12 pt, 60 pt …) but display as
decimal picas until Unit::number learns 0p1 / 1p3 notation; that is a
system-wide formatter change left for a separate PR.
Also bumps Unit::number precision from 3 to 4 decimals so Illustrator's
sub-eighth-inch strokes (0.0078, 0.0156, 0.0313, 0.0625 in) show their
authored values instead of rounding to 0.008 / 0.016 / 0.031 / 0.063.
Trailing zeros are still trimmed, so 1 pt stays "1".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dragging a numeric field's label left or right steps the field's value,
as Photoshop's scrubby labels do (beyond Illustrator): one unit every
2 points, ten with Shift, a tenth with Ctrl/Cmd (counts stay whole), in
the field's unit and precision. The panels apply each value as they
apply a typed one, so the art follows live and the caller's limits
hold (dragging back from a limit changes the value at once).
- scrub.rs: the label zone over the label noted just before a field
(dim_label, label_row, widgets::field_label, the Character and
Paragraph panels' icons, the Transform and Properties rotate/shear
icons), the ↔ cursor on hover, a click on the label focuses the
field. The text box itself never scrubs; a field without a label
doesn't. num_field, plain_field, mixed_field (and the spin fields)
and opt_field take part.
- Engine: undo groups (Session::begin_undo_group / end_undo_group). The
edits made in a group, commands and committed live drags alike, are
one undo step, journaled one by one; a cancelled group is undone and
dropped from the journal. The app opens one while a label is dragged,
so a scrub is one undo step whichever path the panel applies values
by (app.run, session.execute, live_run), and Escape cancels it
(shortcuts leave Escape to the drag, so it doesn't close a dialog or
deselect).
- Preferences > General > Scrub Numeric Fields by Dragging
(`scrubNumericFields`, on by default), translated in ja, es, zh-hans,
zh-hant and pt-br.
- Tests: label drags with Shift/Ctrl, limits, counts and units, Escape,
the preference, the cursor, click-to-focus, no scrub on the box or
without a label, and in the app one undo step for a W drag.
- ROADMAP and docs/mcp.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both switch from widgets::font_dropdown to font_menu::font_menu, so they
get the same samples, favourites and filters as the Character and
Properties panels. They only pick a font: the new font_menu::picked
returns the choice and toggles stars, and never previews on the document.
Follow-up to #339 as suggested in #364.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRE8ZD3p8QgYctENhnJDz3
text.setRangeStyle takes charAlign, so the panel menu works on the
characters selected with the Type tool (it routes there while text is
edited, and charAlign alone was answered with "nothing to change").
One char_align_param parses it for both commands. The menu shows with
Preferences › Type › Show East Asian Options, like Mojikumi Set and
Top-to-Top Leading. Rows in the Simplified Chinese, Spanish and
Brazilian Portuguese catalogs; the Japanese Em Box Center names the em
box. ROADMAP: the gating, the range command, and ICF top/bottom still
missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Characters of different sizes on one line all sat on the Roman baseline.
Japanese layout usually lines them up by their ideographic em boxes
instead (a 16 pt 第一回 next to 32 pt 雅楽 centred on it, not sitting on
its baseline). The Character panel menu now has Character Alignment:
- `CharStyle.char_align`: Roman Baseline (as before, the default), or the
em box Top/Right, Center or Bottom/Left: a character smaller than the
line's largest moves so that its em box top, centre or bottom lines up
with the largest's (the em box from the font's vertical metrics, else
0.12 em below the baseline to 0.88 em above it). Vertical type the
same across the column (top → right). Written only when not Roman
Baseline.
- `text.setFormat {charAlign: "romanBaseline"|"emBoxTop"|"emBoxCenter"|
"emBoxBottom"}`; the menu shows the selection's setting; catalog rows
in ja and zh-hant.
- Not yet: aligning on the ICF (average character face) top and bottom.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Select All (Cmd/Ctrl+A) now follows the keyboard, as in Illustrator:
- macOS menu bar: the system menu takes Cmd+A/X/C/V before the window
sees them, so a focused panel, dialog or search field got the document
command. `menus::invoke_from_system_menu` (used by `NativeMenu::poll`)
hands Select All, Cut, Copy and Paste to a focused text field as the
events its keys would send; clicks on those menu items follow the same
rule. Other commands, and these with no field focused, run as before.
- Type tool: `select.all` itself selects all of the text being edited
while the Type tool edits (returns {editing, start, end}), so the menu,
the shortcut, the control channel and headless MCP (`press_key`) agree.
The UI's own Select All interception and the Type tool's hard-wired
Cmd+A are gone; Cmd+A reaches it through the Select All shortcut.
- Windows, Linux and the web already left Cmd+A to a focused field; now
covered by headless app-frame tests (field, Type tool, canvas).
Docs: docs/mcp.md (Select All while editing type), ROADMAP. Not yet:
Select All across threaded frames (one frame's text is selected).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the browser dropped the page's WebGPU device or WebGL context, the
editor kept running but nothing it drew reached the screen: Chrome and
WebKit showed a frozen, stale canvas and Firefox a blank page, while
edits and Save went on.
- ui-egui: new `graphics` module. `GraphicsLoss` carries a loss report
from any thread (wgpu's device-lost callback, the canvas's
`webglcontextlost` event) to the UI thread. `VectorcraftApp::
graphics_lost` writes Data Recovery copies of the modified documents at
once. The app now notices when the host hands it a new egui context and
uploads everything again (fonts and theme, the canvas, place cursor
thumbnails); every per-thread texture cache (layer, symbol, swatch,
style, asset and glyph thumbnails, previews, font samples, Navigator,
Flattener Preview) is a `TexCache` that empties for a new context.
Without this, the moved app drew stale or wrong textures and egui
panicked on its missing font families.
- web: the shell watches both loss signals; on a loss it puts a fresh
canvas in place of the dead one and starts the same `WebRunner` again,
handing over the same app (documents, undo, panels), with services made
for the new context and a status-bar note. If no new graphics can be had
(or after three losses) it removes the stale canvas, lets go of the
recovery copies and says to reload.
- engine: `recovery::leave` lets go of a session's area but keeps its
copies, so the reloaded page offers them at once instead of after the
heartbeat goes stale.
- desktop: eframe can't give a window a new device, so a lost device
(driver reset) is logged, the copies are written and the status bar says
to save and restart; closing then skips the Save Changes question it
can't show.
- Tests: loss reporting across threads, `TexCache` emptying, the app
uploading the canvas, a cached preview, theme and fonts again in a new
context (fails without the fix), copies written on loss, and
`recovery::leave` offering copies at once with both lock and heartbeat
stores. docs/development.md (web section) and ROADMAP updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A live rectangle stored four corner radii but drew them all with the largest,
and the corner widgets always rounded all four corners, so one corner could not
be rounded on its own.
- geom: rectangle_with_corners draws each corner with its own radius and kind
(Round, Inverted Round, Chamfer); a sharp corner is one anchor, a cut one two.
Uniform corners keep the exact rounded-rectangle anchors.
- doc: LiveShape::Rectangle gains `kinds` (saved only when a corner isn't round;
older files read as round), plus helpers mapping anchors to corners.
- engine: object.setLiveShape takes `corners` and `kind`; without `corners` it
edits the Direct-Selected corners, else all four, and keeps the selected
corners selected as their anchors change.
- tools: with Direct Selection, only the selected corners show widgets and a
drag rounds just those; Alt-click cycles the corner kind; double-click opens
the new Corners dialog (also `ui.corners`).
- ui: Properties/Transform radius fields show and set the selected corners;
CSS export keeps per-corner radii and skips cut-in corners.
- docs: mcp.md (Live Corners), control-protocol.md (Corners dialog), ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The em box top that Top-to-Top Leading asks of every glyph's line metrics parsed the font's
cmap and vertical metrics per glyph on every layout: each face now reads it once.
- The Paragraph panel menu shows Top-to-Top / Bottom-to-Bottom Leading with the East Asian
options (Preferences › Type), as the Mojikumi Set does, and Reset Panel goes back to
Bottom-to-Bottom Leading, the default.
- Saved as `leading_model`, like the other paragraph attributes in files.
- Simplified Chinese (landed since) translates the two menu items.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japanese layout measures leading from the top of one line's ideographic
em box to the next (the right side, in vertical type), not from baseline
to baseline. The Paragraph panel menu now offers both:
- `ParaStyle.leading_model`: RomanBaseline (as before, the default) or
EmBoxTop. With EmBoxTop a line's leading is the space below it — the
next line's em box top is this line's plus its leading, and the next
baseline hangs from there by that line's tallest em box — and area
type's first line touches the top of the frame (its em box top on the
frame's top, at least Area Type Options' minimum). With baseline
leading a line's leading stays the space above it.
- Lines of one size are exactly as far apart either way; the models
differ for mixed sizes and for the first line of area type (fonts
whose ascent isn't the em box top: Hiragino's nearly is).
- The em box top comes from the font's vertical metrics (#343), else
0.88 em. Written only when EmBoxTop.
- `text.setFormat {leadingModel: "romanBaseline"|"emBoxTop"}`; menu items
Top-to-Top Leading / Bottom-to-Bottom Leading (checked as set); catalog
rows in ja and zh-hant.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Illustrator sets a paragraph's direction (Left-to-Right / Right-to-Left
Paragraph Direction in the Paragraph panel, with the Middle Eastern
options) and keeps its alignment buttons physical; it has no automatic
alignment toggle. So:
- ParaStyle.direction (None: from each paragraph's first strong
character, as before; written only when set), the Paragraph panel's two
direction buttons with Show Indic Options (original icons), and
text.setFormat {direction: "auto"|"leftToRight"|"rightToLeft"}. The
layout uses it as the base direction.
- The Automatic alignment check box is gone. Justify::Auto stays as the
start of the paragraph's direction, set on new type only (text.create,
the Type tools, with the mojikumi default in one new_type_para()):
TextObject::point is Left again, so importers (PDF, SVG, DXF, EMF) keep
their own alignment. The alignment buttons show what Auto resolves to.
- Bidi only where there is something right to left: plain paragraphs skip
the algorithm (no per-layout allocation), it runs once per paragraph
(shaping takes the paragraph's levels instead of recomputing them; path
type computed it four times), and the shaper keeps Han, Hiragana,
Katakana and Bopomofo in one segment (splitting at every change of
script cost Japanese text a shaper call per kana-kanji switch).
- Vertical type keeps its logical order, so its glyphs and lines are not
marked right to left (the caret no longer runs backwards down a column).
- PDF import puts Hebrew and Arabic, which PDFs (and VectorCraft's own
export) draw in visual order, back in logical order with the direction
that shows it as drawn; with bidi layout it came back mirrored.
- SVG import pins the direction SVG implies (left to right unless
direction: rtl); SVG export also outlines right-to-left paragraphs.
- Docs (development.md), ROADMAP (folded into the Type entry, Type core
row) and README updated; catalog rows for the new labels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Unicode bidi (unicode-bidi, unicode-script, unicode-general-category):
each paragraph's direction from its first strong character, Hebrew and
Arabic shaped right to left, embedded Latin and numbers keep theirs,
lines wrapped in logical order and reordered per line by whole
clusters; source text, undo and style ranges stay logical.
- Caret, hit testing, selection and the arrow keys follow the visual
order.
- New text aligns automatically: right-to-left paragraphs right, others
left (Justify::Auto, text.setStyle {justify: "auto"}); the Paragraph
panel highlights the resolved alignment and has an Automatic alignment
toggle. Explicit alignments, and older documents', are kept.
- SVG export outlines bidirectional text (with a warning) until it can
be written as directional live text.
The Paragraph panel's mojikumi control is one of the panel's options
(hidden with Hide Options) and appears when Preferences > Type > Show
East Asian Options is on, as Illustrator shows its Mojikumi and Kinsoku
sets; it is labelled Mojikumi Set. ROADMAP notes where it lives.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add Snapping to the Artboard Tool
Merged with the Artboard tool's Alt-drag duplicate (#348): a copy snaps too, and to the artboard
and art it leaves behind (a move skips them).
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.
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>
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>