The Perspective Selection tool shows the selection's bounding box in perspective; its handles
scale in plane space (perspective.transform; Shift proportional, Alt from the centre). Alt-drag
moves copies that stay attached, pressing 5 while dragging moves along the plane's normal
(ToolKey::Digit, delivered by the canvas and MCP press_key), and the arrow keys nudge in
perspective (perspective.nudge). Transform Again repeats the last perspective move or scale in
plane space. Attachments (plane and depth) now live on the objects (Node.perspective), so copies,
duplicates and pastes keep them; grids from older files hand theirs over. Homography moves to
vectorcraft-geom. Hint-bar text for the tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
View > Perspective Grid > One/Two/Three Point Perspective list the built-in views
([1P/2P/3P-Normal View], plus Low and High views generated in code) and the saved
presets of each type; Save Grid as Preset... and Define Grid's Preset menu and Save
Preset... save the fields. Edit > Perspective Grid Presets... (previously a
mislabelled reset to the two-point grid) manages them: New, Edit, Delete, Import,
Export (.vcperspective), stored with the preferences. New commands
perspective.presets.list/save/delete/export/import; perspective.grid.preset takes
a preset name. Preset import is fuzzed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Double-clicking a Liquify tool opens its Tool Options (dialog liquifyOptions):
the shared Global Brush Dimensions (Width, Height, Angle, Intensity, Use
Pressure Pen), the tool's options (Detail, Simplify with its checkbox, Twirl
Rate, Complexity, Brush Affects, Horizontal/Vertical) and Show Brush Size.
The canvas captures pen pressure from pen/touch force, ui.pointer and MCP
pointer events take `pressure`, and object.liquify takes [x, y, pressure].
Fixes: LiquifyParams::to_json now sends affectAnchors/In/Out; Alt-drag sizes
the brush from its current size (Shift keeps proportions) instead of
collapsing it. Hint-bar text for the seven tools. Registers the MCP Liquify
tests file added in M8.2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Alt-dragging near (not on) a selected pin turns the art around it; the pin
keeps that turn through later warps. arap::Pin gains an angle constraint and
object.puppetWarp takes angles: [deg|null...].
- Shift-click adds pins to the selection or takes them out; dragging a pin
moves every selected pin. Select All Pins selects them all (it used to
deselect). Alt-click no longer deletes a pin; Delete removes the selection.
- Control bar: Expand (0 allowed), Show Mesh (on by default), Select All
Pins. Hint-bar text for the tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Make with Warp and Make with Mesh open dedicated dialogs (style dropdown,
orientation, bend and distortion sliders; rows and columns) with live
preview; while an envelope is selected the menu shows Reset with Warp /
Reset with Mesh instead, which switch the envelope's kind and keep its
content (Maintain Envelope Shape samples the current surface). Envelope
Options is a full dialog prefilled from the selection (Anti-Alias,
Preserve Shape, Fidelity, Distort Appearance, Distort Linear Gradients,
Distort Pattern Fills); with nothing selected it sets the defaults for new
envelopes. object.envelope.info reads the settings. Release returns the
warp or mesh shape as a gradient mesh and keeps opacity, blend mode and
mask; Expand keeps name, knockout, isolation and mask; Object > Expand
expands envelopes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
View > Perspective Grid > Define Grid... opens a dedicated `perspectiveGrid` dialog
(ui.perspectiveGridDialog) prefilled from the current grid instead of a generic
form with fixed defaults that forced kind 2. The grid model gains (append-only)
name, viewing angle (the station point), units, scale, plane colours and opacity;
grids with a viewing angle foreshorten each axis as a true camera. New commands:
perspective.grid.get (read-only) and perspective.grid.define (one undo step,
only changed fields apply).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each tool keeps its options (a table in vectorcraft_tools::settings) when another
tool is chosen, across document switches, and in the saved preferences
(Prefs::tool_settings). The 7 Liquify tools share one set of Global Brush
Dimensions; the Symbolism tools share their brush. tool.setOption takes
{values} and a `tool` (another tool's options are stored for later), returns
the options, and works headless (MCP host command).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Type > Font, the Character, Properties and Glyphs panels and Find Font list
every installed font by default, as the reference app does, instead of the
four bundled families. The family dropdowns get a search field that filters
the list as you type (Enter picks the first match) and open on the current
font. Show System Fonts, which no longer means anything, becomes Refresh Font
List (text.rescanFonts): it picks up fonts installed since the app started and
redraws type set in them without editing the document. text.fontList lets MCP
agents list the families and a family's styles.
Root cause: the font lists came from FontDb::families(), which only held the
bundled fonts until something scanned the system fonts (fixed with #130). The
Type > Font menu, rebuilt every frame, now builds its items once per font
change, and renderers lay type out again when the fonts change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The issue asked for a vector halftone that turns art or an image into
vector dots, lines or shapes by tone, with angle, frequency, shape, mono
or CMYK separations and clipping to the object. Nothing like it existed.
object.vectorHalftone renders the selection offscreen, samples its tone
once per cell of a grid turned by the screen angle and places a shape
whose area follows the tone (circle, ellipse, square, diamond or line).
Mono screens follow lightness in one colour; CMYK screens follow the ink
amounts the CMYK output separates, one screen per ink at the classic
angles, multiplied over each other. Dots are clipped to the art's
outline as real geometry (only the dots crossing it go through a
boolean). It is a command rather than a live effect: the live pipeline
can't sample rendered tone when exporting. Its dialog previews live and
OK keeps it as one undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The issue asked for non-destructive colour adjustments in the Effect menu
and the Appearance panel that work on vector paints, live type and
embedded images. Only the destructive Edit Colors commands existed, and
the live-effect pipeline only knew geometry and raster effects.
Effect > Color Adjustments adds six effects (adjust.*). They recolour,
in stack order, everything an object paints with: fills and strokes
(gradient stops too), type, gradient meshes, embedded images and a
group's members, each colour keeping its model. The Edit Colors walk over
an object's colours moved to vectorcraft_doc::recolor so the commands
and the live effects share it. The renderer draws adjusted art through
its object-effect path (recoloured images cached within a memory
budget), exports bake the adjusted colours (images as recoloured
copies), and Expand Appearance makes them permanent. The effect dialog
shows sliders, a channel menu and a draggable Curves graph, all previewed
live as one undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The issue asked for one-step symmetric shapes, splitting all selected art
along a line and cropping art to a rectangle as real geometry. Only the
freehand Knife and the Scissors existed, and neither works on compound
paths as one shape.
New commands path.mirrorCut, path.lineCut and path.rectCut cut the
selected paths and compound paths (inside groups too) with pathops
booleans against a half-plane or rectangle, and split open paths with
the Knife/Path Eraser helpers. Mirror & Cut reflects the kept half and
unites it with itself (open halves are joined on the axis). The draw2
piece helpers now rebuild compound paths too. Three tools (Eraser group,
original icons) preview the cut live while dragging and commit it as one
undo step; Mirror & Cut's axis and kept side are tool options shown in
the Control bar.
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>
The Transform panel had the link, but kept it in panel-local egui memory;
the Properties panel's Transform section and the Control bar had none, so
size changes there were never proportional.
The link is now the constrainProportions preference (prefs.get/prefs.set),
one toggle shared by the three places (transform::constrain_link), and every
W/H field passes it to object.setBounds as `proportional`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clicking with the Type tool runs text.create at once, and ending the edit
(Escape, another tool, a click elsewhere) only stopped editing, so every stray
click left an invisible empty text object and a "Type" history step.
The Type tool now remembers the point type its click placed and, when editing
ends with it still empty, runs the new text.discardEmpty command: it removes
the text and, when nothing else changed since the step that created it, drops
those steps so neither the document nor the history keeps a trace. A command
from outside the tool that takes the edited text out of the selection
(Deselect, a Layers panel pick) or removes it now ends the edit too
(Tool::after_command). Dragged area-type frames and type in or on a path keep
their frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rotating an object baked the rotation into its geometry and kept nothing
else, so the bounding box, the handles and the Transform/Properties
panels fell back to the page-aligned box (the Rotate field went back to 0).
Root cause: nodes carried no record of their own axes. Each node now
keeps `bbox_angle` (append-only, written only when non-zero), which every
transform turns with the object (mirroring transforms keep the nearer of
the two possible boxes). The geometry is still baked. The selection's box
(`Document::oriented_bounds`) stands at the angle the selected objects
share. The Selection tool hit-tests and scales its handles in that frame,
and the canvas draws it rotated. `object.setBounds` measures W/H and the
reference point along it. `object.rotate {absolute}` turns the selection
to an angle, which the panels' Rotate field now shows and sets. Object >
Transform > Reset Bounding Box (previously a no-op) clears the angle in
one undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pdf crate's PrintSettings gains advanced {printAsBitmap,
overprints: preserve|discard|simulate, flattenerPreset} and
color.profile, all defaulting so saved print_setup values still load.
Composite output discards overprints (no /OP) or simulates them as
Overprint Preview draws them; separations keep them. A printer profile
converts composite colours with the chosen intent (a CMYK one writes
device CMYK) and separations separate with a CMYK one. The engine
flattens transparency with the preset, or renders each printed region
as one image for Print as Bitmap, before PDF or PostScript layout.
The Print dialog's Advanced and Color Management sections show them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
View > Show Print Tiling (view.printTiling) draws the pages of the
document's print settings on the canvas, dashed and numbered, from
print.preview's own layout (print.tiling), so the canvas and the
printed job agree. The Print Tiling tool replaces the no-op tool: a
drag places the first page's imageable corner (print.tiling.set,
saved as print_setup.tileOrigin, one undo step); a double click on
the canvas or the tool button resets it. Tiles start at the origin
and extend to cover the art; the Print dialog shows the tool's
placement with a Reset button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.print {format: "postscript"} (the default for a .ps path) writes the
print job as Level 3 or 2 (level) PostScript instead of a PDF: DSC comments
with %%Pages the page count, one page per sheet with its paper size,
printer's marks in Registration, separations naming their ink (%%PlateColor)
with its halftone screen, a fixed flatness and film negatives. Transparency
is flattened first with flattenerPreset. The answer says which format was
written. In the app, a PostScript job is always saved, never sent to the
printer (the Print dialog's own options are unchanged).
The PDF print's layout is exposed as vectorcraft_pdf::plan (the PDF writer
uses it too); vectorcraft_eps::print draws its sheets with the EPS writer's
document walk, each sheet once however many copies print.
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>
The Print dialog (Ctrl/Cmd+P) edits the document's print settings in the
sections General, Marks and Bleed, Output, Graphics, Color Management,
Advanced and Summary, with a preview of each printed page under the section
list (drag the art to move its placement). It drives print.setup,
print.preview and file.print; the preview pages now carry their transform,
printed area and trim box. Print sends the job's PDF to the chosen printer
through the new PrintService (lp on macOS/Linux, the shell print verb on
Windows, a hidden frame and the browser's print dialog on the web); without
one, or with PDF File, it saves the PDF. Done only keeps the settings.
UI commands file.print, print.printers and print.printerSetup.
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.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>
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>
Session::note_journal only kept values noted by the top-level command, so
a command run as a step of command.batch lost them: a save in a batch
stamped File Info's modified date from the clock without recording it,
and object.scale's strokes/corners came from the preferences again on
replay. Replaying such a journal a second later (or with other
preferences) gave a different document.
- Session::execute_step runs a batch step with its own journal notes
(note_depth) and returns its params with what it noted; command.batch
writes those params into its step of the batch's journal entry (steps
that note nothing stay as given).
- journal_for_action strips the replay-only dates from a batch's steps
too, so an action recorded with one still dates the document when it
plays.
- Tests: a save in a batch records its date and a replay with the
recorded date moved lands on it, while an action leaves it out; a scale
in a batch records Scale Strokes & Effects and replays alike with the
preference off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.exportPdf applies the bleed (the document's with bleed.useDocument,
else the custom values): each page's TrimBox is its artboard and its BleedBox
and MediaBox the artboard grown by the bleed, so art in the bleed is kept.
Layers whose Print option is off are left out unless includeNonPrinting (new
setting, Save PDF › General) or createLayers is on. The bleed warning is gone;
the Marks and Bleeds section shows the document's bleed when it is used.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Document.slices holds user slices, Node.slice makes an object an object
slice that follows its bounds, and Document.slices_clip_to_artboard picks
the region auto slices fill (the artboards, or the art and the slices).
Object → Slice: Make, Release, Create from Guides, Create from Selection,
Duplicate, Combine, Divide… (dialog), Delete All, Slice Options… (dialog)
and Clip to Artboard; View → Hide/Lock Slices; Select → Object → Slices;
slice.list reads the numbered layout. The canvas draws the slices in the
Slices preferences' line colour with their numbers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File > Package (saved documents; unsaved changes are saved first) writes
<folder>/<name>/<document>.vectorcraft, the linked files in Links/ (relinked in the
packaged copy), the fonts its type uses in Fonts/ (leaving out fonts whose licence
doesn't allow embedding) and <document> Report.txt, then offers Show Package. Without a
folder (the web, agents) the same files come back as a zip.
Document Info's flyout picks one category (document, objects, styles, spot colours,
patterns, gradients, symbols, fonts, font details, linked and embedded images) and Save…
writes the text report Package shares.
Engine: file.package {folder?, name?, copyLinks?, linksFolder?, relink?, copyFonts?,
report?}; document.info gains category, format: text and sections. Text: FontFace
path, fs_type and embeddable. UI: ui.packageDialog (dialog `package`), file.showPackage,
docInfo.save.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Links panel lists every image (thumbnail, name, page, missing/modified/embedded
badge) with Show filters, sorting and thumbnail sizes; its buttons and flyout relink
(to a file or a folder), go to, update, edit the original, embed and unembed, show in
folder and open Placement Options. Link Info shows format, colour space, location, ppi
and effective ppi, dimensions, scale, rotation, size and dates. Edit > Edit Original
and the Properties panel's image section reach the same commands.
Engine: links.list, links.goTo, links.embed, links.unembed, links.info and
links.placementOptions; links.relink and links.update now place the new file as the
image's Placement Options say (transforms, bounds, file dimensions, fit, fill, the
alignment point, clip to the old bounds). Model: ImageObject.placement.
UI: links.editOriginal, links.reveal, ui.placementOptionsDialog; Services.open_file
and pick_folder on the desktop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- With Preferences > File Handling > Enable Background Save / Export on, File >
Save, Save As, Save a Copy and exports encode and write a snapshot of the
document on a worker thread; the call returns at once ({path, background}).
- The status bar shows a spinner and what is being written; ui.inspect lists
the running jobs under `background`.
- The document takes the path and counts as saved as of the snapshot once the
file is written: edits made meanwhile keep it modified.
- Quit and Close wait for running saves; the Save Changes dialog saves at once.
- The host gives a thread-safe writer (Services::write_shared, atomic on the
desktop); without one (web, tests) everything runs at once as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Format v3: optional gzip (Preferences > File Handling > Use Compression, or
document.save {compress}); compressed files open by their magic bytes, legacy
ones too.
- Every file write goes to a temporary file renamed over the target (engine
writes and the desktop app's writer), so a failed save keeps the old file.
- Only the images the document uses are written.
- Document keys from newer versions are kept (Document.extra, flattened).
- Assigned colour profiles are a real field (Document.color_profiles),
migrated from unknown.colorProfiles on load.
- document.save {version: 1|2} writes files older versions open; {preview}
embeds a PNG of the first artboard of at most 256 px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pattern strokes are written as the pattern's tiles clipped to the stroke's
outline (dashes, caps, profile, arrowheads and alignment included) instead
of mid-grey; freeform gradients on fills, strokes and type as an image of
their colour field at the document raster effects resolution, clipped to
what they paint, instead of their average colour. Both read back within
2% of the canvas at 150 ppi with no warning. Text strokes are covered for
cap, miter limit, dash array and alignment; knockout groups keep their
soft-masked form with a warning. The freeform box and grid sampling are
shared with the renderer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PDF export now renders the shadows, glows, blurs and feathers of groups,
layers, type, images, symbol instances, live objects and single fills and
strokes (and those inside opacity masks, symbol definitions and pattern
tiles) as images at the document raster effects resolution, instead of
leaving them out with a warning. Shadows and outer glows go under the
untouched vector art; a path whose fills or strokes carry effects is
written as one piece per fill and stroke. Expand Appearance shares the
image helpers (moved to rasterfx.rs); effect images now cover what the
renderer paints (members' shadows included).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
File Info's dates come from the clock, so documents made or saved a second
apart differed: journal_replay_reproduces_document and
rich_fixture_satisfies_invariants_and_undoes_to_blank failed at random.
One approach for every clock-derived date: it is a command input that
defaults to now, and the journal records the value used.
- file.new takes created? and document.save / file.saveAs take modified?
(Unix seconds, or null for none; default now), through date_param /
clock_date on the one save path (save_with), so Save, Save As and the
UI's save stamp alike; Save a Copy still leaves the dates.
- Their journal entries always record the date used, so replaying the
journal makes the same document whenever it runs. Actions recorded from
the journal (Session::journal_for_action) leave those dates out, so
playing an action later dates the document then.
- Test fixtures date their documents fixtures::CREATED, and the MCP
property test dates its local document as the headless one, so
documents built at different moments compare equal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Older native files (.drawcraft or an older format version) open as
"<name> [Converted]" and Save asks for a .vectorcraft name instead of
overwriting them (preference appendConverted). Open Recent Files honours the
0-30 preference; empty slots are hidden (the native menu rebuilds when they,
saved views or User Defined libraries change). File > Document Color Mode is
one command, file.documentColorMode, converting through the colour settings;
choosing the current mode is not an edit, and object.convertDocumentColorMode
stays as an alias the command palette leaves out. New File > Show in Folder
(file.reveal; paths with spaces work on Windows). Native files store the view
they were saved with (Document.last_view, written at save time so it never
dirties the document) and reopen there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save as Template suggests "<name> template.vctemplate" in the Templates folder
(the new templatesFolder preference; default Documents/VectorCraft Templates,
created by the desktop save panel) and never the source path. New from
Template is an engine command (file.newFromTemplate {path} or {name,
dataBase64}) that opens any readable file as a new Untitled-N document; in the
app, bytes from an agent go straight to the engine, otherwise the open dialog
starts in the Templates folder with template types first. .ait and template
files keep opening untitled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.revert reads and decodes the saved file first: if that fails the
document is left as it is, still modified. Otherwise Session::replace_document
swaps the content in the same tab (same place, path, format and uid; history
and selection cleared; saved state), so every tab keeps its zoom. It runs only
for saved, modified documents. In the app, Revert asks first with the shared
confirm dialog, whose OK runs file.revert {confirmed: true} on the engine.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save As offers VectorCraft, VectorCraft Template, PDF, SVG and SVGZ and asks
for the chosen format's options before writing: SVG Options for SVG/SVGZ, the
Save PDF dialog for PDF (its OK finishes the save); on the web a Save As
dialog first names the file and picks the format. A document opened from or
saved as SVG/PDF remembers its format and options in one place
(DocState.format, save_options: the SVG options, or every Save PDF setting),
so Save writes it again; the result's warnings name what the format loses,
then the encoder's notes. Save a Copy writes "<name> copy.<ext>" and leaves
the path, title and modified state alone. Save is off for a clean saved
document, and Save as PDF... is in the File menu.
Engine: document.save, file.saveAs and file.saveCopy take {path?, format?,
options?, svg?} (SVG options also flat) and write linked images beside an
SVG; file.formatOptions reads a format's options. save_plan/save_with are the
one save path of every frontend, on the shared SVG encoder (one SVGZ path).
The template format is writable. Save panels get per-format filters, a folder
and a suggested name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy and Cut also offer other apps a PNG of the copied objects, a PDF
with On Copy: PDF, and plain text when every copied object is type
(clipboard.flavours; clipboard.exportPng {scale?}, exportPdf,
exportText). Paste outside text editing takes what another app copied:
SVG, then PDF (clipboard.importPdf), then plain text as point text
(clipboard.importText), then a bitmap embedded at the view centre
(clipboard.importImage {dataBase64, mime?, center?}); pasting our own
copy keeps the lossless internal clipboard, and Cmd+V pastes a bitmap
that comes with no text. Services gains system_clipboard
(SystemClipboard: write, holds_ours, read, has): on Windows every
format at once, on macOS and Linux one (the text, else the PNG); the
web keeps SVG text only. With When pasting text: Keep Plain Text, text
the Type tool copied pastes without its formatting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built-in presets are generated in code (VectorCraft Default, High
Quality Print, Press Quality, Smallest File Size and PDF/X-1a, X-3,
X-4, the latter listed as not supported yet) and read-only. Saved
presets live with the preferences (Prefs.pdf_presets) and work as
`preset` wherever PDF options are taken: document.exportPdf,
pdfSettings, export/serialize as PDF and .ai saves.
pdf.preset.list / save / delete / export / import manage them
(.vcpdfpresets files; opening one imports it). The Save PDF dialog's
Save Preset… saves its settings under a name; Edit > PDF Presets…
(ui.pdfPresetsDialog) lists the presets with each one's description and
differences from the default, with New…, Edit… (the Save PDF dialog as
a preset editor, ui.pdfPresetDialog), Delete, Import… and Export….
document.pdfSettings now lists what differs from the chosen preset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve Editing (on in the VectorCraft Default preset) embeds the
native document in the PDF as an embedded file with a hash of the pages
it was written with. document.open of a PDF, .ai or .ait restores it
exactly (restored: true) and falls back to importing the artwork, with a
first warning saying why, when the pages were changed elsewhere or the
data can't be read; SVG and PDF share that fallback and its warnings.
The legacy drawcraft payload name is read too. vectorcraft_pdf gains
editing(), ImportReport.native and PdfOptions.native.
document.save / file.saveAs / Save a Copy write .ai (format "ai" or an
.ai path): every artboard as a PDF that always carries the document and
takes the PDF options; a restored .ai keeps its path so Save writes .ai
again, and .ait templates open their document untitled. The desktop
save dialog lists the formats Save writes. Choosing a standard turns
Preserve Editing off (PDF/A refuses it), Export for Screens and PDFs of
some artboards leave it out with a warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.place of a .txt file takes `text: {characterSet: unicode|ansi, platform: windows|mac,
removeLineReturns, removeParagraphReturns, replaceSpaces}`: Unicode reads UTF-8 or UTF-16 by its
byte-order mark (other bytes as the platform's 8-bit set), ANSI reads Windows-1252 or Mac Roman;
CR LF, CR and LF all break lines; joining lines makes each block one paragraph, blank lines can
go, and runs of spaces can become tabs. The text becomes area type filling `rect`, the replaced
object's bounds, or the artboard less a 36 pt margin (type reflows rather than scaling), as one
undo step. The place cursor and file.place.info take text files too, and File → Place lists them.
In the app, placing a text file (the Place dialog, a drop, file.place without options) opens the
Text Import Options dialog. Its dropdown rows share a form helper with Document Setup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A linked image (`ImageObject.link`, now a LinkInfo: absolute path, path relative to the document,
size, modification time, hash, page; old path strings still load) keeps a low-resolution preview
(`ImageBlob.proxy`, at most 256 px a side) that a native save writes in place of the pixels of
images only linked images show. Opening a native document reads the linked files again, looking
for each at its path, then relative to the document and by name in its folder: missing files
show their preview, modified ones are read again only with Update Links: Automatically.
`document.open` reports `missingLinks`, `modifiedLinks` and `updatedLinks`.
links.check reports each link's status, links.update reads modified files again and
links.relink points images at another file or at same-named files in a folder (each keeps its
bounds; one undo step). In the app, a document opened without its linked files asks about each
(Replace…, Ignore, Apply to All), then offers to update modified links when the preference asks.
The renderer caches previews apart from the file's pixels; the Eyedropper samples previews in
the object's pixel space.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
document.rasterEffectsSettings now also takes and reports colorModel,
background, antiAlias, clippingMask, addAround and preserveSpotColors in one
undo step (Document.raster_effects, RasterEffectsSettings, next to the
existing raster_effects_ppi so old files load unchanged). The raster effect
images of PDF export and Expand Appearance get the extra room and are
finished by the settings (hard edges, grayscale or bitmap, opaque on
white, white only under the art with a clipping mask). Rasterize takes
them as defaults (params override), and its clipping mask puts the image
in a clip group with the art's outline. The settings have their own dialog
(rasterEffectsSettings, ui.rasterEffectsSettingsDialog) and Rasterize…
opens its options prefilled from them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.info takes and reports the title, author, author title, description,
keywords, rating, copyright status, notice and URL in one undo step, plus
the read-only created (file.new) and modified (every save to a file) dates;
junk values are refused. Document.metadata (DocMetadata) round-trips
natively. PDF export writes author, subject and keywords to the info
dictionary and XMP, PNG export writes tEXt/iTXt chunks, SVG export writes
<desc> and, with the metadata option, every field as Dublin Core. The File
Info dialog (fileInfo, ui.fileInfoDialog) edits it with keyword chips and
star ratings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy keeps the copied objects with every document resource they use
(Session.clipboard: Clipboard {nodes, images, symbols, patterns,
swatches, graphic_styles, char_styles, para_styles, brushes,
source_artboard, source_layers, source_doc}), following symbols,
patterns, graphic styles and text styles into what they use. Every
edit.paste* brings them in within its undo step: identical resources are
reused, missing ones added, different ones of the same name added
renamed (an earlier paste's copy is reused) and the pasted objects
relinked, graphic style links by id. Global and spot swatches that
conflict ask Merge or Add (swatchConflict; clipboard.conflicts lists
them; the app's Swatch Conflict dialog asks one by one, with Apply to
All). Tint swatches, linked gradient stops, Lab spots and the target's
Spot Colors option are honoured; [Registration] is never copied.
Clipboard SVG exports them too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.place {path | name+dataBase64, link, template, replace, at, rect, page, crop} places a
file as one undo step without touching the clipboard. Raster images come in at 100% of the
resolution their file declares (PNG pHYs, JFIF/EXIF, TIFF, BMP; 72 ppi otherwise), linked to
their path by default; SVG becomes one group with its embedded images; a PDF/.ai page or a
native document's artboard becomes one clipped group. The images, symbols, patterns and
swatches the art uses join the document (renamed on conflict). Replace keeps the replaced
object's stacking place and transform; Template puts the art on a new locked template layer
below the current one. file.place.info and image.info report size, ppi and colour mode.
File > Place picks a file and opens the Place dialog (Link, Template, Replace); the web picks
through the inbox. The Control bar shows Linked File/Embedded, the file name, colour mode and
ppi of the selected image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>