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>
Align to Artboard aligned the selection to the artboard under its
center, or to the first artboard when no artboard was under the
center. A press with a selection tool inside an artboard makes that
artboard the active one (#693). With the Selection tool, a
Shift-click on an empty spot inside an artboard also makes it active
and keeps the selection. Align to Artboard did not use the active
artboard.
object.align takes artboard, a 0-based artboard index, and uses it
when its to param is "artboard". The app passes its active artboard,
the one the Artboards panel highlights, to each object.align it runs
that names none, as it does for Paste in Place, Paste in Front, Paste
in Back (#693) and Select › All on Active Artboard (#1006). MCP calls
in remote mode also get it. Without artboard (headless MCP and the
CLI), or with an index the document doesn't have, object.align aligns
to the artboard under the center of the selection, or to the first
artboard when no artboard is under the center.
command.batch takes artboard too, and the app passes it the active
artboard. The batch passes it on to each step of these five commands
that names none, when the step runs. When the document a step runs in
has no artboard at that index (for example, after an earlier step
deleted the active artboard when it was the last one), the step gets
that document's last artboard. A step in a document that an earlier
step opened, created or switched to gets the same index, not that
document's own active artboard.
With Align to Selection, one selected object aligned from the Align
panel, the Control bar or the Properties panel is aligned to the
active artboard too, unless it is the key object.
Recorded actions leave out the artboard that the app passes to these
commands and to the batch. The Actions panel plays an action as a
batch. Its Align to Artboard, All on Active Artboard and in-place
paste steps that name no artboard use the artboard that is active
when it plays, and the default Center on Artboard action now centers
on the active artboard. In-place paste steps recorded since #693 and
before this change still paste onto the artboard that was active when
they were recorded.
Tests cover object.align with and without artboard on three artboards;
a Shift-click that makes an artboard active, after which Align to
Artboard aligns to that artboard but keeps an artboard the call names;
a batch that passes its artboard to each step when the step runs,
after a step deletes the last artboard and in a new document; recorded
actions that leave the artboard out; and recorded Align to Artboard
and Paste in Place steps and the default Center on Artboard action,
which play on the artboard that is active when they play.
Docs: docs/mcp.md (Align to Artboard), docs/target-app-parity.md.
Search: a search field at the top of the Libraries panel filters the
current library's colours, character and paragraph styles and graphics
by name, ignoring case; groups without a match are hidden, groups with
one open, and a search nothing matches says so ("No results").
Groups: items can be put in user-named groups, kept in the library file
(`groups: [{name, items: [{kind, item}]}]`, left out when there are
none, so files written before groups load as they are). New commands:
library.createGroup {library?, name?, items?}, library.renameGroup
{library?, group, name}, library.deleteGroup {library?, group} (its
items stay, ungrouped) and library.moveItem {library?, kind, item,
group?}. An item is in one group at most; deleting an item takes it out
of its group. The panel shows each group as a collapsible section with
its item count after the ungrouped items (rename and delete on its
context menu), each item's context menu has Move to Group (No Group,
the groups, New Group...), and a New Group button sits in the footer.
library.get lists the groups, library.list counts them. The generic
command dialog takes fixed params in `__params` for this.
Export / import: library.export {library?, path?} writes a library
(colours, styles, graphics with art and thumbnails, groups) as a
.vclibrary file, the JSON the library folder keeps (without a path:
{data}); library.import {path | data | dataBase64, name?} adds one as a
new library, made current, numbered on a name clash ("Brand (2)").
The panel menu has Import Library... and Export Library..., through the
app's file dialogs (the web downloads, and picks through File > Open,
which now lists Libraries files). Imported and folder files are made
safe: at most 10,000 items of a kind and 1,000 groups, names clipped and
made unique, sizes finite, thumbnails kept only when their PNG header
says at most 1024 px a side, group entries naming missing items or
unknown kinds dropped; a file over 512 MB or that isn't a library is an
error. Paths go through the engine's file helpers (automation roots).
The importer is fuzzed (import_fuzz: garbage and mutated libraries).
Also fixed: the third graphic added to a library got the second's id
(`graphic-2` twice).
Docs: the commands' params docs, docs/mcp.md, ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File › Place and File › Open read PSD and PSB files: the merged image
every such document carries after its layers, which is what a linked PSD
shows. A placed PSD links like any image, so the Links panel updates and
relinks it, and it is sized by its ResolutionInfo resource.
The reader is written from the public file format specification. It
reads Bitmap, Grayscale, Duotone, Indexed, RGB, CMYK (through the colour
settings' CMYK) and Lab, at 1, 8, 16 and 32 bits per channel (32-bit
values are linear), raw, PackBits or zipped with or without prediction.
A document with transparency stores its merged image on white with the
alpha in the first extra channel. It comes back with straight colours,
also for 16- and 32-bit files, which say so in their Lr16/Lr32 block.
Multichannel files are refused with a message. The file is untrusted:
every read is bounds-checked and the image's size is checked against
the decode budget before anything is allocated.
PSB is a read-only format of its own, so exports never write a PSD under
a .psb name. The desktop packaging still doesn't associate either
extension with VectorCraft (UNASSOCIATED_EXTS): they belong to raster
editors. Layers (import options) are left for later.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vectorcraft-cli mcp and vectorcraft --control take the flags PhotoCraft
takes. While roots are in force, every path a command reads (open, place,
relink, SVG image links, library and profile loads, plug-ins, preset
imports, font searches) must lie inside the read root and every path it
writes (save, export, package, Export for Screens and for Web, print to
file, captures, library saves) inside the write root. Paths are made
absolute, links are followed (a missing file's nearest folder's), and the
result is compared by whole components, ignoring case on Windows; `..`
below a missing folder, dangling links and device names are refused. A
root left out grants none of its access; without the flags nothing
changes.
The checks live in one place, vectorcraft_engine::file_access, consulted
by the engine's file helpers and the app's file services. The headless
server confines its whole process; the app confines control requests,
the frames carrying input they inject and the background saves they
start, leaving the person at the keyboard and the app's own folders
alone. The screenshot tool now has the backend write its file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Fit to Artwork Bounds fits the art exports draw: hidden objects and
layers, guides and template layers don't count, and the bounds stop at
a layer's clip.
- The comparison with the page catches an object one draws that the
other doesn't (pixels whose neighbours all differ, away from type), a
light colour against white, and, for an EPS whose page is its art's
box, art the layers print outside it.
- textAs: outlines opens a .ai whose type shows from its PDF part, which
has the outlines.
- document.open takes editingData: false to open an Illustrator EPS or
.ai as its page or PDF part only, for print pipelines.
Part of #637.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
swatch.library.copyToUser {library} copies a library loaded from
another folder into the user library folder of the desktop app. A
.vcswatches, .gpl or .ase file is copied as it is; the swatches of a
document and a library loaded from the data or dataBase64 parameter are
written as a .vcswatches file. The copy gets a name that no file in the
folder has, and when a file with the same extension and bytes is
already there, that file is used. The library is then listed as User
Defined only. Until the app quits, commands given its loaded id use the
copy, unless another library is loaded under that id. Without a user
library folder (the web and headless sessions) the command is disabled.
A file with the same extension and bytes as a User Defined library
opens as that User Defined library. The unchanged original of a copied
.ase file is one such file. Graphic style libraries open the same way.
The Libraries panel was a placeholder: it invited dragging art onto it,
but nothing happened. It now keeps local libraries (no account, no
cloud), as Illustrator's Libraries panel does:
- drag art off the canvas (or a swatch) onto the panel to add it, or use
+ to add the selection's graphic, fill or stroke colour, character
style or paragraph style;
- drag a graphic onto the canvas (or double-click it) to place a copy,
click a colour to paint the selection through the active Fill/Stroke
proxy, click a style to apply it to the selected type;
- the library picker and the panel menu create, rename and delete
libraries; each is a .vclibrary file in a Libraries folder next to the
preferences (session-only in the web app, headless sessions and runs
without preferences).
Engine: library.list/get/setCurrent/create/rename/delete/add/use/
removeItem. A graphic is stored as a native document with a thumbnail:
adding copies as Copy does and placing pastes as Paste does, resources
and swatch-name conflicts included. Using a style adds it to the
document and applies it in one undo step.
Clipboard::to_document gave its layer the id u64::MAX, which made a
saved copy overflow Document::fix_next_id when read back; the layer now
takes an id above the objects', and fix_next_id saturates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The layers reader of the editing copy (operators translated into
PostScript for the interpreter, any file with RGB colours, gradients,
transparency or images falling back to its page) is replaced by a reader
of the whole structure (import/ai): layers and sublayers with their
options, groups, compound paths, clipping groups and layer clipping,
names, hidden and locked objects, RGB, CMYK, grey, named and spot
colours, gradients, transparency, fill rule, overprint, embedded images
with their alpha channel, guides and every artboard, and objects drawn
as their look when they have several fills or strokes, effects or a
brush. Containers: Zstandard, zlib or plain data in EPS and .ai.
The text placement, the comparison with the page and the overwrite guard
stay around it. The page's type takes its text object's place without a
group; the comparison is by lightness, on nothing, without layers that
don't print. Symbols, pattern fills, placed files and unknown operators
make the file come in as its page where they show, and are left out of
layers that don't.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine refuses to write over the file a document was read from when
reading it left something out, unless acknowledgeLoss is passed. In the
app that surfaced as an error naming a parameter. File > Save, Save As,
Save a Copy, Save as Template and Export As now ask instead ("Replace
'a.eps', the file this document was opened from?", with what would be
lost), and OK repeats the command with acknowledgeLoss. Closing a
document whose save asks keeps it open, and so does Package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An Illustrator EPS is a flattened page plus, after %%EOF, an editing copy of
the document; a PDF-compatible .ai carries the same in its private data.
Importing only the page lost layer names, order, visibility, hidden layers
and hidden objects and hidden text.
- eps: read the editing copy (Zstandard %AI24_DataStream, older zlib
%AI9_DataStream) into layers, sublayers, groups, compound paths, clip
groups and hidden objects, run by the existing PostScript interpreter.
Falls back to the page, with a warning, when a shown layer uses an
operator that isn't read or the art differs from the page by over 5%.
- eps: read the text document (ate.rs) to place type on hidden layers and
objects; the page's type is matched to the file's text objects by
position (pieces of a line included) and kept with the nearest one when
none claims it. Area type and type on a path are left out with a note.
- pdf/engine: .ai files go through the same reader.
- engine/cli: a file whose import left things out isn't overwritten unless
asked (acknowledgeLoss, --replace-lossy).
- eps: font names re-encoded with a *<n> suffix resolve to their family.
- i18n: es/it/ru for the new import messages; ROADMAP updated.
Container and text-document formats were inferred from user-owned files; the
Adobe file format specification was not read.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
New from Template... and Save as Template opened their dialog in
~/Documents/VectorCraft Templates. On desktops whose documents folder has
another name (Italian ~/Documenti) or where the folder was never made, the
dialog started in a folder that doesn't exist and showed an error.
- The default Templates folder is now in the user's documents folder: on Linux
and the BSDs the one XDG_DOCUMENTS_DIR names in user-dirs.dirs, elsewhere
~/Documents.
- The folder a dialog starts in is made when missing; where it can't be, the
dialog starts in the nearest folder above it that exists, else home.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: ROADMAP (main's guides and file-format rows, with the Affinity
items kept); fileio/load.rs (main's physical-size raster documents, kept
visible to the Affinity preview fallback).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
Conflicts: README and ROADMAP (both sides kept: the French and Russian
interface with the Affinity sentence; the placed-documents bullet with
the Affinity one); place and swatch libraries (upstream's byte sniffing
and comments, with the preview-only refusal kept).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
An image opened as a document took 1 px = 1 pt whatever its pHYs, JFIF or
EXIF resolution said, so a 300 ppi print file opened at four times its
physical size. It now opens the way Place already sizes it (pt_per_px):
at the resolution it declares, 72 ppi when it declares none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.saveAs and document.save wrote a PDF-compatible .ai, but
document.export (and with it the CLI's run --export x.ai and convert
in.svg out.ai, and MCP's export) refused it as a format that can only be
opened, and --help didn't list it as writable.
The .ai encoder is now shared: save.rs's ai_params and encode_ai (a PDF
of every artboard with the PDF options, carrying the native document,
blank pages with pdfCompatible: false) serve Save As and the exports
alike. The .ai format is writable, so document.formats, --help and the
MCP export tool list it; an export leaves the document's own path alone.
Export As and Export for Screens keep leaving .ai to Save As.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The macOS bundle must declare every File › Open extension, and the
packaging of a vector app must not mention raster-editor files. Affinity
Photo documents still open and place: the reader recognizes them by
their content, and a test checks it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
Open and Place read Affinity documents natively (.af from Affinity 3,
.afdesign/.afphoto/.afpub from Affinity 1 and 2): layers and sublayers,
groups, artboards with their names and backgrounds, Publisher pages side
by side, curves with live corners, parametric shapes, compound shapes,
clipping by nesting, vector and pixel masks, solid and gradient fills,
strokes, fill layers, artistic and frame text, placed images (their
original JPEG/PNG), pixel layers, embedded documents as their cached
picture, opacity, visibility, locking and blend modes, at the document's
resolution in points. Unsupported content (effects, adjustments, brushes,
master pages, special shapes, Affinity-only blend modes...) is listed in
the import warning, one line per kind. A file whose native data can't be
read opens as its embedded preview with the reason, and that preview
cannot be placed or used as a template or library. Save never writes back
over the Affinity file and there is no Affinity export.
The document semantics were fitted to the thumbnails Affinity embeds in
176 public documents. `cargo xtask corpus --affinity` fetches 22 public
CC0/MIT/Apache-2.0 documents at pinned commits, sha256-verified; the new
engine test renders each and compares it with Affinity's own thumbnail
(0-4.4 of 255 mean difference, per-file ceilings). import_fuzz mutates a
synthetic native document's stream and container through open, render
and export; the fuzz crate gains a stream target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
- Type left unfinished when a layer's marked content ends, and a clip that closes in a later layer,
stay in their own layer (no stray "Page N" layer, no art moved to the next layer).
- Top-level layers stack as the pages mark them, so a layer whose art starts on a later page keeps its
place; layers marked with no art come in empty.
- A stroke written as each glyph's outline stroked, right after (or before) the type, becomes the type
object's stroke above (or below) its characters: the type stays one editable object.
- Spaces read from gaps wider than a space get their own tracking where even tracking would put the
glyphs after them more than a tenth of an em off (barcode digit groups); lines are measured to their
last visible glyph, so a wrapped line's trailing space no longer spreads into the tracking.
- Lines wrapped in a frame (each wrapped line ending in a space, one left edge, font, size and leading,
with an optional paragraph spacing) come back as one area type object, left aligned or justified,
only where laying it out breaks the lines exactly where the file does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read only the indexed PNG thumbnail observed in version-12 Affinity containers. Bound and validate the PNG before decoding, warn about omitted native content, clear the source save target, and reject auxiliary imports that cannot display that warning. Add malformed-input, lifecycle, property and optional native-oracle tests plus a fuzz target. Editable native layers and Affinity export remain unsupported.
Switching to CMYK separates RGB greys through the CMYK profile into
four-colour greys and a rich black, as Illustrator does; only Gray
colours stay on the black plate. The command catalogue said "greys stay
greys", which read as if R = G = B colours went to K only.
- file.documentColorMode (and its alias) and document.open {colorMode}
take grays: "profile" (default, unchanged) | "black". With "black",
RGB greys with R = G = B convert to K only, K = their grey value (the
inks Edit Colors > Convert to Grayscale before the switch gives);
other colours convert as before. Swatches, tints, gradient stops and
pattern tiles follow the same rule.
- Pattern tiles are now converted with the rest of the document (they
kept RGB colours in a CMYK document before), as the command said.
- The command catalogue, docs/mcp.md and the ROADMAP say what happens to
Gray colours and RGB greys.
- Tests: the profile path (four-colour greys, rich black, pattern tiles,
Gray kept), grays: "black" over art, swatches, tints, gradients and
patterns, document.open, and the issue's file over MCP through a PDF
export and back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The export tool's description, the docs' open_file and export rows and the
CLI's run/convert usage listed only some formats. The CLI usage now prints
the writable extensions next to the readable ones, both generated from the
format table (export_extensions), the export tool points to its generated
format enum, and the docs name EPS, DXF, EMF, WMF, SVGZ and templates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.exportSelection (the MCP export tool's `selection: true`) now
replies with `format` and the encoder's `warnings`, the same list a
whole-document export gives: lossy WebP written lossless, blend modes left
out of SVG Tiny 1.2, PDF layers at 1.4 and the rest. A selection export
that approximates nothing replies with an empty list.
Root cause: export_selection encoded with `encode`, which drops the
warnings encode_with_warnings returns, and its reply carried only path,
bytes and bounds. The params doc and docs/mcp.md now describe the reply.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VectorCraft had an Additional Plug-ins Folder preference but nothing loaded from it. Port
PhotoCraft's wasmi plug-in host (same sandbox, manifest and parameter schema) as the new
vectorcraft-plugins crate (L2), extended for vector documents:
- Object filters read the selected paths and compound paths as JSON (native PathData and
Paint forms: geometry, fills, strokes, gradients, opacity) and return the objects to keep,
change, add or delete; the output is validated in full and applied as one undo step
(plugin.run). A failing plug-in leaves the document untouched.
- Live-effect plug-ins are geometry effects in the appearance stack (effect id
plugin.<id>), re-evaluated (and cached) when the object or parameters change, editable,
exported and baked by Expand Appearance; a missing plug-in leaves the geometry as is.
- plugin.list / info / run / install / remove / reload; the preference folder installs
every *.wasm at startup and when it changes; File > Open of a .wasm installs it (web).
- Object > Plug-ins and Effect > Plug-ins menus with dialogs generated from the parameter
schema (live preview), the Appearance panel's fx menu, docs/plugins.md, an original
example plug-in (Desaturate, a Rust crate outside the workspace) and fuzzing of the
plug-in boundary (engine/tests/plugin_fuzz.rs).
Tool plug-ins and paint-changing live effects are left for later (docs/plugins.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
compatibility "1.3" writes a %PDF-1.3 file (its XMP metadata says 1.3
too) without transparency: a copy of the document is flattened first
with flattenerPreset (stored with PDF presets; empty is High Resolution)
and the flattener options over it, see-through image pixels counting as
transparency and rasterized areas clipped to their regions so no soft
mask is needed. Page groups are left out and the writer refuses a file
that would still have transparency. PDF/X-1a and PDF/X-3 share this
path (their version is now PDF 1.3; 1.4 is still accepted and written
as 1.3), and with it the preset, the header and metadata code and the
transparency check. Passwords at PDF 1.3 encrypt with 40-bit RC4
(revision 2). The Save PDF dialog lists PDF 1.3 and picks the flattener
preset under Advanced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Embed Page Thumbnails gives every page a /Thumb image: the page drawn on
white, 106 px on its long side, without the layers the page leaves out
(the engine draws them; vectorcraft_pdf::page_areas says what each page
shows; PDF/X files get grey ones). Optimize for Fast Web View writes a
linearised file: the objects are put in linearised order and numbering,
encrypted when a password asks (the encryption dictionary goes with the
catalog, PDF layers' groups too), then laid out with the linearization
dictionary, both cross-reference tables and the page offset and shared
object hint tables (the hint stream encrypted like any other). Their
"not applied yet" warnings are gone. Reading the objects back uses the
lexer of encrypt.rs (syntax.rs adds what rewriting needs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Create PDF layers from top-level layers (document.exportPdf createLayers, the
Save PDF dialog's General option) now writes an optional content group per
top-level layer (template layers are left out): named as the layer, off when
the layer is hidden, /PrintState /OFF when its Print option is off, locked
when it is locked, listed top first, with a print auto-state (not under a
standard). The file reopens with those layers.
The PDF writer has no optional content, so each layer is drawn as a form of
its own (no transparency group, so it draws as in place) starting with a mark;
after writing, the uses of the layers' forms are wrapped in /OC ... BDC/EMC
and the catalog gets /OCProperties (crates/pdf/src/forms.rs, through the
shared patch writer and the PDF syntax reader of encrypt.rs). PDF 1.4 and a
knockout page group write plain page content with a warning; the dialog greys
the option at 1.4. The "not written yet" warning is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.exportPdf {standard: pdfX1a|pdfX3|pdfX4} and the PDF/X presets now
write conforming files instead of being refused:
- every PDF/X file gets a /GTS_PDFX output intent (by default the CMYK
profile in effect, embedded), TrimBox, Trapped, GTS_PDFXVersion (and
GTS_PDFXConformance for X-1a), a title and dates; editing data,
passwords and, in X-1a/X-3, PDF layers are refused
- PDF/X-1a converts every colour and image to a CMYK destination,
untagged, with CMYK spot alternates; X-3 and X-4 tag colours with ICC
profiles
- X-1a and X-3 are written as PDF 1.3 and their transparency is flattened
first (High Resolution preset; images with see-through pixels count,
rasters stay opaque); X-4 keeps transparency, is PDF 1.6 at most and
names the standard in its XMP metadata
- the written file is checked against its standard (soft masks,
transparency groups, opacity, blend modes, RGB in X-1a, fonts not
embedded) and refused rather than mislabelled
- choosing a standard sets the latest compatibility it allows and turns
off what it forbids (engine and Save PDF dialog)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.open and file.place read .eps files and PostScript by any name
(PostScript .ai opens as artwork, .ait as a new untitled document). An EPS
file VectorCraft wrote restores the document it carries (restored: true);
other files run through a small clean-room PostScript interpreter (paths,
colours and spot inks, gsave/grestore, concat, line state, bind def,
procedures, loops and dictionaries, clips, shadings, images through the
decoding filters, type as point type), and fall back to their TIFF preview
with a warning when the program can't be read. Operation, nesting, memory
and path budgets keep hostile files from hanging or exhausting memory.
The clipping-group nesting the metafile reader used moves to
vectorcraft_doc::clipnest, shared by both readers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Print dialog's Print Preset list loads [Default] or a saved preset
(reading [Custom] once the settings differ) and Save Preset... names the
settings as a preset. Edit > Print Presets... lists [Default] (generated,
protected) and the saved presets with how they differ from it; New... and
Edit... open the preset editor (the Print dialog's sections), Delete,
Import... and Export... use .vcprintpresets files, which File > Open also
imports. Commands print.presets.list/save/delete/import/export; the presets
live in Prefs.print_presets and persist with the preferences.
The open-dialog filter count test counts the new Print presets filter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save PDF > Output now applies: colours convert to a destination profile
(destination, or preserveNumbers keeping the numbers of colours already in
its model; grey stays grey; images and freeform gradient images convert
too; printer's marks don't). Profile inclusion writes ICC-based colour
spaces (CMYK with the destination's or the document's profile, RGB as sRGB,
grey), and transparency in CMYK output blends in the CMYK profile's space.
The output intent profile is embedded as the catalog's /GTS_PDFX output
intent with its condition, identifier and registry, and Trapped goes in
the document information (PDF/A keeps its own). A shared patch module
edits written PDFs (Lab spot alternates use it too).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.export {format: "psd"} writes layered bitmaps (8 bits per channel),
written from the format's public specification: RGB, CMYK (ink amounts) or
grayscale, the resolution and an embedded profile, PackBits channels, and the
merged image (the flat render). layers (default true) draws each top-level
layer alone as a pixel layer, cropped to what it paints, with its name,
opacity and blend mode; a background colour becomes a Background layer.
maxEditability turns layers and sublayers into groups and every object into a
layer named as the Layers panel names it (text by its text); clipping, masked,
knockout or appearance-carrying layers stay one layer. hiddenLayers writes
hidden layers hidden instead of leaving them out; layers: false writes a flat
image on white. At most 30000 pixels a side.
Export As offers PSD with the psdOptions dialog (the PNG Options rows, then
Color Model, Flat Image / Write Layers with Maximum Editability and Include
Hidden Layers, Embed ICC Profile). Tests read the files back with the psd
crate (MIT OR Apache-2.0, a dev-dependency).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The svg crate's new CSS writer (css_rules) gives the CSS web pages style objects
with: rectangles and ellipses as background-color / linear-/radial-gradient(),
border and border-radius; type as font, color, spacing and alignment; shadows,
glows and blur as box-shadow, text-shadow and filter. It shares SVG export's
declarations (transparency, fonts, type spacing, colours, gradient stops), which
moved into svg/src/css.rs. Options: units, absolute position, width and height,
unnamed objects, rasterize unsupported art (as a PNG background-image).
Commands css.selection, css.generate and css.export; UI commands css.copy and
css.exportFile; the CSS Properties panel with a highlighted code view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Window > Asset Export lists the document's assets as thumbnails: art
dragged in off the canvas (or added with +) becomes one asset per object,
or one of them all with Alt; Object > Collect for Export does the same.
Assets name their objects, so they follow edits (moving the art moves the
crop) and deleting the art removes it from its asset. Click selects,
double-click renames, the bin removes. The panel's export settings are
Export for Screens' own format rows on the document's shared settings, and
Export writes each asset alone, cropped to its art, into a picked folder
(the web downloads a zip). Export for Screens' Assets tab lists the assets
with checkboxes and exports the checked ones.
Commands: assets.add, assets.remove, assets.rename, assets.list,
assets.export, assets.settings.set; document.exportForScreens takes
assets. Model: Document.assets (ExportAsset {id, name, nodes}).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File > Export > Save for Web (Legacy) (Ctrl/Cmd+Alt+Shift+S) opens a dialog with Original,
Optimized, 2-Up and 4-Up views showing each file's size and download time, and settings for
colour reduction, colours, dither, transparency and matte, interlacing, web snap, lossy GIF,
JPEG quality, the colour table (lock, map to transparent, web shift, sort), image size and
anti-aliasing, Clip to Artboard, sRGB, metadata, slices (all or selected), HTML output and
presets. Preview in Browser opens the HTML page.
Engine: document.exportForWeb, document.exportForWeb.preview (its byte count is the size of
the file Save writes), webExport.settings and webExport.presets.list/save/delete. Saved presets
and the remembered settings are stored in Prefs. Render: quantize_locked, and encode::web for
web snap, colour table edits, lossy GIF and GIF/JPEG comments. PNG files get an sRGB chunk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.export {format: "tga"} writes uncompressed true-colour Targa files
through the raster export path (resolution, background, anti-aliasing,
artboard ranges): depth 16 (5-5-5 with a one-bit alpha), 24 (flattened on
white) or 32 (8-bit alpha), rows bottom-up, with the version 2 footer.
Export As offers it with the tgaOptions dialog (Depth).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.export {format: "bmp"} writes BMP through the raster export path:
depth 1 (black and white), 4 or 8 (a palette by reduction and dither,
optionally RLE4/RLE8), 16 (5-5-5), 24 or 32 (with alpha, a version 4
header); colorModel rgb or gray; fileFormat windows or os2 (a core header);
flipRows writes top-down rows (a negative height). Impossible combinations
are refused as bad params. Export As offers it with the bmpOptions dialog,
which keeps its choices writable together; BMP moves from read-only to read
and write (tests that used .bmp as the unwritable format now use .dwg).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.export {format: "tiff"} writes baseline TIFF through the raster
export path (resolution, background, anti-aliasing, artboard ranges):
colorModel rgb (transparency as unassociated alpha), cmyk (ink amounts,
CMYK colours keep their inks) or gray, lzw (weezl), byteOrder little (II)
or big (MM), embedIcc, and the resolution tags. Export As offers it with
the tiffOptions dialog; TIFF moves from read-only to read and write.
The TIFF writer is shared: EPS previews now use it (and its PackBits)
instead of their own copy, so eps depends on render (layers allow it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crates/metafile import: EMF and WMF (placeable or not) records play on a
small device-context model (objects numbered as GDI does, saved DCs, world
transforms and mapping modes, path brackets, the current position) into one
artboard (the picture's frame) and one layer: paths with fills and strokes
(pens with caps, joins, dashes), clipping groups for clips, images from DIBs
(StretchDIBits, BitBlt, StretchBlt, AlphaBlend, StretchDIB, DIBBitBlt) and
point type in the font the file names. Lines drawn one after another join
into one path; a clip around the whole picture is dropped; objects far
outside it are left out; unknown records are skipped with one warning.
document.open and file.place read .emf and .wmf through the shared loader
(Open and Place filters list them). clipboard.importEmf loads a picture into
the clipboard, and Paste takes image/emf from a system clipboard service
ahead of text and bitmaps. The import fuzzer covers mutated and random-record
metafiles opened, placed and pasted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New crates/metafile (L3, pure Rust): hand-written EMF and WMF writers over
one shared document walk. EMF keeps Bezier paths, solid brushes, geometric
pens (width, caps, joins, miter limit, user-style dashes), clipping masks as
path clips, images (StretchDIBits, or AlphaBlend with transparency) placed by
a world transform, type as outlines; gradients become images clipped to their
shape and pattern fills their tiles clipped to it. The header's frame is the
artboard in 0.01 mm on a 100 px/mm reference device. WMF flattens curves into
polygons in 16-bit units behind a placeable header (with its checksum),
numbers objects as players do, and reports what it leaves out (clipping,
transparency, gradients, rotated images).
document.export {format: emf|wmf} (and serialize, export) writes one artboard,
or one file per artboard with useArtboards (false: the art's bounds); raster
effects are rendered to images first, as for PDF. Export As lists EMF and WMF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASCII DXF of any version opens (document.open) and places (file.place) through
a new reader in vectorcraft-cad: lines, LW/2D/3D polylines with bulge arcs,
circles, arcs, ellipses, NURBS splines (exact Beziers up to cubic, fitted
otherwise), solids, solid and pattern hatches, TEXT/MTEXT/ATTRIB as point
type, named blocks as symbols (one per look for by-block art), anonymous
blocks and dimensions as groups, MINSERT arrays, OCS extrusion; layers keep
off/frozen/locked/plot state; ACI and true colour, lineweights, linetypes and
transparency resolve through layers and blocks. The `dxf` option object picks
the layout, ratio or fit, lineweight scaling, centring and merged layers;
document.dxfInfo lists layouts, layers and units. Binary DXF and DWG answer a
clear error. The DXF Import Options dialog (dxfImport) asks on open and place
with a layout preview. Fuzzed in import_fuzz.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- document.save / Save As / Save a Copy to a native or .ai file take
separateArtboards + range (also write <name>-<artboard>.<ext> per artboard
beside the master file, holding that artboard and the art touching it; the
result lists every file), includeLinked (linked images keep their files' own
pixels), embedProfiles (ICC profiles loaded from files that the document is
tagged with; opening installs them where missing) and pdfCompatible (native:
also carry a PDF of every artboard; .ai: false writes blank pages around the
native document). .ai files also take compress (compression.compressText).
- File > Save As to a native or .ai file shows the options dialog (VectorCraft
Options) after the save panel: Save each artboard to a separate file (All or a
range), Include Linked Files, Embed ICC Profiles, Create PDF-Compatible File,
Use Compression, Preview; the web's Save As dialog shows them too. Save, Save a
Copy and Save as Template reuse the options as last saved.
- Format: optional "profiles" and "pdf" members (load_file, pdf_content),
SaveOptions.include_linked/profiles/pdf; files without them are unchanged.
- Changed test: tests_aisave::save_as_ai_writes_a_pdf_that_reopens_editable now
confirms the options dialog Save As .ai shows before writing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every Preferences > File Handling > Data Recovery interval, modified documents
changed since their last copy are written to the recovery store in the
background (the same snapshot and encoder as Background Save; the status bar
shows it). Complex documents (over 20,000 objects) are skipped while Turn off
Data Recovery for complex documents is on.
- A copy goes when its document is saved, reverted, closed or undone back to its
saved state, and when the app quits with nothing unsaved.
- Each running app keeps its copies in an area of its own: a sub-folder whose
.lock file it keeps locked for its lifetime (File::try_lock), or on the web a
heartbeat refreshed every minute. Only areas nobody holds (lock free, or no
heartbeat for three intervals, at least three minutes) are offered, and an
area is held while it is restored or discarded, so a second running instance
never offers or deletes a live instance's copies. Empty leftover areas are
tidied away.
- The next launch after a crash asks with the Recover Documents dialog (Restore /
Discard / Cancel): restored documents open as "<name> [Recovered]", unsaved, and
Save asks where to save them, suggesting their original file; the copy moves
into the new app's area.
- Store: the recoveryFolder preference, else Data Recovery beside the desktop
app's preferences; browser storage on the web (Services::recovery_store);
tests use an in-memory store (with in-memory locks, or heartbeats).
- Engine: file.recovery.save, file.recovery.list, file.recovery.restore {file?},
file.recovery.discard {file?}; DocState.recovered and DocState.recovery.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- pdf/src/encrypt.rs encrypts a written PDF with the standard security handler: 128-bit RC4
(PDF 1.4, revision 3; PDF 1.5, revision 4), 128-bit AES (1.6) or 256-bit AES (1.7/2.0,
revision 6), every string and stream, plaintext XMP metadata when asked, the permission bits
(printing none/low/high, five change levels, copy, screen readers). Crypto from RustCrypto
(aes, md-5, sha2: MIT OR Apache-2.0, pure Rust, wasm).
- PDF/A and PDF/X refuse passwords; equal passwords and passwords the algorithm can't take are
refused.
- Opening: the permissions password opens RC4/AES-128 files too; editing data comes back from
an encrypted PDF opened with its password.
- Save PDF › Security: the passwords, permissions and plaintext metadata, the encryption level.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.print {settings?, path?} lays the document out on paper and writes one PDF page per sheet:
copies (collated, reversed), artboards (all, a range, ignored), blank artboards skipped, paper
size and orientation (or turned per artboard), transverse, Print Layers (template layers never
print), placement on the imageable area, no scale / fit / custom / tiles of the paper or of the
imageable area with overlap and a tile range; printer's marks and bleed as PDF export draws
them; composite or separations (one grey plate per ink that prints, overprints honoured, spots
as process), emulsion down, negative; rendering intent and preserve numbers. print.preview lists
the pages, tiles, inks and warnings; print.setup saves the settings with the document
(Document.print_setup, one undo step).
PDF export and print share one page writer; the ink separation helpers move from the renderer to
vectorcraft_doc::inks and the range parser to vectorcraft_doc::range so print can use them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
command.batch kept its steps in one interaction of the document active
when it began and journaled itself by committing that interaction. A
step that opened, closed or reverted a document broke both: the switch
cancelled the interaction (throwing away the steps so far), and the
commit then ran in the new document, which had none, so the batch was
never journaled and a replay was missing the new document. An error
after the switch rolled back only the active document.
- Each step runs inside an interaction of the document active when it
starts; a switch leaves it open while a batch runs, so every document
the batch edits gets one undo step when it succeeds.
- The batch journals itself, whichever document it ends in.
- On error everything rolls back: documents the batch opened close, the
ones it closed or reverted come back (Session::batch_stash), each
one's interaction is undone, and the active document and the
Untitled-N count go back too.
- DocState::keep_interaction / undo_interaction share the undo-step and
rollback code with commit_interaction / cancel_interaction.
- Tests: a batch that opens a new document is journaled, replays to the
same documents and keeps one undo step in each; a failed batch closes
the document it opened and restores the one it closed; a failed batch
brings back a document it reverted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New crates/eps (L3): a hand-written EPS writer. A DSC header with
%%BoundingBox and %%HiResBoundingBox (y up from the origin), %%LanguageLevel
and spot colours; paths filled and stroked with caps, joins, miter limits
and dashes (strokes a plain line can't draw as filled outlines, brushes as
their art), clipping groups, linear and radial gradients as smooth
shadings at Level 3 and stepped fills at Level 2 or with compatible
gradient printing, patterns as tiles, freeform gradients and placed images
as images (Flate with masked transparent pixels at Level 3, run-length at
Level 2), type as glyph outlines, spot colours as Separation spaces and
overprints when preserved; RGB written as CMYK on request. A TIFF preview
(1-bit or colour, transparent or opaque) behind the binary header, a PNG
thumbnail and the native document in comments (eps::native and
eps::thumbnail read them back for a later import).
document.export {format: eps} and document.exportEps: transparency is
flattened first with a flattener preset (built-in or saved) and options;
the visible art's bounds, or with useArtboards one file per artboard named
{stem}_{artboard}.eps. Export As lists EPS, then EPS Options
(ui.epsOptionsDialog for agents, remembered between exports).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SVG export writes UTF-8, UTF-16 (big-endian with a byte order mark) or
ISO 8859-1 (other characters as character references) and SVG/SVGZ
open in any of them. The Tiny 1.2 profile (simplified) writes
presentation attributes only and leaves out filters, masks, blend
modes and symbols with warnings. embedFonts embeds the fonts type uses
as @font-face subsets of the characters used, honouring the fonts'
fsType (whole when subsetting is forbidden, left out when embedding
is). New options: encoding, profile, embedFonts (SVG Options dialog,
document.formats); document.serialize also returns dataBase64 for
non-UTF-8 files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Artboards: All, a range or Full Document (one file per format: a PDF of
every artboard, other formats the bounds of all art), Include Bleed
(artboards grown by the document's bleed). Rows size files as a factor,
100w, 100h or 72ppi (suffixes @2x, @100w, @100h), and choose PNG, PNG 8,
JPG 100/80/50, WebP, GIF, SVG or PDF. Create Sub-folders (by size, by
format, or a row's own folder) and the presets Mobile 1x/2x/3x and
Density buckets (ldpi…xxxhdpi sub-folders). The gear opens Format
Settings: per-format options for every row of that format (the raster
and SVG options dialogs' rows, the PDF preset). Open Location after
Export shows the first file. The dialog has Artboards and Assets tabs.
The params of the last export are saved in the document
(Document.export_settings, not an undo step), read with
document.exportSettings, and the dialog reopens on them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>