Commit Graph
512 Commits
Author SHA1 Message Date
dotCooCoo 2ccb147403 Variables: Import Data Sets… defines the variables a file's header names
dataset.import refused every header cell that wasn't the name of a
variable the document defines. A data source file could only fill
variables made beforehand, and `#` and `@` in a header were read as part
of a variable's name.

A header cell that names no variable now defines a new, unbound
variable in the import's undo step. `#Shown` defines a visibility
variable `Shown`, `@Photo` defines an image variable `Photo`, and a
cell without a prefix defines a text variable. A prefixed cell whose
name is already a variable of the prefix's kind names it. A cell that
is a variable's exact name keeps naming it, even when the name starts
with `#`, `@` or `%`, so files that dataset.export writes import back
as before. The import fails, naming the column, on a prefixed cell
whose name is a variable of another kind and on a `%` cell that names
no variable (graph data files). While the variables are locked, a
header that would define a variable fails the import with "variables
are locked", as New Variable does. The reply's `defined` lists the new
variables.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 01:27:13 +07:00
dotCooCoo a12f5b0f20 Export: Save for Office, CSS and Copy warn when an image couldn't be decoded or a font is missing
Save for Office Documents, the pictures that css.export rasterizes and
the PNG and PDF that Copy offers other apps were encoded with
fileio::encode, which drops the encoder's warnings. When the art held
an image that the renderer could not decode, the PNGs lacked it; type
in a font that is not installed came out in the fallback font in the
Office PNG and Copy's PNG and PDF. No reply or status message said so.
The app also dropped the warnings that document.exportSelection
returns.

document.exportForOffice, clipboard.exportPng and clipboard.exportPdf
now return `warnings`, and css.export adds the notes of its pictures to
its `warnings`, each once. Save for Office Documents and
document.exportSelection show the first note in the status bar in Save
As's message, and the CSS Properties panel's Export Selected CSS… and
Export All… show it in Export As's message. After Copy or Cut, once
the PNG and PDF are on the system clipboard, the status bar shows their
first note. The files are written as before.

Docs: docs/mcp.md (Clipboard, CSS Properties), docs/control-protocol.md.
2026-10-18 01:27:13 +07:00
dotCooCoo 919970a467 Export for Screens: exports warn when an image couldn't be decoded or a font is missing
Export for Screens wrote each file with fileio::encode, which drops the
encoder's warnings, and its replies had no `warnings` key. When the art
held an image that the renderer could not decode, or type in a font
that is not installed, the files lacked the image or used the fallback
font, and neither the reply nor the status bar said so. document.export
returns the same notes in `warnings`, and Export As shows the first in
the status bar.

document.exportForScreens and assets.export now collect the warnings of
every file they export, each once, and every reply carries them in
`warnings`. The export still writes or returns every file. When there
are warnings, the Export for Screens dialog and the Asset Export panel
show the first in the status bar as Save As shows a save's notes.

Docs: docs/mcp.md (Export for Screens), docs/control-protocol.md.
2026-10-18 01:27:13 +07:00
dotCooCoo 6b13dd776b Export: names typed into file names follow Windows' naming rules
Export for Screens, Batch over data sets, Save for Web and the
Libraries panel name files after text the person typed: a prefix, a
suffix, a name pattern, the name of an artboard, asset, layer, slice,
data set or library, or the document's title. The prefix, the suffix,
name patterns and Batch's data set names had only /, \ and : replaced,
so on Windows a name holding <, >, ", |, ?, * or a control character
failed to save, and Save for Web without a path named its files after
the document's title as it was. A row folder name could end in a
period, which Windows does not allow, and names such as CON, NUL, COM1
or LPT1, which Windows reserves for devices, were written as they
were.

file_part, which Export for Screens' prefix and suffix and Batch's
data set names use, now writes all of those characters as -, and name
patterns use it in place of their own copy. A new safe_file_name runs
a whole file or folder name through file_part, removes trailing
periods and spaces, and adds - after a device name that is the whole
name or comes before its first period (CON.png becomes CON-.png).
Export for Screens uses it for each file, folder and zip it names, and
keeps item names distinct after it. Save for Web uses it for slice
images and, without a path, for files named after the title. Library
files use it too. This happens on every platform. Other names are
written as before, except where a changed name now equals another
item's name, which then gets a number added.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 01:27:13 +07:00
dotCooCoo 7f858a7e59 Actions: Batch's Override Save and Export name files with the format's extension
With Override Save or Override Export on, action.batch named each
step's file in the destination folder with the step's format exactly as
written. The save and export commands read the format in any case and
with a leading dot, so a step with "format": ".svg" wrote
"Untitled_a..svg", "SVG" wrote ".SVG", "png8" wrote ".png8" and
"template" wrote ".template".

The file now ends in the format's extension: the extension as written,
in lower case and without the dot, when it is one of the format's
(".JPEG" gives ".jpeg"), and otherwise the format's first extension, the
one Export and Save use for a file name ("png8" gives ".png", "template"
gives ".vctemplate"). An unknown format ("xyz") keeps its spelling, and
the step fails with the command's error as before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 01:24:35 +07:00
dotCooCoo 1c4ead3703 Place: Word .docx files place as area type with their formatting
File > Place could not place a Word document: a .docx file was "not a
format VectorCraft reads". The reference app's File > Place reads
Microsoft Word files as text.

File > Place now reads .docx files, written from ECMA-376 Part 1
(WordprocessingML, Transitional and Strict), and sets their text as
area type the way a text or RTF file is placed. The package is read
through its relationships. The formatting comes from the document
defaults, the paragraph style (else the default paragraph style), the
character style (else the default character style), the styles each
is based on, and direct formatting. Bold, italic, caps, small caps,
strikethrough and hidden text are toggle properties: a style that
turns one on reverses the state the styles before it give.

