Area Type Options > Align: Top / Center / Bottom / Justify (AreaOptions.verticalAlign,
LayoutOptions.vertical_align). Each row/column cell is aligned on its own; rectangular
cells shift their lines, other frames and text wrap reflow until settled without
losing text. Justify shares the leftover space equally between line gaps. Area Type Options
shows Align and First Baseline as dropdowns (the parameter dialog's choices table).
Font services load their fonts with GDI at run time, from files in their
own folders, without installing them: neither the font folders nor the
registry list them, so the font scan never saw them. DirectWrite's system
font collection holds them since Windows 10 1607.
- vectorcraft-text: `set_platform_font_files` lets the app name font files
the platform knows; each scan and each check for installed fonts asks
again and reads those outside the font folders. A file named by itself
is read whatever its extension (font services keep fonts in files
without one).
- The desktop app lists the local files of DirectWrite's system font
collection on Windows (`system_fonts.rs`, through the windows crate
already in the build), read in place.
- The check for fonts installed while the app was in the background runs
on a thread: DirectWrite's check for updates takes about 50 ms.
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>
The Gradient panel's colour chip opens the stop popover on a click. The
popover closes on a click elsewhere, and the opening click itself, which
is released in the frame the popover first draws and lies outside it,
counted as one: the popover closed at once (a double-click got through,
as the popover lets the double-click that opens it from the canvas pass).
The popover now records the frame it first drew in and only a click in a
later frame closes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add ru.tsv with the full interface coverage: every menu string, every
tl! literal, the plurals (one/few/many) and the status/error messages
(@msg). Registered as complete_menus with the Russian plural rule;
every ru-* locale resolves to it. Keeps the product, workspace and
perspective preset names in English like the other complete catalogs.
Tests: russian_reads_as_russian, russian_plurals_have_three_forms,
russian_glyphs_are_available_without_system_fonts; ru added to
KEEPS_MENU_NAMES and COMPLETE_MESSAGES.
Clean-room: written from the meaning of the English labels in the
vocabulary Russian-speaking users of vector illustration software know
(Слой, Образцы, Обработка контуров, Монтажная область, Обводка,
Заливка), matching the Russian catalog of the sibling app PhotoCraft.
No other application's localisation was copied.
file.saveAs and document.save wrote a PDF-compatible .ai, but
document.export (and with it the CLI's run --export x.ai and convert
in.svg out.ai, and MCP's export) refused it as a format that can only be
opened, and --help didn't list it as writable.
The .ai encoder is now shared: save.rs's ai_params and encode_ai (a PDF
of every artboard with the PDF options, carrying the native document,
blank pages with pdfCompatible: false) serve Save As and the exports
alike. The .ai format is writable, so document.formats, --help and the
MCP export tool list it; an export leaves the document's own path alone.
Export As and Export for Screens keep leaving .ai to Save As.
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>
egui's combo box puts its own scroll area around a combo's list, so the
font menu, which scrolls its families itself, had two scrollbars: once
the families reached their end the wheel scrolled the outer one, taking
the search field and the filters out of view.
The font menu (widgets::combo with searchable) now opens its list in a
popup of its own, drawn like the combo box, without that outer scroll
area: below the button, or above it when there is more room there. The
families list fills the popup's room under the search field and filters,
which stay at the top while the list scrolls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exporting one artboard of many to SVG wrote the whole document's art into
that artboard's file, the other artboards' art outside its viewBox: an
editor opening it showed every artboard's art. A file for an artboard
named in the export (Use Artboards with a range or All, Export for
Screens) now holds the objects over that artboard only, at any depth of
layers, as DXF's crop leaves them; objects reaching into it stay whole
and a clipping layer keeps all of its objects. An SVG with no artboard
named (Save As) still holds the whole document.
Part of #546: Export As with a range writes the chosen artboard alone in
SVG, DXF, PDF and PNG, each file with that artboard's art (tested from
the dialogs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tracing a 2048 x 2048 photo with High Fidelity Photo made 733,000 paths
and 7.8 million anchors. The selected result's outlines and anchor
squares then made an egui mesh of 1.8 GB, over the GPU's largest buffer:
wgpu panicked while painting and the app quit (or ran out of memory
first, at 6.4 GB).
- Canvas: a selection with more than 100,000 anchors shows its bounds
instead of every outline and anchor, and the hover highlight skips
such an object. The Contextual Task Bar and that outline read the
selection's bounds from a per-revision cache (VectorcraftApp::
selection_bounds): a selected trace drew a frame in 20 ms, not 170.
- Image Trace: the colour layers are traced in parallel (26 s to 1.9 s
for that photo; the web traces them in turn). trace_within stops once
the paths pass an anchor budget; imageTrace.make allows two million
and otherwise reports the trace as too complex, with what to change.
Images over 64 megapixels are refused from their header, before they
are decoded.
- The trace crate's messages are translated (es, it) and scanned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Make… and Make and Expand… opened the generic parameter form, whose
preset was a text box holding a preset's name (Black and White Logo,
6 Colors), so the other presets looked missing. As in Illustrator, Make
and Make and Expand now trace at once with the Default preset, as the
Control bar's Image Trace button does; the button's arrow and the Image
Trace panel's Preset list choose among the 12 presets. The catalogs drop
the two "…" labels; Czech gets Make and Expand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The clipboard keys already went to a focused text field, but on Windows,
Linux and the web the press that opens a menu takes the keyboard from the
field, so Edit > Copy (and Cut, Paste, Select > All) acted on the art.
The menu bar now keeps the field that had the keyboard, and its
selection, while the menus are open; those four commands go to it as the
events their keys send, the frame after the click that chose them, and
give it the keyboard back. Everything else, and those commands with no
field, run as before. The macOS menu bar shares the same helpers.
Tests: Copy, Cut and Paste events in the W field and the Color panel's
hex field edit the text, never the art; Select > All, Edit > Copy and
Edit > Paste clicked in the in-window menus act on the field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The drawing language is process-wide, so a Japanese frame translated the
tests running beside it (Appearance, Attributes, menus). The test now
checks that each frame hands the engine its language without leaving
English.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rust 1.98's clippy adds `chunks_exact_to_as_chunks`, which `-D warnings`
turns into an error, so `cargo xtask ci` fails on main with the current
stable toolchain. The EPS stencil-mask loop and the cursor rasterizer
(and two cursor tests) now use `as_chunks`, which yields fixed-size
arrays; like `chunks_exact`, it skips an incomplete last chunk, so
nothing changes in behaviour.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On macOS the menus are in the system menu bar, laid out as a Mac app's (Illustrator on the Mac as
the reference), and the app settings sit in the VectorCraft menu: About, Settings ▸ (one item per
Preferences page, General… ⌘K), Language ▸, Appearance ▸ (UI brightness), Services, Hide
VectorCraft ⌃⌘H (⌘H stays View › Hide Edges), Hide Others ⌥⌘H, Show All and Quit ⌘Q (the app's own
quit, so unsaved documents are asked about); they leave Edit and Help. Window gets Minimize ⌃⌘M,
Zoom and Bring All to Front; Help is the system's Help menu. Windows, Linux and the web keep the
in-window menu bar unchanged; --in-window-menus or VECTORCRAFT_IN_WINDOW_MENUS keeps it on macOS
too, and so does a menu bar AppKit refuses to build.
One menu model: the in-window menus and the native bar draw the same rows (menus::entry), and the
platform-free native_menu module lays them out, decides when to update (a cheap state hash; labels,
enabled and checked in place, a rebuild only when the structure changes) and hands key equivalents
back to egui as the key presses they were, so shortcuts, user overrides and focused text fields
behave as in the window and nothing fires twice. The muda backend (pinned =0.21.1, macOS only, no
unsafe) is apps/vectorcraft/src/mac_menu.rs, mirroring PhotoCraft's. ui.inspect reports
nativeMenuBar. Translations for the new labels in ja, es, zh-hans, zh-hant, cs and pt-br.
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>
- Italian rows for the strings main gained after the PR's base: the
floating panels of #513 (Float Panel, Dock Panel, the drag hints, two
window.float messages) and the GPU fallback status message of #519, so
the completeness tests pass on current main.
- File > Document Setup... reads "Imposta documento..." like the dialog
title and the Properties button, instead of "Impostazione documento...".
- The Properties panel's quick action "Arrange: Bring to Front" reads
"Porta in primo piano": "Disponi: Porta in primo piano" was clipped at
both ends of its fixed-width button.
- OpenType figure style "Proportional Oldstyle" reads "Proporzionali stile
antico", matching "Tabulari stile antico".
- Blank line between the Italian test and the next item; ROADMAP's
"Last updated" lists the Italian interface (#514).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds an Italian catalog (crates/ui-egui/src/i18n/it.tsv, 3,549 rows in
es.tsv's order) covering every menu string, every tl! literal, the
plurals and every status/error message, at the same scope as Spanish.
Standard Italian in sentence case, written clean-room from the meaning
of the English labels; the product, workspace, perspective preset and
language names stay in English as in es and ja.
Registered as "Italiano" with the one/other plural rule and
complete_menus; every it-* locale resolves to it. Tests: it joins
KEEPS_MENU_NAMES and COMPLETE_MESSAGES, italian_reads_as_italian checks
vocabulary and plurals, tags_map_to_languages the locale tags, and the
bundled-font glyph test covers Italian accents and guillemets. README,
ROADMAP and docs/development.md list the language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011iayep93edeimiAE8oSfaT
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>
- The worker reads the clipboard only while nothing is copied in the app
and a document is open (as the in-line check did before), and idles on
a channel otherwise; it sends a verdict and wakes the UI only when the
answer changes.
- The in-line check (Windows, macOS, web) keeps its short-circuit: it
doesn't open the system clipboard after an in-app copy or without a
document, as before the PR.
- A dead worker hands the check back to the UI thread instead of
freezing the last verdict.
- Dropping the app wakes the worker and waits (250 ms at most) until it
has let go of its clipboard handle: on X11 the last arboard handle
alive hands what the app copied to the clipboard manager, and a
detached worker holding it would lose that handover at exit.
- The factory stays in `Services` until the first frame takes it (no
extra app fields); main.rs installs it with `cfg!(target_os = "linux")`.
- Tests trimmed from five to three that share tests_sysclip's helpers:
the frame never waits on a slow clipboard and only the probe thread
reads it (nothing read without a document), the probe rests after an
in-app copy and lets go of its handle when the app drops, and a dead
probe falls back to the in-line check. The in-line case is already
covered by tests_clipboard.
- ROADMAP (Last updated, Platforms, Shipped so far) and
docs/development.md (Linux: clipboard owners that never answer).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`has()` on an X11 clipboard owner that never answers can block for
seconds; the app called it every 0.25 s on the UI thread to decide
whether Paste is enabled, so a stuck owner froze the frame loop (the
window stopped drawing and could not be closed).
`clipboard_probe` owns a second clipboard handle on a worker thread,
publishes its verdict over a channel and wakes the UI only when it
changes; the frame reads the latest verdict without ever blocking.
Without a factory the check stays in line, throttled, exactly as before
(the web shows SVG text only; hosts that don't install one keep the old
path).
The factory is installed on Linux only for now (the environment the bug
reproduces in); other platforms keep the in-line check, so this cannot
change their behaviour.
Tests: a fake clipboard whose `has()` blocks on a gate, a probe factory
sharing a recorder, and a headless frame whose elapsed time is measured.
They assert the frame does not wait on the clipboard and that the check
runs on the probe thread, that the probe's verdict reaches `system_paste`
and the Paste menu, that dropping the app stops the probe, and that the
in-line check (no factory: the web, older hosts) still works.
As decided in #432 (option 2): while the interface is in Japanese, new type starts with em box
top-to-top leading and em box centre character alignment; other languages keep the Roman
baseline, so Latin area type doesn't move.
- The UI tells the engine the language it is drawn in each frame (`Session::ui_language`); without
a UI, an explicit `interfaceLanguage` preference counts (`Session::japanese_interface`).
- `text.create` and `text.createInPath` (the Type tools' clicks and drags) apply it, and take
`leadingModel` / `charAlign` to choose; the values used are journaled, so a replay does the same
in any language.
- `paraStyle.new` / `charStyle.new` made from nothing (no `attrs`, no text selected) carry them.
- Imported text (PDF, SVG, DXF…) keeps the Roman baseline. Saved documents keep what they say.
- docs/mcp.md (New type in a Japanese interface) and the ROADMAP note it.
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>
Reported: with the Pen there was no way to edit curves in place as Ctrl does in Illustrator on
Windows (Cmd on the Mac), segments couldn't be grabbed, and the curve-editing tools felt slow and
sometimes didn't take a handle.
- Cmd/Ctrl held at a press with any tool but a selection tool now borrows the selection tool in the
engine (Session::pointer), not in the canvas: the selection tool chosen last, or Direct Selection
for the Pen, Curvature and anchor tools until one is chosen. The tool lent to isn't deactivated,
so the Pen goes on drawing its path after the release while that path stays selected; it no
longer draws on another open path selected meanwhile. Agents reach it with pointer_gesture
mods.cmd. With Cmd held the pointer is the borrowed tool's. Choosing a tool or switching
documents mid-gesture gives the tool back first.
- Pen + Alt over a segment of a selected path reshapes it, as the Anchor Point tool does.
- The handles shown are the handles that drag (one helper for Direct Selection, the Anchor Point
tool and Pen + Alt): Direct Selection now drags the handles of a path selected as a whole (as
the Pen leaves it, so Cmd + drag of a handle while drawing works), the Anchor Point tool no
longer grabs handles that Show handles when multiple anchors are selected hides, the nearest
handle wins, and a handle shorter than the tolerance leaves its anchor to be picked. The anchor
tools pick within Selection & Anchor Display > Tolerance instead of a fixed 4 px.
- path.reshapeSegment keeps a smooth anchor at either end smooth (its other handle turns with the
moved one) instead of kinking it.
- A dragged handle gathered its smart guide targets from the whole document on every pointer move
(21 ms per move on 20,000 paths): HandleSnap now gathers them once per drag (1 ms).
vectorcraft-cli perf measures handle and segment drags on a 5,000-anchor path.
- catalog::is_selection_tool and catalog::edits_anchors replace the UI's own lists.
- Tests in tools, engine and MCP (headless pointer_gesture); docs/mcp.md, control-protocol.md,
ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>