craft-docpath follows a stored path only on this computer or on the
share the document is on, but its check is lexical and cannot see
mapped drives. fileio::document_path therefore followed a link stored
as Y:\img\logo.png, where Y: is mapped to \\server\other, from any
document: opening the document, links.check, Link Info and the
linked-file watch read or stamped the file on that server.
On Windows, document_path now reads the final path of the root folder
of a link's drive (std::fs::canonicalize). Windows opens that root
folder on the drive's server to answer, so the check still contacts
the server once per lookup; no file on it is read, stamped or watched.
When the final path names a share, the link is checked against the
document's path on its share, and a link on another share is refused
as on another computer, as a link stored with that share's own path
is. Links on this computer's drives and on the document's share are
followed as before, and a link on the document's own drive is not
looked up. A document opened as \\?\Z:\... is looked up on its share
as Z:\... is. Other systems are unchanged.
Docs: docs/mcp.md, docs/target-app-parity.md.
A plug-in could filter objects or reshape them as a live effect, but
nothing it made could leave the app: pen plotters, cutting machines and
laser cutters read formats VectorCraft doesn't write. An exporter is a
third kind of plug-in ("kind": "exporter", with the file "extensions"
it writes), in the same sandbox and with the same ABI v1 exports.
- It adds a write-only format, plugin.<id>. document.formats lists it
with its plug-in id, and document.export writes it by that id, or by
a path ending in one of its extensions that no built-in format has;
"params" carries its parameters, checked against its manifest as a
filter's are.
- File › Export › Export As lists it after the built-in formats, with
Use Artboards greyed out, since the plug-in writes the whole
document. One with parameters asks for them in a dialog generated
from its schema (pluginExport) once the file is picked.
- The plug-in is given the native file's JSON (format::json_part:
images of 8 KiB or more without their data) and _context {mode:
"export", extension, name}. It returns {"dataBase64", "warnings"?}
or {"error"}. The host writes the file, and nothing when the plug-in
declines or returns bad data.
- An export gets 10 G instructions plus 4,000 per byte and 300 s.
Background Export's Cancel stops it between two slices of that
budget (Error::Cancelled), and a cancelled live-effect run is not
cached as a failure.
- plugin.run and Object › Plug-ins say where an exporter is instead of
calling it a live effect.
engine/tests/plugin_export.rs reads back what an exporter is handed
with wat::echo_exporter, an exporter whose file is its input or its
parameters (its base64 encoder is written in WAT). The document it is
given is document.save's JSON, without the large image's data, and its
parameters arrive defaulted, clamped and with the file's name and
extension. It also covers listing and removal, built-in extensions
winning, selectedOnly, declines, bad base64, missing data, warnings,
and Cancel stopping a plug-in that never returns. It is a test binary
of its own: installed plug-ins are process-wide, and the format-list
unit tests check exact lists. dialogs/tests_plugin_export.rs drives
Export As headlessly; plugins/tests/abi.rs covers the budget, the
parameters and the echo exporter; format/tests/native_v4.rs covers
json_part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
craft-docpath follows a network path only on the share the document is
on, and its caller contract asks for a document opened from a mapped
drive to be passed by its UNC path. fileio::document_path passed the
drive path as given, so a document opened as Z:\jobs\doc.vectorcraft
treated a link stored as \\server\share\img\logo.png, on the share
behind Z:, as on another computer: the image was missing, and
document.open, links.check and links.info gave refused: "the path is on
another computer".
On Windows, when a stored path is refused as on another computer and
the document is on a drive, document_path now reads the final path of
the document's folder (std::fs::canonicalize). When that names a share
(\\?\UNC\server\share\...), the path is checked again against the
document's path on that share. Links on other computers stay refused,
and paths that were followed resolve as before. Opening a document,
Link Info, links.check and the linked-file watch all use it. Other
systems are unchanged.
Docs: docs/mcp.md, docs/target-app-parity.md.
document.pdfInfo refused PostScript (an EPS, a .ps file, or a .ai in
PostScript form), mostly with "this is a PostScript file ..., not a
PDF", although document.open and file.place read the same file
through the EPS reader as one page.
It now answers {pages: 1, needsPassword: false, pageInfo: [{width,
height, rotation: 0, boxes: {}}]}. The page is the file's bounding box
(%%HiResBoundingBox when it has one, a US Letter page without one),
read from its comments without running its program. thumbnail: 1 is a
PNG of what that page prints, read by the EPS reader without any
editing data the file carries. A program whose header is only %! is
read as PostScript, as clipboard.importEps reads it.
Docs: docs/mcp.md, docs/target-app-parity.md.
While an export runs in the background (Background Export), a dialog shows
what is being exported and how far it has got: artboard by artboard for a
raster export of several artboards, busy otherwise. Cancel (also Esc) stops
it and writes no file; Continue in Background leaves it to the status bar.
It opens after 0.4 s and never over another dialog.
Built on the shared vectorcraft_doc::cancel (#2546): fileio::progress::Progress
wraps its CancelToken and a progress sink, so a cancelled export stops
drawing and keeps no half-drawn pattern tile or effect raster.
- ui: background jobs carry their Progress; export_progress draws the
dialog; UI commands file.cancelExport and file.exportInBackground.
- i18n: the strings in every language with complete menus, Danish, Dutch
and Indonesian included; "{n} more exports waiting" is a plural (trn).
- docs: ui-parity, gaps (G12) and ROADMAP rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dt49z1ubK9x1bPq51APgz3
The Export for Screens dialog built its Example line from its fields as
typed, so it often showed a name the export does not write. It used the
artboard's name with its spaces (Artboard 1.png where the export writes
Artboard-1.png) and the first artboard whatever the range, kept a
prefix, a suffix or a device name such as CON as typed, used the scale
text as the row folder instead of the row's own folder or its size,
kept a vector row's @2x suffix, left out the folder that Create
Sub-folders adds, and read a counter set as text as 1.
The line now comes from the export's own naming. The naming part of
document.exportForScreens moves into a Naming type that the export
uses for every file, and fileio::screen_first_file returns the path of
the first file the export writes for the given params, without encoding
or writing anything. The dialog passes it the params it exports with,
without the destination, so the line shows before a folder is chosen
and is left out while the settings choose nothing to export. The Start
at field shows the counter the export uses when it was set as text.
Docs: docs/target-app-parity.md.
document.export, document.serialize, document.exportSelection and
Export for Screens rows answered "PDF-compatible .ait template files
can be opened but not written" for a .ait, although
file.saveAsTemplate writes them. The CLI's convert and run --export
call document.export, so vectorcraft-cli convert in.pdf out.ait
failed with that error.
The error now reads "PDF-compatible .ait template files are written
by file.saveAsTemplate", with a row in the 17 catalogs that translate
status messages and in bg, da, id and nl. convert and run --export
write a .ait with file.saveAsTemplate, which writes every artboard
with the .ai options. convert passes --replace-lossy and its .ai flags
on, and refuses --artboard and --range for a .ait.
Docs: docs/mcp.md, docs/target-app-parity.md.
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.
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.
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.
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.
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.
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.
Batch over data sets names each output file after the document and the
data set, as the reference app's File + Data Set Name option does. It
joined that name onto the destination folder as it was, and a data set
name can hold any character. A data set named a/b failed to save, and
one named a/../../x wrote its file outside the destination folder. On
Windows, one named c\d also failed to save, and one named e:f left an
empty file.
The data set's name now goes through file_part, which Export for
Screens uses for its file name prefix and a format row's suffix, so
output file names have - in place of /, \ and : from a data set's name.
When two data sets then give the same name, compared without case, the
later ones get -2, -3 and so on, as artboard file names do, through a
distinct_names helper split out of unique_file_names. This covers the
folder destination and Override Save/Export.
Docs: docs/mcp.md, docs/target-app-parity.md.
The MCP server read one line, ran it and answered it before reading the next,
so notifications/cancelled was never seen during a call and every request
(even ping) waited behind a running export.
- vectorcraft_doc::cancel: a thread-local cancel token and progress sink that
long work polls. Checkpoints: PDF pages, PDF flattening, geometry bake,
raster-effect flattening, drawing objects, raster artboards, and before any
file is written. A cancelled command returns EngineError::Cancelled and
changes nothing.
- Server::serve reads on its own thread: notifications/cancelled drops a
queued request or stops the running one (no reply, as the MCP spec says),
engine-free requests (ping, the list methods, prompts/get) are answered at
once, a request that would wait behind a call running for 2 s is refused
with -32003 naming that call, and _meta.progressToken gets
notifications/progress (page n of m). Queue and output are bounded.
- The remote backend stops waiting for the app when the call is cancelled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paths stored with linked images and placed documents, and the hrefs of
SVG images, go through fileio::document_path (craft-docpath): a relative
path is taken from the document's folder, and only local files or files
on the document's own network share are looked at. Opening a document,
Link Info, Package, the linked-file watch and SVG import all use it; the
watch stats files through fileio, so automation roots apply to it too.
A path that is not followed counts as missing; document.open,
links.check and links.info say why in `refused`.
Issue #1935 asks for batch exports of many logo variations in many
formats, each format in its own folder. This builds on Export for
Screens, which already writes every chosen artboard in every format row.
Names: a file name pattern (`name`) names every file alike. Tokens:
<filename> (the document), <name> (the artboard, asset, layer or
object), <index>, a run of # (a counter, zero-padded to the run's
length, from counterStart; counterPer item or file), YYYYMMDD,
YYYY-MM-DD, <date>, <time>, <YYYY>, <MM>, <DD>, <format>, <scale> and
<suffix>. A trailing .ext is dropped. Words are not changed: only the
date patterns and the bracketed tokens are. `date` sets the day (the
default is today, UTC). Different files that a pattern names alike get
-2, -3... Without a pattern, two rows with one name still write the
first row only, as before.
Folders: `folders` puts all files in one folder, a folder per row (as
Create Sub-folders did), per extension, per item, or per item and then
extension.
What is exported: besides artboards, assets and the full document,
`layers` (each layer's art alone, sublayers included) and `selection`
(one file of the selection, or a file per object).
Formats: rows can also be EPS, AI and VectorCraft (a document of the
item alone, its one artboard). A PDF row with printer's marks (PDF
Print in the dialog: trim, registration, colour bars, page information,
suffix -print) sits next to a plain PDF row.
Presets: document.exportPresets.list, .save and .delete keep batch
export presets with the preferences, and `exportPreset` uses one under
the call's own params. Built-in: Logo Package (SVG, PNG 1x and 2x, PDF
and print PDF, a folder per format) and Print-Ready PDF.
Dialog: Layers and Selection tabs with thumbnails; an Export Preset
menu with Save and Delete; Folders; File Name with Insert for the
tokens, Start at, Prefix, and an example of the first file's name. The
OK button says Export Layer or Export Selection on those tabs.
Tests: the pattern tokens, the request's example (SVG, PNG, PDF and
print PDF for artboards 1-3, a folder per extension, the print PDF a
bigger page), folders by item and both, the counter by item and by
file, numbered duplicates, layers and the selection, EPS, AI and
VectorCraft per artboard, presets (built-in, saved, used, replaced,
deleted, kept with the preferences), bad params, and the dialog (every
tab, the example name, a pattern export into folders, the Layers and
Selection tabs, presets applied and saved). The three tests that
refused EPS and VectorCraft rows now check those files. New labels
are translated in all 19 catalogs.
Effect › Distort › Glass… offered only its four built-in textures. The
reference app's Texture menu also has Load Texture, which takes an
image file as the glass surface.
The Glass dialog now has a Load Texture… button. It reads a PNG, JPEG,
GIF, WebP, TIFF, BMP, PSD or PSB file within the pixel allowance the
renderer draws images in, and the Texture menu gains Loaded Texture.
The effect stores the image's gray levels in `textureImage` as a
grayscale PNG in base64, so the document saves the texture with the
effect, and copies of the effect carry it. effect.apply and
effect.setParams take `textureImage` as {path}, {name, dataBase64} or
the stored string; any other value is an error.
The texture lies one image pixel per point at Scaling 100%, centered on
the object and repeated. Each pixel's luma is the surface's height,
from 0 for black to 1 pt for white, and a transparent pixel counts as
middle gray. A Glass without `textureImage` draws and saves as before.
Docs: docs/mcp.md (Glass textures), docs/effects-parity.md,
docs/gaps.md, docs/target-app-parity.md.
Play an action on every file in a folder or every data set, with destination
and Override Open/Save/Export. Actions panel and File › Automate › Batch…
open the dialog; new strings in pt-br.
Co-authored-by: Cursor <cursoragent@cursor.com>
Object › Artboards › Fit to Artwork Bounds and Fit to Selected Art ran
artboard.fitToArt and artboard.fitToSelection without an index, so
they resized artboard 1 whichever artboard was active. The Artboards
panel menu's Fit to Artwork Bounds already passed the active artboard.
The app now passes its active artboard as the index of the two
commands when a call names none, as it passes the artboard to Align to
Artboard, All on Active Artboard and Paste in Place, in Front and in
Back. That covers the menu items, keyboard shortcuts, the command
palette and MCP's run_command in remote mode. A batch passes its
artboard as the index to their steps that name none, and a recorded
action leaves the index out, so an action resizes the artboard that is
active when it plays. The params texts of the two commands and of
command.batch describe where the index comes from.
Docs: docs/mcp.md, docs/target-app-parity.md.
File › Save as Template always wrote a .vctemplate. The save panel
offered only that type, file.saveAsTemplate ignored its format param,
and a name typed with .ait got .vctemplate added. New from Template
already opened a .ait as an untitled document.
Save as Template now also writes a .ait: the PDF-compatible file Save
As writes for .ai, carrying the document. The desktop save panel lists
VectorCraft Template first and the .ait type beside it, and the web
build's Save as Template dialog gets a Format menu with the same two
types. file.saveAsTemplate writes .ait for format: "ait", or for a path
ending in .ait when no format is given, and takes the .ai options;
file.formatOptions answers for ait. .vctemplate stays the default.
Exports and Export As still do not write .ait.
Docs: docs/mcp.md, docs/file-format-parity.md (.ait row),
docs/target-app-parity.md.
A PDF page's UserUnit (PDF 1.6) sets the size of its user space unit
in multiples of 1/72 inch. The PDF reader has applied it since #1894, so
a page with /UserUnit 10 and a 36 × 50.4 media box opens at 360 × 504
pt. A .ai whose editing data has its artboards in the page's own units
then no longer matched its PDF part. It opened from the PDF part,
without its layers, its hidden objects and the art outside its
artboards, and with its artboards in a row instead of where the
editing data puts them. document.pdfInfo gave such a page's size in
points and its boxes in the file's numbers.
Editing data whose first artboard has the page's size in the page's
units, and not in points, is now compared with the PDF part scaled
down by the UserUnit. The document read from it is then scaled up by
the UserUnit, with its stroke weights, dashes, effects and pattern
tiles. When the layers can't be read, the artboard layout taken from
the editing data is scaled the same way. document.pdfInfo gives the
boxes in points, as it gives the size.
Docs: docs/mcp.md, docs/target-app-parity.md.
document.export, document.serialize and document.exportSelection write
a PDF-compatible .ai (format: "ai"), and document.formats lists ai as
writable, but none of their params texts named it. The params text of
document.exportSelection named only png, jpg, webp, svg, svgz and pdf,
although the command writes all 20 writable formats. The description
of the MCP export tool and its row in docs/mcp.md named every format
of the tool's format enum except ai, and the range text and the row
said that only PDF writes several artboards.
The three params texts now list every format their command writes,
and document.export's range and useArtboards texts name .ai beside
PDF. The MCP export tool's description names ai, and its artboard and
range texts say that .ai, like PDF, writes one page per artboard. The
range text and the row also say that SVG, SVGZ and EPS write one file
per chosen artboard, and that rasters, DXF, EMF and WMF do so with
options.useArtboards: true.
Docs: docs/mcp.md (the export tool).
The document.exportForScreens params text listed png, png8, jpg, webp,
gif, svg, svgz and pdf for a row's format, and its settings keys left
out svgz. A row of another format VectorCraft writes, such as eps, was
refused with "Export for Screens writes png, png8, jpg, webp, gif, svg,
svgz or pdf". Rows of tiff, bmp, tga and psd are written too, and the
Export for Screens dialog offers TIFF rows.
The params text, its settings keys and the error now list the 12
formats a row writes: png, png8, jpg, webp, gif, tiff, bmp, tga, psd,
svg, svgz and pdf. Which formats a row accepts does not change.
Export Selection, Asset Export, the Assets tab of Export for Screens
and the rasterized art of CSS Properties cropped each object to its
visual bounds, which leave out live effects. A path with an 8 pt drop
shadow was exported with the shadow cut off at the path's edge, and
a group's own shadow was cut off the same way.
The crop is now the bounds the renderer measures for what it draws
(`vectorcraft_render::effect_bounds`): `painted_bounds` without its
room for brush art. A brushed path is cropped to its visual bounds as
before, and a Shaper group leaves out its transparent source art as
its visual bounds do. `assets.list` reports the same crop. The
renderer measures a shadow's or glow's reach on every side of the
object, so the image also has transparent room on the side away from
an offset shadow.
Docs: docs/mcp.md, docs/target-app-parity.md.
With Align to Artboard chosen, the Align panel's six Distribute Objects
buttons and its two Distribute Spacing buttons acted on the selection.
object.distribute and object.distributeSpacing took no to or artboard
param. Distribute Objects kept the outermost objects where they were
and did nothing with two objects, and Distribute Spacing kept the first
object's left or top edge and the last object's right or bottom edge
where they were.
Both commands now take to ("selection", "artboard" or "key") and
artboard, and the panel sends its Align To choice as to. When to is
"artboard", the first object's left or top edge goes to the artboard's
left or top edge, and the last object's right or bottom edge goes to
the artboard's right or bottom edge. Distribute Objects spaces the
other objects evenly by its button's line, and two objects are enough.
Distribute Spacing without a spacing value makes the gaps between the
objects equal. Both commands use the same artboard as object.align.
The app passes the active artboard to both commands, and recorded
actions leave it out, so an action plays on the artboard that is
active when it plays. With "selection" or "key", or without to, both
commands work as before. Distribute Spacing with a spacing value still
keeps the key object in place with each of the three to values. With
anchor points selected, both commands still space the points among
themselves and do not use to.
Docs: docs/mcp.md, docs/target-app-parity.md.
aiVersion 7 saves through the legacy writer, one file per artboard; range picks the artboards
and document.save writes them again. Appearances are expanded first. Transparent images
(see-through pixels, opacity or a blending mode) are flattened with the art under them into
opaque images clipped to where they change it (transparentImages "flatten", the default), or
left out ("omit"); both warn.
A shortcut set another app saved as slash-name text (a .kys file whose
text starts with a slash name and "{", such as "/Menus {") opens in
Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts ›
Import… in the desktop app, or from a drop on the window, whether or
not a document is open. The dialog proposes a target for an entry when
an earlier import kept one for that name, or else when one tool,
command or panel matches the name clearly by its id or by one of its
labels. It decodes the file's key and modifier codes from the codes
earlier imports kept, from the example in the AiCommandPalette README
(/Key 48 for the 0 key, with /Modifiers 0 taken as no modifiers), and
from VectorCraft's default shortcuts where at least two entries vote and
four votes in five agree. The user picks the remaining targets, presses
the key for each unknown key code or marks it No equivalent, and chooses
the modifiers for each unknown modifier code. Apply assigns the ready
rows with the Keyboard Shortcuts rules: a tool takes a single key,
optionally with Shift. It goes through the rows from the last one back
and leaves a row out when a later row it applies has the same shortcut
or the same tool, command or panel. Keyboard Shortcuts then shows the
result as unsaved changes until the user clicks OK. Copy Report copies
the set's name, the rows Apply leaves out and the parts of the file that
weren't read.
- shortcutSet.read is a query command: it reads a set and does not
modify the document or the preferences. It returns each entry with
its section, codes and line, and up to 1,000 skipped tokens, each cut
to 80 characters. It reads at most 4 MiB of text and 20,000 entries,
skips blocks nested more than 16 deep and cuts names at 1,024 bytes.
With a path it reads at most 4 MiB and 4 bytes of the file, and with
--automation-read-root (#832) the path must lie inside the read root.
A file whose text starts with "<", such as an XML shortcut file saved
as UTF-8, fails with "This shortcut file is in a form VectorCraft
can't read."
- importMatches.list, importMatches.set and importMatches.reset read
and change the matches kept with the preferences; resetting the
preferences keeps them. With Remember My Choices on (the default),
Apply keeps the targets and codes the user chose, and for the rows it
assigns, it also keeps the matched targets and the codes decoded by
agreement. A later import proposes them.
- The name matcher compares a name with ids by exact match and shared
words. It compares the name with labels, in English and in the
interface language, by exact match, shared words and character
pairs. It proposes a match only when one tool, command or panel
scores at least 0.6 and at least 0.1 more than the next one.
- When Import Shortcut Set is opened from Keyboard Shortcuts, Cancel,
Escape and ui.dialog.cancel close it and return to Keyboard Shortcuts
as it was (dialogs::PARENT). A document's queued Missing Fonts dialog
(#792) opens after Keyboard Shortcuts closes.
- File › Open has a "Shortcut sets" filter, and in the desktop app
Keyboard Shortcuts' Import… shows .json and .kys files.
Docs: docs/mcp.md, docs/control-protocol.md, docs/development.md,
docs/target-app-parity.md, docs/roadmap.md.
VectorCraft does not run scripts written for the reference app, and
File › Scripts held only the disabled Other Script…. File › Scripts ›
Check Script… picks a .jsx, .jsxinc or .jsxbin file, reads it without
running any of it, and shows a report: what the script needs (the
script language's own objects, the document model, files, menu
commands, dialogs, or features only the reference app has), what it
would do outside the document, the menu-command strings it passes, the
document-model member chains and type names it uses, its directives,
the files it includes, and the problems that would stop it loading.
Copy Report copies the report as JSON. File › Open and drag-and-drop
show the report for those files too.
- vectorcraft-scriptcheck, a new Standalone crate with no
dependencies, holds the checker: a lexer for ECMAScript 3 with E4X
literals and the #name and //@name directive lines, and an analysis
of the tokens that follows variables, function scopes and the files
a script includes. It never recurses: open brackets, the levels of
an XML literal and the function bodies being read are kept on stacks
capped at 4,096. It reads at most 8 MiB of a file and 32 MiB with the
included files, follows 64 included files 16 deep, looks up at most
1,024 paths for them, keeps 1,000 entries in a list, tracks 1,000
variables holding member chains and 1,000 holding File objects, and
cuts echoed text to 120 characters. A compiled script is reported as
compiled and isn't read.
- script.check (engine, a query) takes path, data or dataBase64. With a
path and includes: true, it reads the files the #include lines name
from inside the script's folder, through the automation read root,
and when the script or one of those files assigns a member chain or a
File object to a variable, the check treats the variable as holding
it in every file. It doesn't follow absolute names, names, folders or
links that lead out of the folder, names or folders longer than 4,096
bytes, or JSINCLUDE, and it reads only regular files.
- ui.checkScript shows the report (dialog scriptCheck). File › Open
lists Scripts (.jsx, .jsxinc, .jsxbin) and sends a script's path to
script.check, which reads at most 8 MiB of it.
The language rules come from ECMA-262 3rd edition and ECMA-357 2nd
edition. The directive, platform and document-model root names come
from the public JavaScript Tools Guide and scripting guide pages that
catalog.rs cites by URL, and executeMenuCommand, doScript, loadAction,
unloadAction and selectTool come from the public forum posts cited
there; only names are used. Every script in the tests is written from
scratch with invented names.
Docs: docs/mcp.md, docs/target-app-parity.md.
An image whose decode needs more than the 2 GiB allowance (512 MB on the web) is not drawn.
A raster export says so since #1517; on screen it was still blank with no word. Open and
Place now carry a warning for it, and the Links panel's row a note (links.list: note).
Trim decoded the image with the decoder's default 512 MB allocation limit, so
a 300 ppi roll (6614 x 59055 px, 1.56 GB as RGBA) that opens fine failed with
"the image's pixels can't be read". It now decodes with the allowance opening
already has (MAX_RASTER_ALLOC: 2 GB, 512 MB on the web). A CMYK image, kept as
its ink amounts, has no alpha: trim leaves it alone (trimmed: false) instead
of failing to decode it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PDF-compatible .ai opens on its trim boxes, its editor's artboards,
with the bleed boxes round them as the document's bleed (#1349). A PDF
saved with its editing data (#1345) keeps that data under
AIPDFPrivateData keys, which has_illustrator_data didn't look for, so
such a PDF with a bleed still opened on its crop box: the artboard took
the bleed in and the editing data was refused as being for a different
page. has_illustrator_data now finds either key. has_private_data, which
also decides how a page is clipped, is unchanged.
Test: a_file_with_a_bleed_opens_on_its_artboard_with_the_bleed_round_it
(the .ai case, and the same hand-written file with AIPDFPrivateData).
A PDF saved with the reference app's editing data keeps that data in
streams named AIPDFPrivateData1, AIPDFPrivateData2, ..., where a .ai
file uses AIPrivateData1, AIPrivateData2, .... illustrator_data read
only the .ai names, so such a PDF opened like any other PDF: plain
groups opened ungrouped, and hidden objects and guides were missing. The
import gave no warning that the PDF's editing data went unread.
illustrator_data now reads the AIPDFPrivateData streams when the file
has no AIPrivateData1, and the PDF opens from its editing data as a .ai
file does. editingData: false still opens it like any other PDF.
has_private_data still looks for AIPrivateData keys only.
layered_ai also returns whether it read the layers. When it did, art
reaching past a PDF's page opens whole, and through_editing_data drops
the PDF importer's note that such art was clipped (PAST_PAGE_NOTE, now
public) as it drops the off-artboard note. When it didn't, both notes
stay. On main, a .ai whose editing data can't be decoded or has no
layers opened from its PDF part without the off-artboard note.
testkit::ai::pdf writes the .ai file of testkit::ai::ai with the PDF
key names. The editing-data tests open it as a PDF, and the import fuzz
test opens the damaged sample as a PDF too.
Docs: the document.open params, docs/mcp.md,
docs/file-format-parity.md, docs/gaps.md (G9: the export direction of
#868 remains), one evidence line in docs/target-app-parity.md, and #868
moved from layer structure to editing data in the File formats row of
docs/target-app-parity.md and in docs/roadmap.md.
A PDF-compatible .ai with a bleed writes its PDF part's page as the
artboard grown by the bleed: the crop box takes the bleed in, the trim
box is the artboard and the bleed box the bleed. The importer made the
crop box the artboard, so the bleed opened as more white page with no
bleed guide; and since the editing data's artboard no longer matched
that page, its layers weren't read either ("its editing data is for a
different page").
Opening a PDF-compatible .ai with the crop box (the default) now takes
its trim boxes as the artboards. Any import on the trim boxes (Crop To
› Trim, for a PDF too) sets the document's bleed to how far the bleed
boxes reach past them on each side (ISO 32000-1, 14.11.2), kept to
0–72 pt, and the document read from the editing data keeps it. Art in
the bleed shows outside the artboard, within the bleed guide. Plain
PDFs still open on their crop box by default, and Place is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save serializes JSON straight into the compressor and writes images from their Arc into the
file; open reads each image into its own buffer, and NodeKind reads its tag first instead of
buffering every node. Already-compressed images are stored rather than deflated again in the
same gzip stream. Limits apply per section, so any file VectorCraft writes opens again. Data
Recovery writes each image once, content-addressed, instead of every interval. Native files open
on a background thread. Files stay readable by main both ways; JSON-only saves are identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
Find in Folder… in Linked File Not Found and in the Links panel menu
looks in a folder the user picks, and in its subfolders, for the files
with the names of missing linked files, in any letter case. In the
Links panel menu it looks for the selected linked files when there are
any. It uses the folder search of the Missing Fonts dialog. Find
Linked Files lists each file it looks for with the files found for it.
A file with the size and contents saved with the link comes first. A
file of the saved size comes next when the link saved no hash or the
file is larger than 1 GB, and any other file of the name comes last.
Within each group, a file named in the same letter case comes first.
Then each file's folder names are compared with the saved path's, from
the innermost outward, up to the first one that differs, and a file
with more matching names comes before one with fewer. A file is chosen
when it is the only file found, or the only one with the saved
contents, and the user can choose another file or Don't Relink.
Relink relinks each file chosen, and each relink is one undo step.
With Copy into the document's folder, which shows for a saved
document, each image's file is copied next to the document first. The
file's name, then Name 2.png, Name 3.png and so on, are tried in turn
until one is free, which gets the copy, or is a file with the same
contents, which is used. A placed document is relinked where its file
is. The status line names the first file that fails to relink, and the
other files still relink. When Find Linked Files opens from Linked
File Not Found, the missing files left without a choice, and any that
fail to relink, go back to that dialog. On the web, Find in Folder… is
disabled in the Links panel menu, and links.findFiles can't start a
search.
- links.findFiles searches on separate threads and returns at once;
links.findFiles {} reports the search, and {stop: true} stops it. It
looks for at most 1,000 linked files at once: the active document's
missing ones, or those of the objects it is given. It hashes a file
only when a link of its name saved the file's size and a hash, and
only when the file is 1 GB or smaller. It ends once every linked
file has its same file, and it searches only the folders
text.findFontFiles may search, with the same maxSeconds, depth and
count limits.
- links.relink {copy} copies the file it is given into the saved
document's folder and links images to the copy; a call that includes
a placed document fails. Each name it writes is checked against the
automation write root. text.addFontFiles and links.relink {copy}
make their copies with fileio::copy_new, which never replaces a
file. The text.addFontFiles params text and docs/mcp.md say which
names are tried for a copy.
- vectorcraft-doc adds links::hash_reader, which hashes what a reader
reads as hash_bytes hashes bytes.
- ui.findLinksInFolder is the Links panel's Find in Folder… for agents;
docs/control-protocol.md lists the dialog's fields.
Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md.
An agent driving VectorCraft over the confined MCP server had no way to
see which files it was given, so it guessed names. `file.list {path}`
lists a folder's files (with their sizes) and folders by name, held to
the automation read root like every read; links are listed as links and
not followed. At most 5000 entries, with `truncated` saying when there
were more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>