Commit Graph

442 Commits

Author SHA1 Message Date
Brandon Thomas 10f1c56a86
Release prep: 0.9.0 and a Linux riscv64 release build (#1071)
- Version 0.8.0 -> 0.9.0 (cargo xtask version set; Cargo.lock refreshed).
- release.yml: new linux-riscv64 job, ported from GridCraft. Cross-compiles
  riscv64gc-unknown-linux-gnu on ubuntu-24.04 against riscv64 -dev packages
  from ports.ubuntu.com (the same set the linux job installs), packages a
  tar.gz, and runs vectorcraft-cli --version under qemu-riscv64. The draft
  release job now needs it, so its tarball is uploaded and in SHA256SUMS.
- packaging/linux/package.sh: CROSS_ARCH / CROSS_TARGET / CROSS_COMPILE /
  EMULATOR support, as in GridCraft.
- docs/releasing.md: list the riscv64 artifact.

Checked locally: cargo check -p vectorcraft -p vectorcraft-cli --target
riscv64gc-unknown-linux-gnu, cargo fmt --check, actionlint, shellcheck.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:47:38 -04:00
bflatastic 2048084396 #1027: PostScript-form .ai files open through their layers
A .ai in PostScript form went only to the PostScript interpreter, which
stopped on the first operator its missing prolog would define
(`Adobe_packedarray`, `Lb`, `Np`): files from Rhino and other CAD apps name
the procsets without including them.

- Such a program is its own editing data: when it has layers
  (`%AI5_BeginLayer`) and no private data, the editing-data reader reads it,
  compared with the page when the program runs, from the layers alone when
  it can't be run. If the layers can't be read either, the error says why.
- The reader takes the legacy ten-operand `Lb`, ignores flatness `i`, and
  uses the bounding box as the page when nothing else says where it is.
- Tests on synthetic files; fuzz fixture and hostile-program layers added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	ROADMAP.md
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-11 06:04:48 +07:00
bflatastic cee73d03a3 #1025: .ai editing data keeps sublayers with global colours and pattern fills
The editing-data reader stopped on `Xk`/`XK` (global process colours) and
on `p`/`P` (pattern fills), so the file fell back to its PDF part, which has
only the top-level layers.

- `Xk`/`XK` read like `Xx`: a global swatch (not a spot ink), linked at its tint.
- `%AI3_BeginPattern` sections are read by a reader of their own into
  pattern swatches (tile from `(name) llx lly urx ury`, art in pattern space);
  `p`/`P` fill or stroke with them, placed by the operator's matrix. A pattern
  it can't read whole still makes a fill with it unreadable, as before.
- Tests on synthetic editing data; fuzz tokens and fixture extended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 06:04:48 +07:00
bflatastic c183c47df3 Review: guides on hidden or locked layers, and through layer commands
- Guides on a hidden or locked layer leave the selection (Selection::prune,
  run after every edit and selection change), their Layers rows neither
  select nor drag them, and guide.move / guide.remove / guide.setLayer
  refuse them; guide.setLayer refuses a locked target layer, as dragged
  rows are refused there.
- A new guide goes where new art goes (the current layer, else the top
  shown and unlocked one; refused when there is none), so it never lands
  on a hidden layer and vanishes.
- Layer commands keep guides with their layers: Duplicate (and Alt-drag
  copies of rows) copy a layer's guides onto the copy, Merge Selected and
  Flatten Artwork put the merged layers' guides on the merged layer and
  Flatten discards a hidden layer's. One shared doc predicate for "the
  layer a guide is on"; the deleted-layer cleanup moves into the doc.
- Tests: engine tests for the above and their undo, a doc check that an
  old guide reads with no layer and none is written, and a headless
  Layers panel test of guide rows (select, drag to another layer, locked).
- Translations for the new message; docs and a ROADMAP progress line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 06:03:48 +07:00
redrun6767 2d2e0c6992 Docs: ruler guides on layers in the parity docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
2026-10-11 06:03:47 +07:00
redrun6767 2972e2e6bf Guides: drag guide rows to another layer; put older guides on a layer
Guide rows in the Layers panel drag (the selected guides when the row is
one of them) onto any row of another layer, which lights up, and go to
that layer in one undo step (guide.setLayer takes indexes). Guides on no
layer, from files saved before guides had layers, go on the top visible,
unlocked layer when a document opens or is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
2026-10-11 06:03:47 +07:00
redrun6767 02e93902b3 Guides: ruler guides as layer objects
A ruler guide goes on the current layer (guide.add {layer}), shows,
hides, locks and is deleted with its layer, is listed in the Layers panel
as a row that selects it, and moves to another layer (guide.setLayer).
Guides on a hidden layer neither show nor snap; those on a locked layer
can't be picked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
2026-10-11 06:03:47 +07:00
redrun6767 53f8516cc6 Docs: panel sets and dock columns in the parity docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
2026-10-11 06:03:25 +07:00
redrun6767 a7279231cb UI: drop panels anywhere in a set or dock column, and on its tabs
A group's upper half inserts the dropped groups right above it and its
lower half right below it, in floating sets and dock columns alike, and a
dock column's room under its groups puts them at its bottom; the edge
bands were too thin to hit, and the one above a dock column's top group
lay outside the column. The tabs a drop stacks panels with now light up
over the panels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
2026-10-11 06:03:25 +07:00
redrun6767 77667945b1 UI: dock floating panel sets as dock columns
A floating set dropped on the left edge of the icon column or of a dock
column docks as a new dock column there, building a multi-column dock.
Its groups stack, take tabs and tear out as in a floating set; the strip
along its top floats the column again and its × docks its panels where
they live. Popped-out icon panels open left of the dock columns. Columns
are kept in preferences and workspaces; window.panel.dock takes column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
2026-10-11 06:03:25 +07:00
redrun6767 faa3678a3f UI: stack floating panel groups into sets
Floating panel groups dropped on another group's top or bottom edge join it
in a set: the groups stack top to bottom, move together by the top group's
title bar (whose × docks them all), and a group dragged by its tab strip
leaves the set. A double-click on a tab collapses a group to its tabs.
Sets and collapsed groups are kept in preferences and workspaces, and
window.panel.float takes below, above and collapsed.

Behaviour from public documentation:
https://helpx.adobe.com/illustrator/using/workspace-basics.html

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
2026-10-11 06:03:25 +07:00
redrun6767 d93e4e9cf1 Docs: Shift-selecting guides with art in the parity doc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CC5DiGJEZ5ADaQMBEQk4W1
2026-10-11 04:35:16 +07:00
bflatastic 148ef1870a #1004: a connected app's screenshot renders the artboard asked for
The desktop app's ui.render (MCP `screenshot` when connected) always rendered the first
artboard and ignored `artboard`, which the headless backend honours.

It now renders the 0-based `artboard` (the first without one, an error for one that doesn't
exist), as headless does. control-protocol.md and the control.rs list show the parameter.

The test drives ui.render through the control channel on a 64 x 64 and a 96 x 48 artboard:
no index and 0 give 64 x 64, 1 gives 96 x 48 (48 x 24 at scale 0.5), 2 is an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	ROADMAP.md
2026-10-11 03:51:19 +07:00
bflatastic 178f99d60e #983: supersample Art Optimized edges in exports
Asset Export, File › Export, Save for Web and Object › Rasterize drew
each shape with its own smoothed edge, so where two shapes met edge to
edge (Divide, Shape Builder, abutting art) both half-covered the pixels
on the line and the background showed through as a thin ghost border.

With Art Optimized (the default) these now supersample, as the option's
name in Illustrator says: the art is drawn with hard edges at four times
the size (three or two times for large images, within a 40-megapixel
budget and the renderer's 65,535-pixel side) and each block averaged
into one pixel. Pixels shared by two shapes are fully owned by one or
the other at the finer size, so no seam is left, and the outer edges
are still smooth. The canvas keeps shape-by-shape smoothing for speed;
None and Type Optimized are unchanged.

RenderOptions::supersample turns it on (exports and Rasterize set it).
A render test checks two triangles sharing a diagonal: a seam with
shape-by-shape smoothing, none supersampled, and the factor's limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 03:48:03 +07:00
bflatastic 59c890df9e Part of gap 2: Effect › Brush Strokes (Accented Edges, Angled Strokes, Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes, Sumi-e)
Eight raster effects on the pixel-filter pipeline
(vectorcraft_effects::pixel::brushstrokes), under a new Effect › Brush
Strokes submenu between Blur and Distort in the Raster Effects section:

- Accented Edges (edgeWidth pt 1..14, default 2; edgeBrightness 0..50,
  38; smoothness 1..15, 5): the edges of the softened object painted
  towards white chalk at high brightness or black ink at low brightness;
  Smoothness also leaves the faint edges out. The shape stays as it was.
- Angled Strokes (directionBalance 0..100, 50; strokeLength pt 3..50, 15;
  sharpness 0..10, 3): light areas in right diagonal strokes, dark areas
  in left diagonal ones; 0 makes every stroke a left diagonal, 100 every
  stroke a right diagonal. Sharpness brings detail and bristles back.
- Crosshatch (strokeLength pt 3..50, 9; sharpness 0..20, 6; strength
  1..3, 1): the detail kept under pencil hatching along both diagonals,
  Strength passes of it, the edges roughened.
- Dark Strokes (balance 0..10, 5; blackIntensity 0..10, 6;
  whiteIntensity 0..10, 2): short strokes towards black in the dark
  areas, long strokes towards white in the light areas.
- Ink Outlines (strokeLength pt 1..50, 4; darkIntensity 0..50, 20;
  lightIntensity 0..50, 10): black outlines over the edges, ink hatching
  in the shadows, shadows darkened and highlights lightened.
- Spatter (sprayRadius pt 0..25, 10; smoothness 1..15, 5): each pixel
  shifted by noise up to 0.4 x the radius; Smoothness makes the grains
  larger and smoother.
- Sprayed Strokes (strokeLength pt 0..20, 12; sprayRadius pt 0..25, 7;
  strokeDirection Right Diagonal, Horizontal, Left Diagonal or Vertical):
  strokes along the direction, scattered by up to 0.3 x the radius.
- Sumi-e (strokeWidth pt 3..15, 10; strokePressure 0..15, 2; contrast
  0..40, 16): wet strokes, ink bleeding from the darks along paper
  fibres, more ink with pressure and an S-curve contrast for rich blacks.
  It works in value only: hues and saturation stay, and so does the
  object's shape (no fringe in the transparency).

A stroke averages the colour along a segment through the pixel (16
samples at most, the art softened first when they lie more than a pixel
apart); strokes lie in rows around the object's centre, each row
starting at random and each stroke shifted along itself, with streaked
noise for the bristles. Everything is in document space, so the effects
look the same at any zoom or resolution, move with the object and draw
the same every time; outsets and reaches are exact. On a 2000 x 2000
raster in release the slowest settings take 0.3-2.0 s (Crosshatch at
50 pt with 3 passes the most).

Shared helpers instead of copies: edges::sobel (Glowing Edges now uses
it, unchanged), distort::displace and texture::Tones are pub(super).

Catalogue rows with parameter docs, menu entries, Sprayed Strokes'
direction dropdown, translations (de, es, fr, it, ja, ru, uk, zh-hans,
zh-hant; menu labels in cs and pt-br), tests (parameters and clamping,
outsets and reaches, each filter and option showing, chalk and ink on
the edges only, shadows and rich blacks, Sumi-e keeping the shape and
hues, hatching keeping the tone, strokes smearing along their direction,
Spatter's radius, resolution and move invariance, canvas, multithreaded,
PDF and SVG, the command sweep with junk parameters, the menu) and
ROADMAP.

Behaviour from public documentation only:
https://helpx.adobe.com/photoshop-elements/using/brush-stroke-filters.html
https://www.computerhope.com/jargon/p/photoshop-sprayed-strokes.htm

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:19:26 +07:00
Brandon Thomas d4b0a5479b
Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#994)
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, hardware, UI and localization parity docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:11:14 -04:00
alpha-rock c36b6976e3 M9: add live Revolve with artboard rotation and lighting controls 2026-10-10 23:44:46 +07:00
bflatastic 199a464140 #964: start again when the first frame never reaches the screen
With High Performance chosen on a hybrid laptop, the window's first
frame never reached the screen: no error, no panic, the app stayed in
the background without a window and the log stopped after "rendering
with". The start-up recovery of #502 only saw failures.

A watch thread now gives the first frame 20 seconds from the app's
creation (after the files named at the start opened). When the second
frame hasn't begun by then, so the first was never presented, the app
starts again without that adapter, as it does after a graphics failure,
and this process ends. A window minimized at the start isn't counted.
When the hung processor was chosen in Preferences, the status bar's
note after the restart points to Automatic.

docs/development.md explains it and how to recover from a Graphics
Processor choice that doesn't work; ROADMAP Last updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:38:58 +07:00
dwetscher 194968bc27 German interface language (de)
Add the German catalog: every menu string, every `tl!` literal, the plural
forms and the status and error messages (3,818 rows), written from the meaning
of the English labels in the vocabulary German-speaking users of vector
illustration software know (Zeichenfläche, Farbfelder, Kontur, Fläche, Pfad,
Ankerpunkt, Ebene, Verlauf, Deckkraft). Conventions are in the header of
`de.tsv`.

- Register `de` ("Deutsch") in `i18n::LANGUAGES` as a complete language; every
  `de-*` locale (`de-DE`, `de-AT`, `de-CH` …) resolves to it.
- Tests: German joins the menu-label, menu-string, `tl!`-literal and message
  coverage checks; `german_reads_as_german` pins the core vocabulary, the
  plurals, a nested message, the joined "…format" style names and the theme
  context ("Erscheinungsbild" vs the Appearance panel's "Aussehen"); ä ö Ä Ö ß
  are checked in the bundled UI fonts. The "unsupported locale" cases use
  Dutch now that German is supported.
- README, ROADMAP and docs/development.md list German.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:28:15 +07:00
bflatastic 6315086f96 Review: System Title Bar only where it applies, docs, title repaint
- Preferences hides System Title Bar on macOS and the web, where it did
  nothing (it fell through to the plain checkbox row), and lists it with
  the window options instead of under Language.
- The next-launch note reuses "Applies the next time VectorCraft starts."
  instead of a second wording in every catalog.
- A new window title asks for one more frame: AppKit lays the title bar
  out again for it, and the next frame centres the window buttons (#968).
- The menu bar drops the bold branch for the VectorCraft menu it no
  longer shows; theme is imported where the tests use it.
- Docs: Language is Preferences › User Interface › Language (VectorCraft
  › Language on macOS); the control protocol describes System Title Bar;
  ROADMAP Last updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:15:21 +07:00
rafi612 bd895a1de7 Remove the in-window VectorCraft menu (File first, Exit quits, Mac unchanged) 2026-10-10 21:01:38 +07:00
BFlat 18c9d27c8d
Merge pull request #947 from IHAGI-c/fix/graph-blank-values
Graph Data: blank cells are left out, not drawn as zero; quoted numbers are labels
2026-10-10 18:16:26 +07:00
IHAGI-c 35c4a428ff Graph Data: blank cells are left out, not drawn as zero; quoted numbers are labels
A graph's data can now hold blank cells: an empty CSV cell, a null in
`rows` (any cell that isn't a finite number), the rest of a short row,
or a row that has only a label.

- Columns, bars and stacked segments leave a blank cell out instead of
  drawing a zero-height mark.
- Line and scatter graphs break their lines around it and draw no
  marker there; the value axis ignores it.
- Area, pie and radar graphs draw it as 0, which is what it adds to
  them.

The help pages don't describe blank cells, so this is our own choice:
a cell with no value has nothing to draw, which also matches how a
series with a missing year reads in a report.

In the file a blank cell keeps a 0 in `rows` and is listed in a new
`blanks` field ([category, series] pairs, written only when there are
any). A version that doesn't know blanks ignores the field and reads
the zeros, so files still open there, and files without blanks are
unchanged.

A number in straight quotes is a label, as the help page on graph
labels says (a year as a category name, for one). The CSV reader used
to strip the quotes first and read it as a value. Label columns are now
decided for the whole column: once any row starts with a label, every
row's first cell is its label, so a row with an empty label no longer
shifts its values one place left. The data window writes a header row,
labels that are empty, read as numbers or have spaces around them in
quotes, and blanks as empty cells, so they come back as they were.
graph.setData {} returns blanks in `rows` as null, so the rows it
returns can be edited and sent back. A label-only row keeps its place
with blank values; a row with neither a value nor a label is dropped,
and a first row that is a label followed by empty cells (`Year,,`) is
still read as a header with no series names.

The data grid is built in full for drawing and for the data window, so
it now stops at 10,000 categories of 256 series, which keeps a file
with one very long row from allocating without bound.

Behaviour (quoted labels): Add graph labels and data sets:
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/add-graph-labels-and-data-sets.html
2026-10-10 17:59:45 +07:00
Reinhard Schuerer 79dd751d3b Draw non-native art in .ai editing data from its PDF
A /ForeignObject (non-native art: the content of a placed PDF, which
Illustrator shows but doesn't edit) was left out with a note. The
letterhead of #861 keeps its fan of swooshes that way: a radial gradient
in a 3-point PDF, scaled 271 times and clipped by the swoosh paths, so
the page lost its main graphic.

The reader now decodes the object's ASCII85 /Data, has the PDF importer
read the PDF, and fits its art to the object's box as a group named
"Non-native art": /Bounds measured from /Origin with y down, then
/RTransform, which lands exactly on the clipping paths around both
objects of that file; the PDF's content is kept in its own y-up
coordinates, so it is flipped into the box. Ids are the document's and
the PDF's swatches stay unlinked; its images join the document by
content key. A PDF that can't be read is left out with a note, as
before.

The EPS crate now uses the PDF crate (same layer; allowed in the layer
check like render and metafile).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ZwZjADUZP4VzvrFGpiZch
2026-10-10 17:58:55 +07:00
bflatastic d57134a308 Fix #938: Live Corner widgets on every selected path
The corner widgets only showed when the selection was exactly one path.
Now every selected path that has them shows its widgets: live rectangles
and polygons with the Selection tool, any path with Direct Selection,
each with its own corners (all of a path selected whole, the
Direct-Selected ones otherwise). Dragging any widget rounds every shown
corner of every path in one step, Alt-click cycles their kind, and the
red limit outline covers them all; a double-click opens Corners for the
path it was on. Each widget is drawn in its own layer's colour.

object.setLiveShape takes items: [{id, corners?}], several objects each
with its own corners, which the drag sends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:19:52 +07:00
bflatastic d62ecfe33a Fix #926: the Libraries panel's search, groups and library export/import
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>
2026-10-10 17:17:01 +07:00
bflatastic 0eb4ce5e98 Fix #918: PSD and PSB files open and place as their merged image
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>
2026-10-10 16:09:41 +07:00
bflatastic 11beb8fff1 Fix #925: linked files changed in another app update when the window comes back
Preferences › File Handling › Update Links only acted when a document
opened, so a linked image edited in another app while VectorCraft stayed
open kept its old pixels until Update Link in the Links panel, even with
Automatically chosen.

When the window comes back to the front, the active document's links
are now checked. With Automatically, changed files are read again; with
Ask When Modified, they are offered for update, once per change of a
file (keyed by path, size and modification time, since two saves can
share a time); with Manually, the Links panel shows them as modified.
Nothing is asked while a dialog is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:49:08 +07:00
bflatastic 20e5a746bd Part of #885: the Transform effect's dialog takes Transform Each's controls
Effect › Distort & Transform › Transform… was a plain list of fields.
It now has Transform Each's layout: Scale and Move with sliders, Rotate
with an angle dial, then Copies, Reflect X and Y, the reference point and
Random, kept with the effect so editing it from the Appearance panel
shows them again.

- The effect scales, rotates and reflects about the chosen point of the
  9-point grid (`reference`, the centre by default).
- Random takes a share of each value, drawn from the object's id: each
  object varies its own way and keeps its result on every redraw; a
  group's members move together.
- Copies stop before one reaches past 1e9 pt, and the scale and move are
  capped, so large values can't overflow.
- Transform Each and the effect share the sections and options code; the
  angle dial is a new widget.
- A plain number field now takes the focus when it is a dialog's first
  field, so both dialogs open with Scale › Horizontal ready for typing.

Transform Objects, Transform Patterns and Scale Strokes & Effects are not
in the effect yet: the geometry effects reshape paths only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:32:10 +07:00
bflatastic 9f875050ef Fix #832: --automation-read-root and --automation-write-root confine the MCP server's files
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>
2026-10-10 14:27:24 +07:00
bflatastic 77e2a30c6d Part of #394: Actual Size shows print size whatever the display scaling
Display Print Size at 100% Zoom divided an assumed 96 pixels per inch by
the display's scale factor, so on a scaled display (150%, Retina) a
document inch came out a fraction of an inch. Nothing measures the
screen's real density, so an inch is now 96 screen points, the reference
density display scaling is set against, at any scale factor. The unused
screen_ppi and pixels_per_point fields go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:25:50 +07:00
Fabio William Conceição a7faeb4299 Part of #394: Display Print Size at 100% Zoom drives Actual Size. 2026-10-10 14:25:44 +07:00
Fabio William Conceição 89812275d7 Part of #394: Show Font Names in English labels the font menus. 2026-10-10 14:10:31 +07:00
Sawizzle e15ef47094
docs: register the MCP server from an installed release (#856)
* docs: register the MCP server from an installed release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: drop the third-party link; the Windows path is the default install folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: drop the third-party link; the Windows path is the default install folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: drop the third-party link; the Windows path is the default install folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:08:20 +07:00
bflatastic 990d869897 Part of #812: more tools open their options on a double-click
Double-clicking a tool's button (or tool.options {tool}) now also opens:
- Artboard tool: Artboard Options of the active artboard (the Artboard
  tool's while it is in use, else the window's);
- the graph tools: Graph Type for the selected graph (an error without
  one), the dialog Object > Graph > Type opens;
- the Symbolism tools: Symbolism Tools Options with the brush the eight
  tools share (Diameter, Intensity, Symbol Set Density);
- Magic Wand: the Magic Wand panel.

The Object > Graph dialogs' opener is shared with the toolbar. The new
labels are translated in the eight complete catalogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:00:21 +07:00
bflatastic ae27d899b8 Part of #812: Flare Tool Options last and open from the toolbar
The Flare tool keeps its options (Center: Diameter, Opacity, Brightness;
Halo: Growth, Fuzziness; Rays: on/off, Number, Longest, Fuzziness; Rings:
on/off, Path, Number, Largest, Direction) as persistent tool options,
saved with the preferences. The next flare dragged out uses them (the drag
still sets the centre and diameter, and Up/Down the ray count), a click
with the tool opens them filled in, and double-clicking the tool (or
tool.options {tool: "flare"}) opens them to edit: OK keeps them and draws
nothing. Before, the click dialog was the generic parameter form with
fixed defaults and the drag always used the command's defaults.

The new Flare Tool Options dialog lays the four groups out in two
columns; Rays and Rings have their checkboxes, and every value is kept
within its range. Its labels are translated in the eight complete
catalogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:00:21 +07:00
bflatastic 7c0ce8a382 Part of #812: polygon properties in the Properties and Transform panels
A selected live polygon now shows Illustrator's Polygon Properties in the
Transform panel and the Properties panel's Transform section: side count,
angle, corner type and radius, radius, side length and Make Sides Equal
(enabled once an uneven scale has made its sides differ). Before, only the
side count and corner radius were there.

object.setLiveShape takes polygonAngle (degrees counterclockwise),
polygonRadius and sideLength (points, centre to vertex / side) and
makeSidesEqual, which drops the uneven scale and shear and keeps the
polygon's centre, angle and mean radius. Each call is one undo step; a
radius or length that isn't a number above 0 is an error. A star stays a
plain path, as in Illustrator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:00:21 +07:00
bflatastic 1db82cbc02 Fix #798: the Curvature tool edits any selected path
The Curvature tool only edited the path it was drawing. It now edits any
selected path, whatever drew it, keeping its shape except where edited:
a click on a point makes it the current (direct-selected) point and a
drag moves it, re-curving only its two segments (the neighbours keep
their handle directions); Alt-click or double-click toggles it smooth or
corner and Delete removes it; a click on a segment adds a smooth point
without changing the shape, and a drag moves it in the same undo step.
With an end of an open path current, clicks go on from it (the old
segments keep their shape) and a click on the other end closes it.

The edits go through a new path.curvatureEdit command (move, insert,
extend, close), path.convertAnchor and path.removeAnchor, one undo step
each. The tool now claims Delete/Backspace while it has a point to
remove, so the key no longer deletes the whole path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:12:10 +07:00
bflatastic b767e850ed Part of #785: run and command_batch steps can use earlier results
A run (vectorcraft-cli run, the MCP command_batch tool) had no way to use
an id an earlier step returned. A parameter string that is exactly
$N.path or $last.path now takes that value from step N's result
(0-based), e.g. {"id":"$1.id"}; $$ starts a literal $, and a
reference to nothing fails the step. Objects given to a command are now
used as given: an ids element or id that isn't an object id (or an
unresolved reference) fails instead of the command acting on the
selection. text.setRangeStyle styles the selected type object when no
id is given, as text.setStyle does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:28:03 +07:00
bflatastic 9a0183a02d Fix #831: color books (.acb) open as swatch libraries
Color books such as the Pantone books a user owns (.acb) couldn't be
opened. They are now read (never written), from public descriptions of
the format: their RGB, CMYK or Lab colors, named with the book's prefix
and suffix ("PANTONE 185 C"), as spot colors unless the book marks
them as process colors, leaving out the pages' unnamed padding. They
open from Window › Swatch Libraries › Other Library…, File › Open and
the user library folder like .ase files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:27:58 +07:00
bflatastic de564bc24f Fix #806: Windows doesn't load Vulkan drivers unless asked
Creating a Vulkan instance loads every installed Vulkan driver into the
process, and a faulty one (Intel's igvk64.dll) crashed VectorCraft
before its window appeared. On Windows the window now starts with
Direct3D 12, falling back to OpenGL, as PdfCraft does; WGPU_BACKEND
(e.g. vulkan) still chooses others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:27:51 +07:00
bflatastic 3198abe2fd Part of #637: area type, threaded type and type on a path from the text document
Illustrator EPS and .ai type is made whole from the file's text
document, shown or hidden: point type, area type (a story in several
frames as threaded type that flows when the file opens) and type on a
path, with its fonts, size, leading, tracking, scaling, baseline shift,
fill and stroke colours (grey, RGB or CMYK), alignment, indents and
paragraph spacing. The page's drawing of that type, often in pieces, is
left out; where the text document can't say, type that shows still
comes from the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:27:46 +07:00
bflatastic 2762f61e83 Review fixes for #777: report sections per document, fmt, docs and ROADMAP
The report kept a placed document's LINKED FILES and FONTS inside its parent's list. Each document
now gets its own sections, the packaged one first and unchanged (no Document: line), then each
placed document's, headed by its file. Drops a debug_assert and an unneeded mut, runs rustfmt, and
documents the nested packaging in docs/mcp.md and the ROADMAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:32:12 +07:00
dotCooCoo 11cf254a07 Fonts: a folder search finds .suit suitcases and skips font files over 256 MB
Add Fonts adds only files a folder search found.

- macOS declares .suit as the extension of a font suitcase
  (com.apple.font-suitcase), and the font scan reads a suitcase
  whatever its name. A search considered only suitcases without an
  extension. It now also considers files whose extension is .suit, in
  any letter case, and reads one only when it is a suitcase font.
- Add Fonts copies at most 256 MB a file. A search also read larger
  font files, and a search that found one for a font could end before
  it found a smaller copy. A search now skips font files over 256 MB.

Docs: docs/mcp.md.
2026-10-10 08:42:32 +07:00
dotCooCoo 7b7e10e82e Fonts: a folder search skips suitcase fonts whose resource fork is over 8 MB
A folder search reads at most 8 MB of each font file. The suitcase
reader reads a suitcase font's resource fork whole, up to 256 MB.
Before this change, the search applied its limit to each font only
after that read. One search thread could read 256 MB from a single
suitcase and hold a copy of its fonts as well. The search now reads a
suitcase font only when its resource fork is 8 MB or less. The font
scan still reads each suitcase font's resource fork whole, up to
256 MB.

Docs: docs/mcp.md.
2026-10-10 08:42:32 +07:00
dotCooCoo 5709e636a5 Fonts: missing fonts are listed when a document opens, with Find Fonts and a folder search
When a document opens with fonts that aren't available, fonts used
only in its symbols included, VectorCraft lists them in a Missing
Fonts dialog after the missing linked file questions, one document at
a time, with Find Fonts and Close. Find Fonts opens Type › Find Font
on the first missing font of the layers' type, to replace it. After
Find Fonts or Close the dialog doesn't open again for that open; when
another dialog replaces it, it opens again once that dialog closes.

Search Fonts Folder (the Fonts folder File › Package writes next to
the document) and Find in Folder… search a folder and its subfolders
for the fonts' files, and Add Fonts copies the files the user chooses
into a Fonts folder next to the preferences. The desktop app and
vectorcraft-cli read that folder with the system's font folders. Type
› Find Font has a Find in Folder… button too when fonts the document
uses are missing. The web app shows no dialog; a status message says
how many fonts aren't available.

- text.missingFonts lists each missing family and style once,
  whatever version the document's type names, and leaves out CSS
  generic families such as sans-serif. text.findFontFiles searches a
  folder on up to 8 threads and returns at once; text.findFontFiles {}
  reports the search, and {stop: true} stops it. A search reads only
  what the font scan reads of font files (their table directories and
  name, fvar and OS/2 tables) and, on macOS, of suitcase fonts, and
  lists a file for a font only when the file makes the font resolve
  exactly. It does not enter app packages, the Program Files and
  ProgramData folders, the system's folders at the root of a volume,
  the Shared and Public folders in Users, a home folder's Library,
  AppData, Applications, snap and dot folders, a Library folder's app
  data folders or the folders the environment names for apps, and it
  skips other hidden folders and does not follow links below the
  picked folder. It ends after 60 s by default, after 10,000,000
  entries listed, with more than 1,000,000 folders waiting, after
  10,000 font files read or 1,000 kept, or once every font has a file;
  Stop and Close end it too.
- text.addFontFiles copies only files the last search found. Of
  those, it copies only regular .ttf, .otf, .ttc, .otc, .woff and
  .woff2 files and suitcase fonts with their resource fork, at most
  256 MB a file and 1 GB in all. It never replaces a file: a file with
  the same contents is kept, and when another file has that name, the
  copy gets a number (Name 2.otf). The fonts are then scanned again,
  and type set in them redraws.
- ui.missingFontsDialog and ui.findFontsInFolder open the dialog for
  agents; docs/control-protocol.md lists its fields.
- File › Package gives a font file whose name has no font extension
  the extension that matches its data (.ttf, .otf or .ttc).
- vectorcraft-text adds set_app_font_dir, which system_font_dirs
  reads, WantedFonts, which reads a font file's names as the font scan
  does, is_font_file and is_suitcase. #762 split its face reader, which
  now takes read budgets: file_face_names_within tries the suitcase
  reader first, and reader_face_names_within applies the budgets to
  each font. vectorcraft-format adds write_new_with.

Docs: docs/mcp.md, docs/control-protocol.md, ROADMAP.
2026-10-10 08:42:32 +07:00
Reinhard Schuerer f2ba41d1c1 Open .ai files whose non-native art has ASCII85 lines starting with %_
A letterhead saved by Illustrator 28 without PDF compatibility wouldn't
open: "its editing data can't be read (it ends inside a layer)". Its
layer starts with a /ForeignObject (non-native art: the content of a
placed PDF), whose /Data entry is the PDF as ASCII85 in comment lines.
ASCII85 includes `_`, so about one line in 85 starts with `%_`, which
the lexer reads as hidden tokens; a `(` in such a line opened a string
that swallowed the dictionary's end, the rest of the art and the
layer's LB.

The reader now skips to the data's `~>` after `/Data ,` in a
/ForeignObject, as it already did for a /Binary dictionary's ASCII85,
and leaves the non-native art out with a note (nothing draws its PDF
from the editing data yet).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ZwZjADUZP4VzvrFGpiZch
2026-10-10 08:25:35 +07:00
BFlat 57932f1204
Merge pull request #748 from storytold/fix/735-editing-data-drift
Fix #735: Illustrator EPS and .ai files match their page in Fit to Artwork Bounds and the page check
2026-10-10 08:02:48 +07:00
bflatastic c599522061 Fix #735: Illustrator EPS and .ai files match their page in Fit to Artwork Bounds and the page check
- 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>
2026-10-10 06:51:50 +07:00
BFlat f203ae6334
Merge pull request #851 from rschuerer/fix/windows-display-gpu
Fix #849: draw on the GPU that drives the primary display
2026-10-10 06:41:47 +07:00