Commit Graph
4353 Commits
Author SHA1 Message Date
Dylan M. Taylor ae663f53aa Merge pull request #2923 from dylanmtaylor/docs/add-sonar-coderabbit-badges
docs: add SonarCloud and CodeRabbit badges to README
2026-10-20 11:31:29 -04:00
Brandon ThomasandClaude Opus 5.5 ad0566943a Release 0.14.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.14.0
2026-10-19 22:18:48 -04:00
BFlat 9e7929ee89 Merge pull request #3173 from KaboomFox/p6-effect-paint
Plug-ins: live effects read their object's fills and strokes (G17)
2026-10-20 08:49:13 +07:00
Louise FoxandClaude Opus 5.5 2e4666a364 Plug-ins: live effects read their object's fills and strokes (G17)
A live-effect plug-in was given only its object's geometry. An effect
that draws as wide as the stroke it sits on, or tells its fills apart,
had to ask for them again as parameters, which then went stale when
the paint changed.

A live effect's input object now holds the object's fills and strokes
too, read only and in the form a filter gets them (one encoder,
objects::paint_lists, for both), and _context.item says where the
effect sits: "object", or {"fill": i} / {"stroke": i} for an effect in
a fill's or a stroke's own effects. effects::GeomContext carries the
object's appearance and the item, and the plug-in cache keys on the
paints, so a change of paint runs the plug-in again. Hosts from before
this give geometry only; nothing in the output changes.

Expand worked a fill's geometry effects out in the object's context, so
a plug-in effect there would be told "object" and a gradient fill was
expanded into a shape the canvas doesn't draw: fill_shape now gives them
the fill's context, as drawing does (expand.rs).

Siblings named with a declared prefix, the other half of such context,
are left out: the effected geometry is worked out from the object alone
wherever it is drawn, measured, hit-tested or expanded.

