As in Illustrator, the Pen shows its continue cursor over an end of any
open path, selected or not, and a click there selects the path and draws
on from that end (reversing it from its first end). While drawing, it
shows a join cursor over an end of another open path, and a click joins
the two into one path, in one undo step, which finishes the path.
path.join takes {ids: [a, b], ends: ["first"|"last", "first"|"last"]}
to join the ends asked for (the Pen's join); without them it joins the
selected paths' nearest ends as before. Joining a path with itself, or
anything that isn't a path, is an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Libraries panel was a placeholder: it invited dragging art onto it,
but nothing happened. It now keeps local libraries (no account, no
cloud), as Illustrator's Libraries panel does:
- drag art off the canvas (or a swatch) onto the panel to add it, or use
+ to add the selection's graphic, fill or stroke colour, character
style or paragraph style;
- drag a graphic onto the canvas (or double-click it) to place a copy,
click a colour to paint the selection through the active Fill/Stroke
proxy, click a style to apply it to the selected type;
- the library picker and the panel menu create, rename and delete
libraries; each is a .vclibrary file in a Libraries folder next to the
preferences (session-only in the web app, headless sessions and runs
without preferences).
Engine: library.list/get/setCurrent/create/rename/delete/add/use/
removeItem. A graphic is stored as a native document with a thumbnail:
adding copies as Copy does and placing pastes as Paste does, resources
and swatch-name conflicts included. Using a style adds it to the
document and applies it in one undo step.
Clipboard::to_document gave its layer the id u64::MAX, which made a
saved copy overflow Document::fix_next_id when read back; the layer now
takes an id above the objects', and fix_next_id saturates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PDF writer defaulted to Convert Text to Outlines, so every glyph of
every page became a path: a text-heavy PDF grew several times larger on
export (a 7-page document went from 27 KB to 226 KB) and its text could
no longer be selected or searched. Illustrator's PDF presets embed font
subsets and keep live text; that is now the default too
(advanced.outlineText: false). Outlining stays available as an option,
fonts that can't be embedded still become outlines with a warning, and
printer's marks (page information) are drawn as shapes as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`artboard.setProps {scaleArt: true}` maps the art fully inside a resized
artboard (as Move Artwork with Artboard gathers it) and its artboard guides
from the old rectangle onto the new one, each side by its own ratio, in one
undo step, and returns the ids scaled. Strokes, effects and corners follow
Scale Strokes & Effects and Scale Corners (`strokes`, `corners`), pattern tiles
Transform Pattern Tiles (`patterns`); locked and hidden art follows
`lockedAndHidden` (default: prefs moveLockedWithArtboard). The resolved choices
are journaled, so a replay scales the same objects the same way. A move that
keeps the size scales nothing.
The Artboard tool's `scaleArt` option (saved with `moveArt`) makes handle
drags proportional, as the public help describes, keeps them proportional at
the 1 pt minimum, and leaves the scaled art out of the Smart Guide targets.
Move and Scale Artwork with Artboard are check boxes in the Control bar and
Properties' Edit Artboards; the artboard size fields there and in the
Transform panel pass the option. Translated in the eight catalogs that have
Move Artwork with Artboard. docs/mcp.md and the ROADMAP updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Such a file's PDF part is a placeholder page, which was refused. When
the file carries editing data, it now opens from that alone: its
structure as a PDF-compatible file's, its point type made from the text
document, and type it can't make (area type, type on a path) left out
with a note that marks the import as lossy. Without editing data, or
with pages picked or layers off, the placeholder is refused as before;
damaged editing data says so.
The page-order note is no longer given when the page has no type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The layers reader of the editing copy (operators translated into
PostScript for the interpreter, any file with RGB colours, gradients,
transparency or images falling back to its page) is replaced by a reader
of the whole structure (import/ai): layers and sublayers with their
options, groups, compound paths, clipping groups and layer clipping,
names, hidden and locked objects, RGB, CMYK, grey, named and spot
colours, gradients, transparency, fill rule, overprint, embedded images
with their alpha channel, guides and every artboard, and objects drawn
as their look when they have several fills or strokes, effects or a
brush. Containers: Zstandard, zlib or plain data in EPS and .ai.
The text placement, the comparison with the page and the overwrite guard
stay around it. The page's type takes its text object's place without a
group; the comparison is by lightness, on nothing, without layers that
don't print. Symbols, pattern fills, placed files and unknown operators
make the file come in as its page where they show, and are left out of
layers that don't.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paragraph panel › Kinsoku Set (with the East Asian options) and `text.setFormat {kinsoku}`, as
decided in #633:
- Hard: today's set, so existing documents don't change; the default, and not saved.
- Soft: as Hard, except that 々, the prolonged sound mark ー (JLREQ cl-10) and small kana (cl-11)
may start a line: JLREQ's level 3 line-breaking rules (Appendix C.3). Half-width forms aren't in
those classes and stay off the line start.
- None: no kinsoku.
The setting reaches every break the composers consider (greedy, every-line and hyphenation
points). Reset Panel returns to Hard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Laser and cutter software skips HATCH entities, so shapes that were only
filled (type, traced art) reached it as nothing. Each filled area is now
written as its outlines (polylines and splines, in the fill's colour)
followed by the solid hatch; R12 keeps writing the outlines alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel laid out a row (with its thumbnail and several whole-document
lookups) for every open row each frame. A layer of 5,000 groups of 10 paths,
all open, took about 29 s a frame headless. Now the rows shown are listed once
per document change (with their layer colour and dimming worked out on the
way down) and ScrollArea::show_rows lays out only those in view: 0.4 ms a
frame. A row highlighted out of view is scrolled to.
The open rows move from UI state into the document's state
(DocState::layers_open, OpenRows): a document opens with only its top-level
layers open, and native files save the open rows (Document::layers_open,
written only when not the default, at save time like the view, so opening or
closing rows never marks the document modified).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Character panel menu › Character Alignment gains ICF Top/Right and ICF Bottom/Left, and
`charAlign` takes `icfTop` / `icfBottom`: a character smaller than the largest on its line lines
up its ideographic character face's top (right, vertical) or bottom (left) with the big one's.
`FontFace::icf_margins` places the ICF inside the em box as the OpenType baseline tags define it
(https://learn.microsoft.com/en-us/typography/opentype/spec/baselinetags): `icfb` / `icft` of the
BASE table's horizontal and vertical axes against `ideo` / `idtp` (else the OS/2 typographic
descender and one em), for Han ideographs (else kana, else the first script). Without them, as that
page allows, the average ink box of some ideographs and kana; a face without ideographs uses its
em box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document symbols placed inline in a run of type (beyond Illustrator; like
InDesign inline anchored objects): TextRun.inline holds the symbol, scale and
shift; the run is one U+FFFC laid out as a glyph sized to the art, centred on
the cap height. text.insertInline and Type > Insert Inline Symbol; editing
keeps the graphic whole; canvas, SVG (<use>) and PDF draw the art; Create
Outlines turns it into a symbol instance.
The DirectWrite font lister (#594) was installed in the desktop app only:
`vectorcraft-cli` (convert, run, MCP) didn't see fonts that font services
such as Adobe Fonts load on Windows, so its exports set such type in a
fallback font.
- The CLI shares the app's `system_fonts.rs` and installs it first thing
on Windows (not the win7 build). The text crate stays free of unsafe
code; the CLI denies unsafe code instead of forbidding it, so the
shared module's own allowance applies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File › Open, Other Library… and the User Defined library folder read
.ase files. Colors keep their model, global and spot colors stay global
and spot, and color groups stay groups. The library readers take a
file's bytes. On the web, File › Open passes a swatch library file to
swatch.library.load as dataBase64 instead of as lossy UTF-8 text.
Swatch and group names in a library are now unique, in .gpl files too.
Add to Swatches puts each color group of a library into one document
group when the document already has a swatch with the group's name.
The library fuzz helpers fail when a command panics.
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.
Area Type Options gain a fit: none, autoHeight (Illustrator's Auto Size:
the frame's height follows the text after every edit, resolved in
refresh_bounds inside the edit so it shares the edit's undo step) and
shrinkText {minPercent} (beyond Illustrator: overflowing text scales its
sizes, leading and baseline shifts down at layout time by the largest
factor, in 0.1 pt steps of the first run, that fits). The Auto Size New
Area Type preference now applies. text.areaOptions and document.inspect
report overflow and the shrink factor; the Area Type Options dialog
offers fit and first baseline as dropdowns.
A `.ai` file's PDF part is what apps that don't read VectorCraft's native
document open. It had no PDF layers: hidden layers and sublayers were left
out of it and everything else was flat page content.
- `.ai` files (Save As, exports, the CLI) write PDF layers unless
`createLayers` is false (a PDF standard still decides for itself).
- createLayers writes sublayers too, as optional content groups listed
under their layer's in `/Order`, hidden ones off with their art.
- A layer's form gets a box round the whole page instead of its art's
bounds, which readers made a clipping group of, gathering a layer's art
and its sublayers' in one group.
- PDF import: art inside a group, clip or form that dissolves into its
parent keeps the layer it was drawn in, so sublayers nested in their
layer's content import as sublayers.
- Save PDF: "Create PDF layers from layers and sublayers" (ja, es, it,
pt-br, ru, zh-hans, zh-hant).
Hidden objects (not layers) still aren't in the PDF part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
An SVG used as an animation rig lost its hidden alternates and their
rotation pivots: the exporter left hidden objects out (only hidden layers
could be kept), and the importer dropped data-* attributes.
- SVG export: hiddenLayers (on by default for document.save, as before)
now keeps hidden objects too, each written display="none" on its own
element (path, image, text or group). Exports still leave them out
unless asked; the CLI's convert asks, as a conversion keeps the
document.
- An object's own data: ObjectAttributes.data, the data-* attributes in
order (ours, data-name and data-vc-*, aside). The importer keeps them on
the objects and layers they belong to (at most 64 per element), the
exporter writes them back, the native format saves them (missing in
older files), and object.setProps {data: {key: value|null}} sets or
removes them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
Every drawing tool now asks one snapper (guides::DrawSnap) for the points it
places: the Pen's and Curvature tool's anchors, the Pencil's ends, the shape
tools' start and dragged corner or radius (Rectangle, Rounded Rectangle,
Ellipse, Polygon, Star, Line Segment, Arc, Spiral, the grids), the Type tool's
click and frame and a new artboard's corners. Points land on other objects'
anchors, centres and paths ("anchor", "center", "path") or line up with their
anchors, edges and centres ("align"); hovering shows it before the press, the
Pen's guides end its rubber band, and the Pen lands on the path being drawn
too. Shift keeps a point to its 45-degree step (from Constrain Angle) or a
box's diagonal and slides it along that way into line with a target.
Preferences > Smart Guides > Construction Guides and Angles now act: a drawn
point near a guide through the point it leaves from lands on it.
Before, the Pen snapped only its press and showed nothing, and the shape tools
re-gathered targets on every drag step from the document showing their own
preview, so the dragged corner stuck to where the last step left it.
Performance: targets are gathered once per document state (keyed on the
document's uid and revision, now in ToolContext), from the art in the window,
and sorted, so the anchors, centres and lines near the pointer are found by
bisection. Leaving objects out of the targets no longer looks up each node's
ancestry (quadratic in the document's size): one pruned walk does it, which
also speeds up the Selection tool's moves and resizes.
The direction handles' HandleSnap (#494) is now a DrawSnap holding its own
targets (Targets::for_handle) for the drag: one cache, one lookup.
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>
- Type left unfinished when a layer's marked content ends, and a clip that closes in a later layer,
stay in their own layer (no stray "Page N" layer, no art moved to the next layer).
- Top-level layers stack as the pages mark them, so a layer whose art starts on a later page keeps its
place; layers marked with no art come in empty.
- A stroke written as each glyph's outline stroked, right after (or before) the type, becomes the type
object's stroke above (or below) its characters: the type stays one editable object.
- Spaces read from gaps wider than a space get their own tracking where even tracking would put the
glyphs after them more than a tenth of an em off (barcode digit groups); lines are measured to their
last visible glyph, so a wrapped line's trailing space no longer spreads into the tracking.
- Lines wrapped in a frame (each wrapped line ending in a space, one left edge, font, size and leading,
with an optional paragraph spacing) come back as one area type object, left aligned or justified,
only where laying it out breaks the lines exactly where the file does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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: 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>
Preferences › User Interface › Auto-Collapse Iconic Panels was stored
and never read: a panel popped out of the icon column stayed until its
» or its icon. On, a press away from the flyout now puts it away. A
press on the icon column is left to the icons (they swap or close the
flyout on release, so acting on the press would reopen the one being
closed), and a press in a popup the flyout opened (a panel menu, a
dropdown) keeps it. Off by default, as in Illustrator.
Developed with AI assistance (Claude Code); reviewed and tested locally.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The option tells points from picas, where a bare `12` could be either: the
reference app dims it unless a unit is Picas, and it starts on. A number typed
with no unit into a field in picas is now read in points with it on (the
default again, as before #486), and in picas with it off; fields in any other
unit read it in their unit either way, as there. The Preferences dialog dims
the checkbox unless one of the Units is Picas.
The fields learn the preference when it changes, through
`prefs_dialog::apply_runtime` as Scrub Numeric Fields does, rather than from a
write into the egui context every frame. Tests for picas and other fields, the
wiring and the dimming; docs/mcp.md and ROADMAP say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preferences › Units › Numbers Without Units Are Points was stored and
never read. On, a number typed with no unit into any length field
(`num_field`, `opt_field`, and so the spin fields built on them) is read
in points whatever the field's unit; a typed unit (`12 mm`, `1in`)
still wins, and the field keeps showing its value in its own unit. The
app sets the frame's rule from the preference in `logic`, the fields
read it through the egui context (`widgets::typed_unit`), so none of
the 45 callers change.
The default flips from true to false, which is Illustrator's default
and the behaviour the fields had while the option was unread.
Developed with AI assistance (Claude Code); reviewed and tested locally.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Preferences › General › Show The Home Screen When No Documents Are Open
was stored and never read: the Home screen always replaced the canvas
while no document was open. Off, the window is now empty while no
document is open (as when Illustrator's option is off), and the Home
button (`app.home`) still shows the screen; closing the last document
returns to the empty window. `menus::home_showing` is the one place
that decides, for the canvas and the Home button's lit state.
Developed with AI assistance (Claude Code); reviewed and tested locally.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A Shift-drag marquee only ever added to the selection, so there was no way to
take some of the selected objects out with it. As in Illustrator, Shift-drag
now toggles what the marquee reaches: the selected ones are deselected, the
others selected, the rest of the selection stays.
- Selection tool: the Shift marquee runs select.toggle, which now also takes
{ids} (each one toggles).
- Direct Selection and Group Selection: the Shift marquee toggles the anchors
inside it (select.anchorsMany mode "toggle"); a path left with none leaves
the selection, one with all of them is selected whole again.
- select.anchorsMany takes mode "set"|"add"|"toggle"|"subtract" (add: true is
still mode add). Adding anchors of a path selected whole keeps it whole, and
anchor references outside the path are ignored.
- Lasso: Shift adds and Alt subtracts through the same command, so an Alt lasso
no longer drops selected type, groups and other non-path objects.
- Tests at the tool, engine (Session::pointer) and MCP (pointer_gesture) levels,
command sweep cases for the new modes; docs/mcp.md and ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bounding-box resizes with the Selection and Free Transform tools already lined
their edges up with the other objects (#355, after v0.4.0), but a handle never
landed on another object's anchor point, nothing was labelled, and the Rotate,
Scale, Shear and Reflect tools didn't snap their drags at all.
- Targets::snap_scale: the dragged corner (free, or along its diagonal with
Shift) or side lands on the nearest anchor or centre within the snapping
tolerance, labelled "anchor"/"center"; alignment snaps keep their line and
now say "align", as an artboard's resize handle does. Alt (from the centre)
and rotated boxes work the same way.
- The transform tools start a drag from the anchor or centre they grabbed (the
point a click would set) and snap the pointer as drawing tools do (Smart
Guides, the grid, pixels or Snap to Point), so a corner dragged onto another
object's anchor scales exactly onto it.
- Factor the nearest anchor/centre lookup (Targets::nearest_point) and the
transform tools' drag teardown (end_drag).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preferences › Guides & Grid › Show Pixel Grid (Above 600% Zoom) was stored and
never read. On (the default), in View › Pixel Preview at 600% zoom and above the
canvas draws a translucent line at every document pixel over the art, so the
pixels the art rasterizes to can be seen; nothing below 600%, out of Pixel
Preview, or with the option off. Drawn by the same `grid()` as the document
grid, at a 1 pt spacing.
A headless frame test counts the pixel grid's lines at 800%, 500% and 600%, in
and out of Pixel Preview and with the option off, and checks they are drawn
over the art.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Preferences › General › Anti-aliased Artwork was stored and never read. Off, the
canvas renders the art with `AntiAlias::None` — a pixel is painted when the art
covers at least half of it, raster effects and pattern tiles staying smooth — and
on (the default) with `AntiAlias::Art`, as before. The preference is part of the
canvas's cache key, so changing it re-renders. Exports keep their own
Anti-aliasing option.
A headless frame test reads the art's raster back out of the frame's texture
upload: edge pixels partly covered with the option on, none off, and smooth
again once it is back on.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>