Each run keeps its font (theme fonts from the theme part), size, bold
and italic, underline, strikethrough, all caps, small caps,
superscript or subscript (in Document Setup's proportions), raised or
lowered text and color. Each paragraph keeps its alignment, start,
end, first-line and hanging indents, space before and after, and
direction; the start indent of a right-to-left paragraph is its right
indent. Table cells are separated by tabs and each table row is a
paragraph. Text in insertions, hyperlinks, content controls and
fields' results is read. Hidden and deleted text, field instructions,
pictures, text boxes, headers, footers, notes and list numbers are
left out.

A ZIP archive named .docx, or one that holds word/document.xml, is
read as a Word document. fileio/zip.rs gains a reader for stored and
deflated files that checks each file's CRC. A package with more than
10,000 files, a part that unpacks to more than 16 MiB or nests
elements more than 256 deep, and a document with more than 4 MiB of
text or 100,000 paragraphs or runs are refused with an error. The
Place dialog lists Word files, and file.place, file.place.info and
file.place.queue report the format `docx`. .doc files are still not
read.

The Word document tests in tests_textimport.rs fail without the
change, and text_import_fuzz.rs places random and damaged packages.

Docs: docs/file-format-parity.md, docs/mcp.md,
docs/target-app-parity.md.
2026-10-18 01:21:31 +07:00
dotCooCoo c44fb242aa Type: Optical Margin Alignment is a setting of the whole thread
Optical Margin Alignment was stored and set per text object, so the
frames of one thread could differ. Turning it on in the first frame of
a thread left the lines in the other frames flush with their margins.

Type › Optical Margin Alignment and type.opticalMarginAlignment now set
every frame of a selected frame's thread, in one undo step, and the
reply's ids list them all. After any edit to a thread, its frames take
the setting of the frame that the edit turned on or off, else the
first frame's. Threading frames gives the thread its first frame's
setting, and frames taken out of a thread keep it. A file whose frames
of one thread differ opens with the first frame's setting in all of
them. This holds for native files and for documents restored from the
editing copy that an EPS, SVG, PDF or .ai file carries.

Threading text from an out port into existing area type
(text.thread.add with to) now flows the story into that frame at once.
Before, the story reached it only at the next edit of one of the
thread's frames.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md.
2026-10-18 01:21:27 +07:00
dotCooCoo e5945c6795 Type: sizes, scales, leading, indents and spacing out of range in a file are read in range
A .vectorcraft file's type opened with the character and paragraph
values it held. With a size of 0, the characters were not drawn. With
a size, leading, horizontal scale, left indent or space before of
1.797e308, point type's bounds were infinite or over 1e308 wide or
high, and moving or scaling it failed with "result would exceed the
canvas".

These values are now read into range when a file opens, in the type of
the layers, symbols, graph designs, patterns and the pattern definition
that Cancel puts back in pattern editing mode, and in character and
paragraph styles. A size, horizontal scale or vertical scale of 0 or
less becomes 12 pt or 100%, and a negative leading becomes Auto. A
size, leading or scale past 4,000,000 (MAX_COORD) becomes 4,000,000,
and a baseline shift, indent or space before or after past 4,000,000
either way becomes 4,000,000 or -4,000,000. A tab stop is kept to the
0 to 100,000 pt that text.tabs.set takes. A leading of 0, which legacy
.ai import stores, and the sizes, scales and indents within these
limits that SVG, PDF and .ai import keep outside the panels' ranges
open as saved.

Docs: docs/mcp.md (Paragraph attributes per paragraph).
2026-10-18 01:21:25 +07:00
dotCooCoo 81574b1648 Type: superscript, subscript and small caps proportions out of range in a file are read in range
document.setup accepts a superscript and subscript size of 1 to 500%
and a position of -500 to 500%, and a small caps size of 1 to 200%.
text.setFormat copies these proportions into the characters it
formats. A .vectorcraft file's characters, character styles and graph
label styles opened with the proportions they held. With a superscript
size of 0, the superscript characters were not drawn, and with a
superscript or small caps size of 1.797e308, moving the type failed
with "result would exceed the canvas".

These proportions are now read as the nearer end of each range when a
file opens: in the type of the layers, symbols, graph designs, patterns
and the pattern definition that Cancel puts back in pattern editing
mode, in graph label styles and in character styles. Proportions in
range and the other character attributes open as saved.

Docs: docs/mcp.md (Small Caps, Superscript and Subscript).
2026-10-18 01:21:24 +07:00
alpha-rock 039a467dcb M9: add multiple colored lights to 3D effects
Add opaque RGB light color and up to ten independently adjustable lights to
Revolve, Extrude & Bevel, Inflate and Rotate. Keep light selection, Add/Delete,
selected-light gizmos and preview transactions on the existing command path.

Preserve legacy white-light Inflate polygons, paint composition and seam joins,
including the 0.05 seam minimum. Use shared smooth normals for colored Inflate,
and batch solid-fill RGB channel paths behind one surface clip. Keep source
paths editable and SVG/PDF output vector artwork.

Rebase onto main f7de3d6a, retaining both sides of progress docs and all 22
catalogs. Shade freeform points once through the shared paint mapper. Document
release measurements and a reproducible CLI fixture, including the remaining
source-gradient cost and the one-light timing tradeoff.

Validation: full cargo xtask ci passed all 10 steps; 7,978 tests passed, zero
failed and 11 ignored. Four legacy white-light PNGs match unchanged main byte
for byte. Three paired release bench/perf repetitions completed without busy
warnings; all six standard perf runs passed every budget. Two-light solid
Inflate improved from 136.2 to 96.8 ms/frame at 1200x1200, default eight threads.
2026-10-18 01:11:39 +07:00
makesomethingshit e1a6e373d5 docs(blend): document Anchor Point tool spine conversions (#2544) 2026-10-18 01:11:39 +07:00
Guillem Hermida 2a5dfa6a94 Fix #1876: Artboard Options has X and Y with a reference point
Artboard Options showed Name, Width and Height only: the generic form
hides x and y, which in the shape dialogs are the click position. The
artboard's position could be dragged but not typed.

The dialog now draws its own rows after the form's, in the same grid:
the reference point locator the Transform panel, the Control bar and
Properties share, then X and Y. They show where the reference point
lies, counted from the ruler origin (#1212), as the Transform panel and
the Control bar show an artboard's position. A typed X or Y moves the
artboard so that its reference point lands there, and a new Width or
Height keeps the reference point in place. The fields still hold the
top-left corner in document points, so OK runs artboard.setProps as
before, in one undo step. The shape dialogs keep their x and y hidden.

form::grid_then lets a dialog add rows to the generic grid.
2026-10-18 01:09:52 +07:00
VictorandClaude Opus 5.5 6190525482 G4: Paragraph panel menu › Roman Hanging Punctuation
The Paragraph panel menu's Roman Hanging Punctuation was a disabled stub.

It is now a per-paragraph attribute: in a left-to-right paragraph of point
or area type with it on, the punctuation Optical Margin Alignment hangs
(quotation marks, hyphens, full stops and commas wholly; asterisks,
tildes, ellipses, en and em dashes, colons and semicolons by half) hangs
outside the margin when it starts or ends a line, on the same sides
(left-aligned lines at their left, right-aligned at their right, centered
and justified at both); letters don't hang. With Optical Margin Alignment
on as well, letters hang too. East Asian punctuation keeps hanging only
through burasagari. The layout shares Optical Margin Alignment's hanging
code, given the share of each glyph that hangs.

text.setFormat takes romanHangingPunctuation; it is off by default, saved
only when on, carried by paragraph styles (New and Redefine capture it)
and turned off by Reset Panel. The panel menu item is a checkbox enabled
with type selected.

Docs: docs/mcp.md, docs/type-parity.md, docs/gaps.md.

Closes #2756.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:09:34 +07:00
BFlat c1c677bc76 Merge pull request #2409 from dotCooCoo/feat/live-rasterize-effect
Effects: Effect › Rasterize draws an object as pixels while its paths stay editable
2026-10-17 20:02:59 +07:00
dotCooCoo 80b9753866 Paths: Reshape Segment, Convert Anchor Point and the Pencil refuse values of 1e9 pt or more
path.reshapeSegment refused only a delta that was not finite. It moves
a segment's two handles by the delta divided by 3t(1-t), which is
0.1425 at t 0.05 or 0.95, so a finite delta above about 2.5e307 wrote
infinite handles into the path. object.blend.spine.reshapeSegment
refuses a delta of 1e9 pt or longer.

path.reshapeSegment now refuses a delta of 1e9 pt or longer, with its
"invalid delta" error. Two other path commands took a point with no
bound. path.convertAnchor with x, y at 1e308 put the two handles 2e308
apart, so the path's control box was infinitely wide, and path.freehand
through such points with keepSelected off fitted a path whose bounds
reached infinity. Both now refuse a point whose x or y is 1e9 pt or
more in size, with "x and y must be finite", the error
object.blend.spine.moveAnchor gives for the same bound.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 19:38:47 +07:00
dotCooCoo 4712fb2c2f Character: variable fonts get a slider for each axis
VectorCraft listed a variable font's named instances as styles, but
type could not be set between or past them. The reference app's
Character panel has a Variable Font control that adjusts a variable
font's axes, such as Weight, Width and Slant.

Type now keeps Variable Font settings by axis tag
(CharStyle::font_axes, saved as fontAxes only when set). text.setFormat
and text.setRangeStyle take fontAxes: each value is clamped to its
axis's range, a tag the font has no axis for is left out, and null
goes back to the style's own setting. Another font or style clears
them, including one from a character style that does not set them. A
character style captured from type sets them, with {} for none.
text.fontList {family, style} lists a font's axes.

Layout, outlines, the substituted-glyph highlight and PDF export use
the font instanced at the settings, and neighboring runs at different
settings are shaped apart, as runs in different fonts are. SVG export
writes them as font-variation-settings, the wght setting as the weight
and the wdth setting as font-stretch, and embeds the font instanced
there; SVG import reads font-variation-settings back. Legacy .ai export
warns that the settings are left out.

The Character panel shows a Variable Font button for a variable font.
It opens a slider and a field for each axis the font does not hide,
labeled with the font's name for the axis, and screen readers get each
slider under that name. A drag is one undo step. Reset Panel leaves
the settings, as it leaves the font and style.

Missing Glyph Protection now keeps the settings and the font version
on the characters that keep their font; before, it kept only the
family and style. A character style applied or redefined with another
font now clears the old font's version, as text.setStyle does, unless
the style sets one. SVG export with embedded fonts warns when faces of
one weight, style and width share characters, which browsers then draw
in one of the faces.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md.
2026-10-17 19:38:46 +07:00
bflatastic f38f6fafc3 Fix #2603: a panel per swatch library, with Persistent
Each swatch or graphic style library now opens in a panel of its own,
`library:<kind>:<library id>`, in the shared docking system: several can be open
at once, and they float, group as tabs, dock and close like any panel.
Opening a library that is open brings its panel forward; Previous / Next
Library show the neighbouring library in the same panel.

The library panel menu gains Persistent. The panels' view, Show Find Field
and Persistent are kept in the UI preferences (`library_panels`); at startup
the persistent panels reopen where the saved layout had them, a library
loaded from a file read again from its path (a missing file is skipped with
a status-bar note). `ui.libraryPanel {panel, library?, view?, showFind?,
persistent?}` reaches all of it; docs/mcp.md documents the panels.
2026-10-17 19:13:49 +07:00
bflatastic 88567ee46c Part of #2572: clicking an area type's out port threads its text into a path or a new frame 2026-10-17 19:13:22 +07:00
dotCooCoo 04b0be2c9f Merge main into feat/live-rasterize-effect 2026-10-17 04:09:12 -07:00
bflatastic 2879c206a8 Fix #2541: installed plug-ins stay installed after a restart
Install Plug-in (plugin.install) now keeps the plug-in's module in a
Plug-ins folder next to the preferences, as Illustrator keeps plug-ins in
its Plug-ins folder, and the desktop app and vectorcraft-cli install the
ones there at startup (a module that doesn't load is skipped with a log
record, at most 512 MB are read). Remove Plug-in deletes it from the
folder. The Additional Plug-ins Folder preference is still loaded from
where it is. Confined MCP servers and VECTORCRAFT_NO_PREFS keep nothing,
as with the PDF preset and colour profile stores.

Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md.
2026-10-17 17:49:38 +07:00
dotCooCoo 14a3740e51 Export: PDF, EPS, SVG, EMF, WMF and image exports warn about type that doesn't fit its frame
Print and Print Preview name the area type and type on a path that
prints without the text past the end of its frame (#2308). The exports
that draw type gave no such warning: a PDF, EPS, SVG, EMF, WMF or
raster export of a frame too small for its text answered with no
warning about it.

These exports now add the same warning, with "isn't drawn in the
export" in place of "doesn't print". Each export counts the visible
objects that it draws. Those are the objects on the pages or artboards
it writes, or all of them in an EPS of the visible art and in an SVG
without chosen artboards. A PDF leaves out layers that don't print
unless includeNonPrinting or createLayers is set. The objects are found
with the walk that fonts::printed_type runs for Print, and typecmd.rs
builds the message with the code that builds Print's. A .ai file keeps
the whole text in its editing data and gives no warning.

Docs: docs/mcp.md (Fitting area type), docs/target-app-parity.md.
2026-10-17 17:13:03 +07:00
dotCooCoo 30c5bd3589 Guides: Lock Guides keeps guides deselected after Undo, Exit Isolation Mode and Paste
With View > Guides > Lock Guides on, Session::select drops the paths
made into guides from the selection (#1534), but commands that set the
selection another way skipped that filter and left a locked guide
selected, so the next move moved it. object.exitIsolation selected an
isolated guide path. Undo, Redo, a canceled undo group, a canceled
drag or dialog preview and a failed command.batch brought back locked
guide paths and ruler guides in the selection. Unlock All, Show All
and Paste selected the guide paths they unlocked, showed or pasted.
Locking the guides deselected them in the active document only.

The filter is now deselect_guides, which also clears the selected
ruler guides. Session::select and Session::edit run it while Lock
Guides is on, and so do object.exitIsolation, Undo, Redo,
Session::cancel_interaction, a canceled undo group and a failed batch.
Locking the guides deselects them in every open document. With Lock
Guides off, the selection is set as before.

Docs: docs/mcp.md.
2026-10-17 17:13:03 +07:00
dotCooCoo 59f63e9161 Variables: Lock Variables stops variables from being defined, deleted or renamed
Nothing locked the variables: any command or panel button could delete
or rename a variable that scripts and data sets name.

variable.lock {locked?} (Lock Variables: a lock button in the Variables
panel's bottom bar and an item in its menu) sets a flag that is saved
with the document. Without `locked` it toggles, and a change is one
undo step. While the flag is set, variable.define, variable.delete,
variable.rename and the four variable.make…Dynamic commands are refused
with "variables are locked", and the panel disables New Variable…,
Variable Options…, Delete Variable and Make … Dynamic. Binding,
unbinding and the dataset commands still run. variable.list
reports `locked`. A file without the flag opens unlocked.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 17:13:03 +07:00
dotCooCoo 67b47c7262 Artboards: Rearrange and Select All on Active Artboard measure a symbol instance by its art
Rearrange and Select › All on Active Artboard measured a symbol
instance as its transform applied to the 20 × 20 square around the
symbol's origin, not by the symbol's art. An instance whose art lies
away from that square moved with, or was selected on, the artboard
the square was on. Convert to Artboards, Transform Each, Lock and
Hide › All Artwork Above, Paste and Place from Library at a point,
and the clipboard imports measured the instance the same way, as did
Copy and Cut when they recorded the source artboard and library.add
when it sized a graphic.

These commands now measure an instance by its art through
Document::symbol_boxes, as Document::art_on_artboard, Fit to Artwork
Bounds and the Align commands do. Clipboard::bounds measures with the
symbols the clipboard holds. clipboard.exportPng, clipboard.exportPdf
and the PNG and PDF that Copy offers other apps are now cropped to
the instance's art.

Docs: docs/mcp.md.
2026-10-17 16:38:43 +07:00
dotCooCoo 5669e02331 Variables: a data set rewrites bound type on hidden layers
dataset.select skipped every bound object inside a hidden layer or
group. Type on a hidden layer kept the previous row's text, which
showed when the layer was shown again, and dataset.list and the
Variables panel reported every row as matching that type while the
layer stayed hidden.

dataset.select, dataset.next and dataset.prev now rewrite type and
apply the other values on hidden objects and inside hidden layers and
groups. Those objects stay hidden unless a visibility value shows
them. Objects that are locked or inside a locked layer or group are
still skipped and counted in `skipped`. dataset.list, dataset.capture
and dataset.update read hidden objects as well. Binding still refuses
art inside a hidden layer or group.

The values now go in the order the variables are defined. The passes
that applied visibility values before and after the other values are
removed.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 16:38:43 +07:00
dotCooCoo 4285ccdda3 Rotate View: a two-finger rotation on a Mac trackpad turns the view about the pointer
A two-finger rotation on a Mac trackpad did not turn the view. With
the new General preference Trackpad Gesture to Rotate View
(`trackpadRotateView`, on by default) and the pointer over the canvas,
the rotation turns the view about the pointer, keeping the document
point under it in place. The view does not turn until the rotation
reaches 5°; it then turns by the whole rotation and follows each later
rotation event. A rotation ends 0.25 s after its last event. A rotation
in the same frame as a pinch zooms and turns about the same point.

egui reports the gesture as `Event::Rotate`: egui-winit makes it from
winit's macOS rotation gesture, and eframe makes it from Safari's
gesture events in the web build. Rotation events that arrive while
fingers are on a touch screen do not turn the view. With the
preference off, the gesture does not turn the view.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/hardware-parity.md.
2026-10-17 16:38:42 +07:00
dotCooCoo 133fd73feb Rasterize, Flatten Transparency: commands that replace art with a rendering warn about an undecodable image
Object > Rasterize, Crop Image, Flatten Transparency, Expand
Appearance of raster effects and Vector Halftone draw the art with the
renderer and replace it with the result. When the art held an image
that could not be decoded, the renderer drew nothing for it, and the
result lacked the image with no message. A raster export of the same
art warns that "an image couldn't be decoded and is missing from the
picture".

These five commands now compare decode_failures before and after they
draw, as raster exports do, and their replies carry that text as
`warning`. They still make their result in one undo step. The app
shows a command's `warning` in the status bar: after a menu command,
after OK in a dialog with a preview, and after a tool runs a command
(Crop Image's Apply).

Docs: docs/mcp.md (Flatten Transparency, Expand Appearance, Document
Raster Effects Settings, Vector halftone).
2026-10-17 16:38:42 +07:00
dotCooCoo e74b09c86d New Document and Document Setup: values out of range in a saved preset or a file are read in range
file.new takes a width and height up to 163,830 pt, 1 to 1,000
artboards, a spacing of 0 to 100,000 pt and a bleed of 0 to 72 pt a
side, but a saved or Recent New Document preset in the preferences
was read as it was stored. With a width of -5, file.new failed with
"width/height must be positive and within the canvas"; a spacing of
-50 overlapped the artboards, a preset with 5,000 artboards made 5,000
artboards, and a bleed of 100 reached the document. A .vectorcraft
file's Document Setup bleed, superscript and subscript proportions and
small caps size also opened as stored, and document.setup then failed
when its own report was passed back to it.

These values are now read as the nearer end of the range their
setter takes, and an artboard width or height of 0 or less is read as
1 pt. Presets and files with values in range read as before.

Docs: docs/mcp.md (Document Setup, New Document).
2026-10-17 16:38:42 +07:00
dotCooCoo d18135bfcd Object Mosaic: columns and rows that are not whole numbers are refused
object.createObjectMosaic read columns and rows with as_u64 and fell
back to 10 for anything else, so 2.5, -3 or "4" made a 10 x 10 mosaic
and reported success. Transform Each read reference and seed the same
way (a reference of 2.5 used the center point, and a seed of 7.5 used
the default seed), and Rearrange All Artboards cut 1.5 columns to 1
and read "1" as the default 2.

These five parameters now take a whole number, written 4 or 4.0 as the
Rearrange All Artboards dialog sends it. A fraction or a negative
number is refused with "`columns` must be a whole number", the message
object.slice.divide gives. A string or another value that is not a
number is refused with the check_numbers error. The command then
changes nothing. The existing ranges stay: 1 to 1000 mosaic tiles
across and down, a reference of at most 8, and 1 to the artboard
count of columns.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 16:38:42 +07:00
dotCooCoo 3adf5141e7 Fix #2276: the Eyedropper and RGB input give color-managed CMYK values in a process document
In a CMYK document, an RGB color from the Eyedropper, a hex value or
paint.setFill was stored as CMYK by the profile-free formula
(k = 1 - max(r, g, b)), not through the color settings. RGB 83/170/223
came in as C63 M24 Y0 K13. A CMYK image was sampled through the RGB its
decoder shows, so its inks came back only when the formula happened to
round-trip them.

paint_from now converts an RGB color into a CMYK document with
Cms::convert and the settings' intent, as Document Color Mode and Swatch
Options already do. This covers paint.setFill and paint.setStroke,
gradient stops, swatch.new {color} and the Eyedropper. With Generic CMYK,
83/170/223 now separates to C59 M12 Y3 K0, and #ff0000 now separates to
C0 M97 Y100 K0 instead of C0 M100 Y100 K0. keepModel: true still keeps
RGB. paint.sampleColor with a stop stores the color the same way.

The Eyedropper samples a CMYK image's ink amounts (a four-component
JPEG, or an 8- or 16-bit chunky CMYK TIFF) and returns them unchanged
(ImageBlob::sample_cmyk_object), also in an RGB document. It takes an
RGB image's sRGB pixel into the working RGB space, as the screen color
sampler does, so the picked color shows as the pixel when the working
space is not sRGB.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 16:38:42 +07:00
vangel76andClaude Opus 5.5 3eed742639 Symbols: Symbol Options › Enable Guides for 9-Slice Scaling (#2342)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:38:41 +07:00
VictorandClaude Opus 5.5 276eeacae6 G7: View › Rulers › Change to Global / Artboard Rulers
View › Rulers › Change to Global Rulers (Cmd+Alt+R) was a disabled
stub: the rulers always counted from one document zero point. The
reference app's public help describes two modes: artboard rulers, the
default, count from the active artboard's top-left corner, and each
artboard can have its own zero point; global rulers keep one zero point
for the whole document whichever artboard is active.
https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/about-rulers.html
https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html

`view.globalRulers {global?}` switches the mode (toggle by default),
undoable and saved with the document (`rulers`). The menu item reads
Change to Artboard Rulers while global rulers are on. A new document has
artboard rulers; a file saved before the mode existed has no `rulers`
key and opens with global rulers, so it measures as it did.

With artboard rulers, dragging the ruler corner sets the active
artboard's zero point, kept as an offset from its corner
(`artboard_ruler_origins`, by artboard id) so it moves with the
artboard, and double-clicking the corner resets it. The global zero
point (`ruler_origin`) stays separate. `document.rulerOrigin`,
`document.setRulerOrigin` and `document.resetRulerOrigin` take
`artboard`; `Document::ruler_origin_for` is the zero point with an
artboard active, falling back to the corner or the global one when a
stored value is out of range.

The rulers, the Transform, Properties and Control bar X/Y, the Info
panel's X/Y (which ignored the zero point before), ruler-guide
readouts and Shift ruler-tick snapping all count from that zero point:
the view's active artboard reaches the tools through `ViewInfo` and
`ToolContext::ruler_origin`. Command coordinates stay absolute.

Tests: engine/tests/ruler_origin.rs (default mode, per-artboard origin
following its artboard, global origin apart, toggle and undo, save and
legacy files, out-of-range values), ui-egui/src/tests_ruler_origin.rs
(position fields and corner drag on the active artboard, menu label and
shortcut), tools/src/rulerguide.rs (guide readouts and tick snapping).
Tests written for the single global zero point now turn global rulers
on first. "Change to Artboard Rulers" is translated in every catalog.

Docs: docs/ui-parity.md, docs/gaps.md (G7), docs/target-app-parity.md,
docs/mcp.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2JbUtM4MkzWLm9FgYiEsq
2026-10-17 16:38:41 +07:00
dotCooCoo 84ccee71ef Merge main into feat/live-rasterize-effect 2026-10-17 01:25:27 -07:00
dotCooCoo b9309f7985 Graphs: Object › Graph › Column draws columns with graph designs
Object › Graph › Column… was a menu stub, so a graph's columns and bars
were always plain rectangles. The reference app fills them with a graph
design, vertically scaled, uniformly scaled, repeated with a value for
each copy, or stretched at a guide line.

graph.column (Column…) draws the columns, bars or stacked segments and
the legend swatch of the series picked with Group Selection with a
design saved by Object › Graph › Design. Vertically Scaled stretches the
design to each column's length at the column's width. Uniformly Scaled
scales it both ways to that length and centers it on the column.
Repeating stacks a copy for each Each Design Represents value out from
the zero line, and For Fractions chops the last copy at the value or
scales it down. Sliding keeps the part above the design's horizontal
guide line in proportion and stretches the part below. The design and
its options are saved with the graph, and the graph follows the design
when it is renamed or deleted. Columns drawn with a design cast no drop
shadow.

A marker or legend design made of one path is now drawn in a group of
its own. Painting a series with Group Selection then leaves the design's
paint alone, as it already did for designs of several objects.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 15:14:43 +07:00
dotCooCoo f9759afa30 Live Paint: Gap Options find and close gaps in Live Paint groups
Object > Live Paint > Gap Options was a stub, and paint leaked through
any gap between the paths of a Live Paint group. The reference app's
Gap Options stop paint at small gaps, close them with unpainted paths
and preview them on the canvas.

livePaint.gapOptions (Gap Options) sets a group's options: detect (Gap
Detection), size (small 3 pt, medium 6 pt, large 12 pt, or a custom
size over 0 up to 72 pt), color (the Gap Preview Color, #rrggbb or a
layer color's name) and closeGaps, which adds an unpainted path across
each gap to the group's paths. A gap runs from a loose end of a path
to the nearest point of any path within the size, unless the paths
already join the two close by. With detection on, the faces are made
again as if a line closed each gap, and each face or edge keeps the
look of the old one it lies in or on. The options are stored on the
group, so they are saved, copied and undone with it, and Expand drops
them. Merge keeps the bottom group's options when its Gap Detection is
on, or else turns Gap Detection on with Small when another merged
group has it on. When Gap Detection is off in every merged group,
Merge keeps it off. livePaint.info lists the options and the gaps up
to the size wide that no path closes.

The menu item opens a Gap Options dialog. With Preview on, the options
apply live and the gaps show on the canvas in the preview color, and
OK keeps them as one undo step. vectorcraft-pathops gains find_gaps
and live_paint_with_gaps.

Docs: docs/mcp.md, docs/target-app-parity.md.
i18n: the 19 complete catalogs have rows for the dialog's labels, and
bg's Gap Options menu label now names gaps instead of line spacing.
2026-10-17 15:14:43 +07:00
dotCooCoo be553fa3cf Window: the SVG Interactivity panel attaches JavaScript events and files for SVG export
Window › SVG Interactivity was a disabled menu item. VectorCraft could
not attach JavaScript to an object's events or link JavaScript files
for SVG export.

The SVG Interactivity panel lists 19 events, from onfocusin to
onscroll. Pick one, type its JavaScript and press Enter to add it to
the selected objects; the list shows their events. Remove Selected
Entry removes the picked event, and the panel menu's Clear Events
removes all of them. Link JavaScript Files… opens the JavaScript
Files dialog, which adds a path or URL to the document's list and
removes the selected one. Its Choose… button puts a picked file in
as a file:/// URL. The commands are svgInteractivity.info,
setEvent, removeEvent, clearEvents, addFile and removeFile, and
ui.javascriptFilesDialog. Each edit is one undo step. A document
links at most 64 files.

SVG export writes each event as an event attribute on the object's
element and each file as a <script xlink:href> element. With the SVG
Tiny 1.2 profile, export leaves the events out with a warning. SVG
import reads the listed event attributes and the script links back
as text. It never reads a linked file or runs a script, and it leaves
inline scripts and the links after the first 64 out with a warning.
The native format keeps both.

An unnamed <g> with data-* attributes of its own around a single
object now imports as a group. Before, import folded it into the
object, which then took the group's data-* or, when it had its own,
dropped the group's. An unnamed <g> with event attributes stays a
group in the same way. The SVG import fuzz tests now include event
attributes and <script> elements.

Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md,
docs/ui-parity.md.
2026-10-17 15:14:43 +07:00
dotCooCoo 8bcebf0e48 Artboards: Alt-click on New Artboard opens Artboard Options for the new artboard
An Alt-click (Option-click on macOS) on the Artboards panel's New
Artboard used to add an artboard at once, as a plain click does. It now
opens Artboard Options for the new artboard, filled in with the name and
size that artboard.new {} would give it, and adds nothing yet. OK adds
the artboard with the fields where artboard.new {} would place it (20 pt
to the right of the last artboard) in one undo step, and makes it the
active artboard; Cancel adds no artboard. A plain click and a row
dropped on the button work as before.

The new UI command ui.newArtboard ("New Artboard Options…") opens the
dialog. It is listed after ui.newLayer, and its label has a row in each
catalog.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 15:14:42 +07:00
dotCooCoo d949c3d8b7 Type: Optical Margin Alignment hangs punctuation and letter edges outside the frame
Type › Optical Margin Alignment was a disabled menu item. The reference
app's Optical Margin Alignment hangs Roman punctuation and the edges of
letters such as W and A outside the margins of every paragraph of a
type object.

type.opticalMarginAlignment (Type › Optical Margin Alignment) turns it
on or off for the selected type in one undo step, and the menu item is
checked while the type has it on. In every left-to-right paragraph of
point and area type, quotation marks, hyphens, full stops and commas at
a line's start or end hang wholly outside the margin there. Asterisks,
tildes, ellipses, en and em dashes, colons and semicolons hang by half
their width, and A, V, W, Y, v, w and y hang by a tenth of theirs. A
punctuation mark followed by a quotation mark hangs with it.
Left-aligned lines hang at their left margin, right-aligned lines hang
at their right margin, and centered and justified lines hang at both.
Lines break where they did before, and the space that justification
adds between glyphs leaves out the gaps next to a hanging punctuation
mark.

The setting is off for new type and for documents from before it, and
the type object saves it only when it is on.

The Type menu's checks for Optical Margin Alignment, Type Orientation
and the Type on a Path effects now read the type inside a selected
group, as the commands do.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md.
2026-10-17 15:14:42 +07:00
dotCooCoo 62fd793603 Expand: pattern fills expand into their tiles clipped by the shape
Object > Expand with Fill expanded gradient fills only. A pattern fill
stayed a live pattern, and object.expand.info did not report it, so the
dialog's Fill option was disabled for it. In the reference app, Expand
with Fill turns a pattern fill into the pattern's tiles, and the shape
becomes their clipping mask.

The Fill step now turns each visible pattern fill of a path or compound
path into a clip group shaped like the object, holding the tiles the
pattern lays over the object's box with fresh ids. The group isolates
the tiles' blending and takes the fill's opacity, blend mode and other
effects. Geometry effects of the fill or the object, such as Offset
Path, shape the group's outline and the box the tiles cover. A path in
the tile art with a random Transform effect is baked with its own id
first, and a path with a brush that varies at random (calligraphic or
scatter with Random variation, or bristle) is baked where the pattern
defines it first, so every tile matches the canvas. The object's other
fills and strokes stay on copies of it in paint order, and Expand stays
one undo step. Type with a pattern fill expands the same way once
Object outlines it, and object.expand.info reports pattern fills under
fill. It also reports gradient fills under a new gradient key, and the
dialog enables Expand Gradient To only when the selection has one. A
pattern fill stays as it is when it lays no tiles over the box, when a
group or type in its art has a random Transform effect, when the
pattern is scaled, rotated, reflected, stretched or sheared and its art
holds a symbol instance, and when the pattern is rotated, reflected,
stretched or sheared, or scaled while its art has geometry effects, and
its art has raster or 3D effects, or a geometry effect on a group or
type.

PatternDef gains instances_in_with, which places the tiles with a given
Scaling. Expand has stroke weights, effect distances and the tiles' own
pattern paints scale with the pattern's transform. When the pattern is
rotated, reflected, stretched or sheared, or scaled while its art has
geometry effects such as Roughen, the strokes and geometry effects of
the tiles' paths become filled art first (bake_appearance, and Outline
Stroke's conversion for brushed strokes), and their live shapes become
plain paths, in their opacity masks too. Tiles place their opacity
masks with them, unlinked ones included (place_art), which also fixes
unlinked masks in pattern tiles in PDF, EPS, EMF and WMF export and in
Distort Pattern Fills.

Faults in the shared code are fixed for every caller. assemble
dropped the effects of a sole part, so Expand lost a lone gradient
fill's own effects and Outline Stroke lost those of a stroke with a
gradient along or across it. Outline Stroke also dropped a brushed
stroke's raster effects from its brush art. Expand replaced an object
whose gradient fill could not be expanded with a plain copy; it now
leaves it as it was. Expand left the geometry effects of a gradient
fill, and with Object off those of the object, on the expanded art,
where they reshaped every strip; they now shape the clip group's
outline, and the gradient fits it, as for pattern fills.
bake_appearance dropped a stroke's raster effects from its filled
outline and filled the outline of a stroke with a gradient along or
across it with a gradient across the outline's box; it now keeps the
effects and makes the gradient meshes clipped to the outline, as
Outline Stroke does. Envelope Options > Distort Appearance uses it too.
PatternDef::offsets_covering overflowed on a region too large to count
in integers, as for a pattern scaled by 1e-20; it now gives no offsets.

Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md.
2026-10-17 15:14:42 +07:00
dotCooCoo 53100a458f Type: the Touch Type tool moves, scales and rotates single characters
The toolbar listed the Touch Type tool (Shift+T), but choosing it did
nothing. The reference app's Touch Type tool moves, scales and rotates
one character of live type by dragging the handles of a box around it.

A click with the tool now selects one character and shows a box around
it. Dragging the box or its lower-left handle moves the character along
its line and up or down. The upper-left handle scales it vertically,
the lower-right handle scales it horizontally, the upper-right handle
scales it proportionally, and the upper-middle handle rotates it. A
character rotated by up to about 84 degrees follows the pointer too.
Near a quarter turn, a drag moves it only along its line. Escape
cancels a drag, and otherwise lets go of the character.

Each drag is one text.touchType command and one undo step. It sets the
character's baseline shift, scales and rotation, and moves it along its
line by changing its own kerning and the kerning of the character
before it, within the kerning range. The characters after it stay
where they are. Where the font kerned the character with its
neighbors, the two kerning values also keep the character in place
when it is only raised, scaled or rotated. The characters after it
stay where they were, or move by the change in its width when it is
scaled. The first character of a line and a right-to-left character
move only up and down. The type stays editable.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md.
2026-10-17 15:14:42 +07:00
dotCooCoo 0ed83870a4 Place: RTF files place as area type with their formatting
File > Place read text files only as plain `.txt`, and an RTF file
could not be placed. The reference app lists RTF among the formats it
places as text, keeping the file's font and style settings.

File > Place now reads RTF files, written from the Rich Text Format
Specification 1.9.1, and sets their text as area type the way a text
file is placed. Each run keeps its font from the font table, its size,
bold and italic, underline, strikethrough, all caps, small caps,
superscript or subscript (in Document Setup's proportions), baseline
shift and its color from the color table. Each paragraph keeps its
alignment, left, right and first-line indents, space before and after,
and direction. Table cells are separated by tabs and each table row is
a paragraph. Unicode characters, Windows-1252 and Mac Roman bytes and
the special-character control words are read, and of a \upr group's
ANSI and Unicode text, the Unicode text is read. Hidden text, pictures,
objects, headers, footers, notes, field instructions and other
destinations are left out, and unknown control words are skipped.

A file is read as RTF when it starts with {\rtf, whatever its name,
except a .txt file, which stays plain text. RTF files share the 4 MiB
limit of text files. Groups nested more than 512 deep share the
formatting of the deepest one read, the font and color tables keep at
most 4,096 entries, and a file with more than 100,000 paragraphs or
runs is refused. The Place dialog lists RTF files, and file.place,
file.place.info and file.place.queue report the format `rtf`.

The tests in tests_textimport.rs fail without the change, and
text_import_fuzz.rs places random RTF token streams.

Docs: docs/file-format-parity.md, docs/mcp.md,
docs/target-app-parity.md.
2026-10-17 15:14:41 +07:00
dotCooCoo e7b78e7150 Brushes: Shift-click and Ctrl/Cmd-click select several brushes, and Delete Brush deletes them
A Shift-click or Ctrl/Cmd-click on a brush in the Brushes panel used to
apply the brush, as a plain click does. It now selects brushes without
applying one. Shift-click adds the brushes from the last brush clicked
without Shift (else the highlighted brush) to the clicked one, in the
order the panel shows them. Ctrl/Cmd-click adds the clicked brush to the
highlighted brushes or removes it. The panel highlights the selected
brushes, and the bottom bar's Delete Brush and the panel menu's
Duplicate Brush and Delete Brush act on all of them. A plain click
works as before and ends the multi-selection, and so does selecting
other art, another document or another current brush.

brush.delete and brush.duplicate also take {names: [name...]}. Each
call is one undo step, and an unknown name fails before anything
changes. brush.duplicate answers {names} for a names list.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 15:14:41 +07:00
dotCooCoo 3f81c70ade .ai import: type keeps its own opacity and blend mode
A .ai or EPS opened from its editing data lost the opacity and blend
mode of its text objects. text_slot made the slot without the graphics
state's opacity and blend mode, which paths, meshes and images take, so
an Xy before a text object was lost. An Xy after it reached the slot,
but fill_slots then put type made from the text document in the slot's
place with only its visibility and name, and style_marker dropped the
transparency that a text object with an appearance carries on its look.

text_slot now takes the state's opacity and blend mode. fill_slots
gives made type the slot's opacity, blend mode, isolation and knockout.
The page's type gets them too, in place of its own transparency when
that is none, the slot's, or the slot's combined with that of the
objects around it; otherwise the page's type and the strokes around it
keep their own (a fill's, for example). When the type has transparency
and comes with other art (the strokes the page draws around it, or page
type that goes with it), a group of them takes the transparency. A text
object with an appearance takes the transparency from its look; where
the look draws more than the type and has transparency, the look and
the type go into a group that has it, and that group is not taken as
the look of a later object. The import fuzz's .ai tokens now include a
commented text object, 1 /FreeUndo , and a group's state marker
(6 () XW).

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 14:46:20 +07:00
dotCooCoo f3072fc748 Paths: Shift keeps a reshaped segment's handles perpendicular and equal in length
Shift pressed while dragging a segment had no effect on the curve:
Direct Selection's curved-segment and spine-segment drags, the Anchor
Point tool and the Pen with Alt sent the pointer offset alone, and the
segment bent freely. The reference app keeps the handles in the
perpendicular direction and of equal length while Shift is held.

path.reshapeSegment and object.blend.spine.reshapeSegment take
perpendicular (default false, which keeps today's behavior). With it,
both of the segment's handles stand at right angles to the line between
its anchors, on the same side and at the same length, and the point at
t lies as far from that line as the moved point does. A smooth anchor
at either end stays smooth, as it does in a drag without perpendicular,
and a segment whose anchors coincide bends as it does without
perpendicular. With Direct Selection, Shift pressed once a curved
segment's drag has started sends perpendicular; Shift held at the press
still toggles the path's selection, as before. Direct Selection's
spine-segment drag and the Anchor Point tool's segment drag send Shift
as perpendicular too, so the Pen with Alt does as well. A straight
segment dragged with Direct Selection already moves at 45° steps with
Shift.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 14:46:20 +07:00
dotCooCoo 08dc614938 File formats: PostScript (.ps) and .epsf files open and place
The EPS reader read PostScript by any name, but the Open and Place
dialogs hid .ps and .epsf files, and a .ps or .epsf file whose header
is only %! could not be opened or placed ("not a format VectorCraft
reads"). The reference app lists .eps, .epsf and .ps under Open and
Place.

- A read-only PostScript format (ps) joins the format table. A .ps
  file opens and places through the EPS reader and reports the
  ps format. document.export does not write .ps; file.print does.
- .epsf is an EPS extension.
- OPEN_EXTS and PLACE_EXTS list epsf and ps, so the dialogs, the CLI
  help and the MCP open_file description list them.
  UNASSOCIATED_EXTS lists them too: the macOS bundle declares neither.
- A .ps or .epsf file whose header is only %! now pastes as EPS when
  copied in a file manager, as one with a %!PS header already did, and
  clipboard.importEps reads PostScript whose header is only %!, which
  it rejected before.

Docs: docs/mcp.md, docs/file-format-parity.md (.ps row),
docs/target-app-parity.md.
2026-10-17 14:43:14 +07:00
dotCooCoo b275eb04d0 Guides: a Cmd/Ctrl+Shift double-click releases the guide under the pointer
A double-click on a ruler guide with the Selection, Direct Selection or
Group Selection tool did nothing, whatever keys were held. A guide could
be released only with View › Guides › Release Guides.

With the Selection, Direct Selection or Group Selection tool, a
double-click with Cmd/Ctrl and Shift held now runs view.guides.release
on the ruler guide under the pointer (index) or, when no ruler guide
is there, on the guide path under it (ids). Nothing is released while
guides are hidden or locked, and a double-click without both keys does
what it did before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 14:43:13 +07:00
dotCooCoo 4f1e4979fc Layers: the panel menu's Save Selection submenu saves, lists and updates saved selections
The Layers panel menu had no saved-selection items, and the Select
menu was the only menu that listed saved selections. The reference
app's help also names a Save Selection menu in the Layers panel that
lists the saved selections and holds Save Selection, Update Selection
and Edit Selection.

The Layers panel menu now has a Save Selection submenu after Locate
Object. It holds Save Selection…, Update Selection and Edit Selection…,
which are enabled and run as in the Select menu, and below them the
active document's saved selections. Choosing a name runs select.recall
with that name, as the Select menu's list does.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 14:43:13 +07:00
dotCooCoo cdb0bbe315 Artboards: Duplicate Artboards, Copy and Cut take every selected artboard
With several artboards selected, the Artboards panel menu's Duplicate
Artboards copied only the active one, and Copy and Cut with the
Artboard tool took only the tool's active artboard.

artboard.duplicate, artboard.copy and artboard.cut now take `indices`,
and without `index` or `indices` they act on the artboards the
Artboard tool has selected. Duplicate Artboards passes the selected
artboards and selects the copies. Duplicate and Cut are one undo step
each, and Paste adds every copied artboard back, with its art, in one
undo step. The copies keep their places relative to each other,
together right of the last artboard. A cut fails when it names every
artboard.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 13:52:06 +07:00
dotCooCoo 14de645441 Templates: Save as Template always writes a file with a template extension
file.saveAsTemplate kept a name whose extension named another format
and wrote the template into it: {path: "card.png"} wrote .vctemplate
JSON to card.png, and card.pdf typed in the desktop save panel got it
too. file.saveAs {format: "ait"} answered that document.export writes
other formats, but document.export does not write .ait either.

file.saveAsTemplate now replaces another format's extension (card.png
is saved as card.vctemplate, or card.ait for format: "ait") and adds
the extension to a name whose extension names no format. The desktop
panel adds the template's extension to any name that does not end in
a template extension (card.pdf.vctemplate), as Save As adds its own.
Save and Save As still refuse ait, and the error now names
file.saveAsTemplate.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 13:52:06 +07:00
dotCooCoo b9c3dd4519 Effects: effect.apply and effect.setParams refuse a color the effect can't draw
effect.apply and effect.setParams stored any string as the color of
Drop Shadow, Inner Glow, Outer Glow and Shift to Color. When the
effect could not read it, it drew its default color with no message,
so a typo such as #ff00 in the Drop Shadow dialog turned the shadow
black.

The commands now refuse such a color before they edit, with
"`color` must be a #rrggbb color", and the document and its undo
history stay as they were. Hex colors, the five named colors,
[r, g, b] and color objects still pass. The check runs in the
commands, so the app, command.batch steps and replayed journals get
it too. The effect dialog's OK shows the error and keeps the dialog
open, with Preview on or off. Saved documents open as before.

Docs: docs/mcp.md (effect params).
2026-10-17 13:52:06 +07:00