wat::when_given is a plug-in whose output depends on whether its input
or parameters hold a text. tests_plugins.rs uses it to check that an
effect sees the stroke's width and the fill, runs again for a thinner
stroke, and on a second stroke is told so; effect.rs checks the item
counting; a gradient fill under such an effect is drawn and expanded
into the same triangle. CRAFT_PLUGIN_PAINTS_WASM=<folder> saves a test
effect to try in the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-20 08:39:41 +07:00
BFlat ee4564c586 Merge pull request #2954 from nrq6993-a11y/stream/vc-feat-g2-sketch-note-paper
G2: Effect › Sketch › Note Paper
2026-10-20 08:39:16 +07:00
bflatastic c9d769ad4e Note Paper: grey tones on white like the other Sketch filters, rustfmt (#2954) 2026-10-20 08:30:05 +07:00
bflatastic 62e8474caa i18n and docs: Note Paper's catalog rows, raster effects 46 of 57 (#2954) 2026-10-20 08:29:26 +07:00
nrq6993-a11y 301783c480 G2: Effect › Sketch › Note Paper raster filter (#2954) 2026-10-20 08:28:51 +07:00
BFlat 64699f5212 Merge pull request #3172 from KaboomFox/p2-rich-params
Plug-ins: labels, units, groups, help and conditions for parameters (G17)
2026-10-20 08:27:36 +07:00
BFlat bea46e63ff Merge pull request #3171 from KaboomFox/p1-exporter-plugins
Plug-ins: exporters write file formats of their own (G17)
2026-10-20 08:27:26 +07:00
Louise Fox c755909d67 Plug-ins: labels, units, groups, help and conditions for parameters (G17)
A plug-in's dialog labelled each field with its parameter's name spaced
out ("Line_spacing_mm:"), showed numbers without their unit and listed
at most 64 parameters in one column. A plug-in with many settings, such
as a generator's or an exporter's, could neither explain nor sort them,
and its dialog showed settings that don't apply to the choices made.

Each parameter may now say how dialogs show it, in keys that hosts
from before them ignore (plugins::dialog):

- label; unit, after the value ("0.25 mm"); help, on the label; and
  helpUrl, an https:// page opened from a button after the field.
- group: the parameters outside any group come first, then each group
  as a section that folds, in the order the groups first appear, the
  fields lined up down the dialog.
- visibleWhen: {"pattern": ["lines"]} shows a parameter only
  while each checkbox or choice it names, declared before it, has one
  of the values listed and is shown itself. Manifest::visible decides
  every parameter in one pass, in declaration order. A hidden parameter
  still goes to the plug-in with its value, so a run never depends on
  what the dialog showed.

Two types join number, int, bool and choice: string, a text field cut
to maxLength, and numberList, numbers typed separated by commas or
spaces, each clamped, minItems to maxItems of them. A plug-in declares
up to 256 parameters, at most 64 outside groups. The filter, live-effect
and exporter dialogs use every key. The Appearance panel's editor of a
plug-in effect uses the labels, units, help and conditions, and reads a
typed number list as the dialog does.

dialog.rs tests parsing, the round trip of a manifest written out,
sections, visibility (also of 256 overlapping conditions, which a
recursive check would take forever on) and the keys refused;
manifest.rs the new types and limits. tests_plugin_fields.rs draws a
dialog with kittest: labels and units, sections in order with their
fields lined up, visibleWhen hiding and showing rows, and the help
button opening its page.
appearance.rs checks the editor's labels, units, conditions and a
typed number list, and form.rs units after numbers (a unit starting
with a digit is spaced from it) and typed lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un

# Conflicts:
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-20 08:17:53 +07:00
Louise FoxandClaude Opus 5.5 30f372a220 Contributors: credit KaboomFox by their GitHub profile name
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-20 08:17:48 +07:00
Louise FoxandClaude Opus 5.5 24936cd006 Plug-ins: exporters write file formats of their own (G17)
A plug-in could filter objects or reshape them as a live effect, but
nothing it made could leave the app: pen plotters, cutting machines and
laser cutters read formats VectorCraft doesn't write. An exporter is a
third kind of plug-in ("kind": "exporter", with the file "extensions"
it writes), in the same sandbox and with the same ABI v1 exports.

- It adds a write-only format, plugin.<id>. document.formats lists it
  with its plug-in id, and document.export writes it by that id, or by
  a path ending in one of its extensions that no built-in format has;
  "params" carries its parameters, checked against its manifest as a
  filter's are.
- File › Export › Export As lists it after the built-in formats, with
  Use Artboards greyed out, since the plug-in writes the whole
  document. One with parameters asks for them in a dialog generated
  from its schema (pluginExport) once the file is picked.
- The plug-in is given the native file's JSON (format::json_part:
  images of 8 KiB or more without their data) and _context {mode:
  "export", extension, name}. It returns {"dataBase64", "warnings"?}
  or {"error"}. The host writes the file, and nothing when the plug-in
  declines or returns bad data.
- An export gets 10 G instructions plus 4,000 per byte and 300 s.
  Background Export's Cancel stops it between two slices of that
  budget (Error::Cancelled), and a cancelled live-effect run is not
  cached as a failure.
- plugin.run and Object › Plug-ins say where an exporter is instead of
  calling it a live effect.

engine/tests/plugin_export.rs reads back what an exporter is handed
with wat::echo_exporter, an exporter whose file is its input or its
parameters (its base64 encoder is written in WAT). The document it is
given is document.save's JSON, without the large image's data, and its
parameters arrive defaulted, clamped and with the file's name and
extension. It also covers listing and removal, built-in extensions
winning, selectedOnly, declines, bad base64, missing data, warnings,
and Cancel stopping a plug-in that never returns. It is a test binary
of its own: installed plug-ins are process-wide, and the format-list
unit tests check exact lists. dialogs/tests_plugin_export.rs drives
Export As headlessly; plugins/tests/abi.rs covers the budget, the
parameters and the echo exporter; format/tests/native_v4.rs covers
json_part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-20 08:17:48 +07:00
BFlat 1dda8d9e67 Merge pull request #2268 from danielmigueltejedor/fix/gnome-window-decorations
Fix GNOME/Wayland window controls
2026-10-20 07:40:12 +07:00
bflatastic 20557277c2 Packaging: package.sh stays executable (#2268) 2026-10-20 07:34:47 +07:00
Daniel 050f68a030 fix(linux): enable font-free Wayland window controls 2026-10-20 07:34:46 +07:00
BFlat 39fdaf7db8 Merge pull request #2565 from dotCooCoo/feat/artboard-size-applies-to-selected
Artboards: Width, Height, Preset and Orientation apply to every selected artboard
2026-10-20 07:32:56 +07:00
BFlat 2544d7f090 Merge pull request #3087 from Zaxaerith/docs/2980-typography-lettering
docs: add typography and custom lettering guide (#2980)
2026-10-20 07:32:40 +07:00
BFlat fd69aa0623 Merge pull request #3029 from Zaxaerith/docs/tiled-printing-guide-2857
docs: make tiled poster printing discoverable (#2857)
2026-10-20 07:32:24 +07:00
BFlat 5dd86dc19c Merge pull request #2408 from dotCooCoo/feat/paste-ai-editing-data
Clipboard: a copied .ai or .ait file pastes the objects of its editing data
2026-10-20 07:32:09 +07:00
dotCooCoo 7617fcd536 Artboards: Width, Height, Preset and Orientation apply to every selected artboard (#2565) 2026-10-20 07:23:49 +07:00
Zaxaerith c3083e245d docs: add typography and custom lettering guide (#2980) 2026-10-20 07:23:47 +07:00
Zaxaerith 0688ad04d7 docs: document tiled poster printing (#2857) 2026-10-20 07:23:47 +07:00
dotCooCoo 89986d01bf Clipboard: a copied .ai or .ait file pastes the objects of its editing data (#2408) 2026-10-20 07:23:40 +07:00
BFlat 4cd77a7a18 Merge pull request #3110 from KnottyDyes/squad/cli-install-docs
Docs: install the CLI and register it with MCP clients
2026-10-20 07:19:18 +07:00
BFlat 19efa9ea2b Merge pull request #2575 from 00xJS/fix/2213-large-image-clipboard-text
Fix #2213: a copy with a large image no longer puts its SVG on the clipboard as text
2026-10-20 07:18:59 +07:00
KnottyDyes 371d0de798 Docs: install the CLI and register it with MCP clients 2026-10-20 07:10:21 +07:00
00xJ0 548ace24d2 Clipboard: a copy with a large image no longer puts its SVG on the clipboard as text (fixes #2213)
With Include SVG Code on, Copy offered the SVG markup as plain text with every placed image
embedded as base64: a 13 MB photo made 17 MB of text. A paste reads the text first (eframe on
every paste key; on Linux and macOS it is the one format written, and holds_ours reads it again),
so each paste moved it through the system clipboard on the UI thread, and on GNOME the app
stopped responding.

The SVG markup now goes out as text only while the images it embeds total at most 1 MiB; above
that it is offered as image/svg+xml and PNG. Type keeps its text, and vector art keeps its SVG
text at any size (only embedded image bytes count).

Windows 11, Ryzen 5 2600X, 13 MB PNG, real Ctrl+C: the longest UI stall fell from 823-1021 ms
to 133 ms. Not run on Linux.
2026-10-20 07:10:21 +07:00
BFlat a13968e821 Merge pull request #3050 from nrq6993-a11y/stream/vc-feat1-g1-group-selection-shift-deselect
G1: Group Selection Shift-click deselects compound path members
2026-10-20 07:10:07 +07:00
nrq6993-a11yandCursor e2e2ac12a9 Group Selection: fold MoveObject pointer-up into shared arm
Read deselect before idle reset and handle shift-deselect via select.toggle in the shared Up match.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 07:00:49 +07:00
nrq6993-a11yandCursor 61f35cd037 G1: Group Selection Shift-click deselects compound path members
Match the Selection tool: Shift on an already selected hit removes it on
release without a drag, including one path of a compound path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 07:00:49 +07:00
BFlat 891793b3a4 Merge pull request #3167 from sungjea713/assets-tab-scrolls-to-checked
Export for Screens: the Assets tab scrolls to the assets it opens on
2026-10-20 07:00:28 +07:00
sungjea kim 6b24e3cd6a Export for Screens: the Assets tab scrolls to the assets it opens on
Export Selection… and the Asset Export panel open Export for Screens on the
Assets tab with the new assets checked, but new assets land at the end of
the list, so in a document with many assets the checked one sat below the
list's visible part and had to be scrolled to by hand. The list now scrolls
to the first checked asset when the dialog opens on them. It keeps asking
until the row is in view (the dialog's first frame only measures it), then
stops, so the list scrolls freely afterwards.
2026-10-20 06:54:13 +07:00
BFlat 02e12c6935 Merge pull request #2533 from vangel76/feat/2527-sketch-filters
Effects: Sketch › Bas Relief, Charcoal, Photocopy and Stamp (#2527)
2026-10-20 06:53:21 +07:00
Martin Vlach f664564e97 Effects: Sketch › Bas Relief, Charcoal, Photocopy and Stamp (#2527)
Squashed from #2533 onto #2525 (Smudge Stick, Sponge, Watercolor): both sets in PIXEL_EFFECTS (45), the catalogs and the docs, whose counts now read 45/57.
2026-10-20 06:43:50 +07:00
BFlat d14b67e015 Merge pull request #2525 from vangel76/feat/2435-artistic-filters
Effects: Artistic: Smudge Stick, Sponge and Watercolor (#2435)
2026-10-20 06:41:08 +07:00
Martin Vlach 5251246778 Effects: Artistic: Smudge Stick, Sponge and Watercolor (#2435)
Squashed from #2525 (its merges of main dropped).
2026-10-20 06:32:32 +07:00
BFlat 1097ab190d Merge pull request #3166 from sungjea713/export-selection-single-asset
Export Selection: the selection becomes one asset, not one per object
2026-10-20 06:32:06 +07:00
sungjea kim 17ba27a0f6 Export Selection: the selection becomes one asset, not one per object
File › Export Selection… collected the selected objects one asset per
object, so a few ungrouped shapes picked to export together came out as
separate images unless they were grouped first. It now collects them as one
asset of them all (`assets.add {multiple: false}`), the selection exported
as one image; `file.exportSelection {multiple: true}` still makes one per
object, and Object › Collect for Export › As Multiple Assets and the Asset
Export panel's Multiple Assets item are unchanged.
2026-10-20 06:26:11 +07:00
BFlat 4322c825a5 Merge pull request #3057 from nrq6993-a11y/stream/vc-feat-g1-grid-ref-point
G1: Rectangular and Polar Grid dialogs reference point locator (#1377)
2026-10-20 06:14:43 +07:00
bflatastic ddab0152af fmt (#3057) 2026-10-20 06:08:16 +07:00
nrq6993-a11yandCursor f12ad3aa0a G1: Pin grid dialog bounds to canvas click via reference locator (#1377)
Store the click as __anchorX/__anchorY and recompute x/y when the locator opens or changes so the chosen point stays on the click like Illustrator.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 06:07:29 +07:00
nrq6993-a11yandCursor 699e6e9af4 G1: Grid tool dialogs add reference point locator (#1377)
Rectangular and Polar Grid click dialogs show the nine-point locator and resize width/height about it using the same artboard_rect logic as Artboard Options.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 06:07:29 +07:00
BFlat 059b6d083c Merge pull request #3165 from danngreen/fix/2143-guide-key-object
Fix #2143: ruler guides and key objects align to each other
2026-10-20 06:07:00 +07:00
Dan Green 7852f363a1 Fix #2143: selected ruler guides align to a key object
A lone object selected with ruler guides can be the key (the Selection
tool's click on it), and Align to the key moves the other selected
guides to its edge or centre: vertical guides to left, centre or right,
horizontal ones to top, centre or bottom. Parallel guides line up on a
key guide. Aligning to the selection or the artboard leaves guides alone.
2026-10-20 05:56:59 +07:00
Dan Green 263c36567f Fix #2143: a ruler guide can be the key object Align lines objects up on
A click with the Selection tool on a guide selected with objects makes
it the key (select.key {guide}), drawn thicker; a click on it again
cancels it. Align to Key Object is on with one object and a selected
guide and makes the guide the key. object.align lines the objects (or
selected anchors) up on the guide's line and leaves the guide where it
is: a vertical guide sets x only, a horizontal one y. The selection
keeps key_guide beside key, never both; it goes with the guide or the
objects, and follows the guide when earlier guides are deleted.
Distribute Spacing doesn't space from a key guide.
2026-10-20 05:56:58 +07:00
BFlat 398d2e19de Merge pull request #3163 from Matoo125/fix/polygon-sides-from-file
Live shapes: a polygon read from a file has 3 to 1000 sides
2026-10-20 05:45:38 +07:00
BFlat 0567752448 Merge pull request #3162 from danngreen/feat/libraries-panel-scroll-reorder
Libraries panel: scrolling, and drag to reorder items and groups
2026-10-20 05:36:04 +07:00
Martin Vlach 38e73060a7 Live shapes: a polygon read from a file has 3 to 1000 sides
Every command keeps a live polygon's side count in 3..=1000
(shape.polygon, object.setLiveShape, the Polygon tool), but reading a
document took `sides` as any u32. Selecting such a polygon builds its
Live Corners: LiveShape::polygon_outline makes one point per side and
set_corners resizes the corner radii and kinds to the side count. A
document a few bytes long ("sides": 4000000000) makes the app allocate
tens of gigabytes and abort or stall, and the unsaved work in other
documents is lost with it. Transforming such a polygon with cut
corners regenerates the same outline.

`sides` is now clamped to 3..=1000 when it is deserialized, so a
polygon from any document that is read (opened, placed, packaged or
taken from a library) keeps the limit the commands keep, and the code
that uses the count needs no check of its own.

The test reads polygons with 0, 5, 1000 and 4,000,000,000 sides,
checks they come out with 3, 5, 1000 and 1000, and that the clamped
one's outline has 1000 anchors.

Reported-by: BS.ehlas.cz
2026-10-20 05:28:32 +07:00
Dan Green 6bed87b5fb Libraries panel: the list scrolls; items and groups reorder by dragging, items drag into groups
The list fills the panel down to its bottom bar and scrolls, also while a drag nears its top or
bottom edge (in the dock and in a flyout, as the Layers panel does).

Dragging in the list:
- an item (colour, text style or graphic) dropped on another of its kind lands just before or
  after it, by the pointer's half, an accent line showing where, and joins that item's group;
- dropped elsewhere on a group (its header too, even collapsed) it goes into that group, and on
  the ungrouped items out of its group, the section outlined; with every item grouped, a
  "Drop here to take it out of its group" strip shows while one is dragged;
- a group dragged by its header goes before or after another group.
Colours and graphics still drag onto art and the canvas as before.

Engine: library.moveItem takes `before` / `after` (an item of the same kind) to place the item
in its kind's order, which the panel shows; the new library.moveGroup {group, to} moves a group.
Text styles drag as PanelDrag::LibraryItem, groups as PanelDrag::LibraryGroup, and a library
colour's Paint drag names its library item, so the panel tells it from a swatch dragged in.

Tests: reordering and moving in the engine, the commands in the library fuzz test, and panel
tests that drop styles and group headers and check the list scrolls and scrolls while dragging.
Strings translated in the 23 catalogs; docs/mcp.md and the parity row updated.
2026-10-20 05:28:17 +07:00