Commit Graph
1142 Commits
Author SHA1 Message Date
dotCooCoo 6e4fb6e4e7 Links: a link on a drive mapped to another share is not followed
craft-docpath follows a stored path only on this computer or on the
share the document is on, but its check is lexical and cannot see
mapped drives. fileio::document_path therefore followed a link stored
as Y:\img\logo.png, where Y: is mapped to \\server\other, from any
document: opening the document, links.check, Link Info and the
linked-file watch read or stamped the file on that server.

On Windows, document_path now reads the final path of the root folder
of a link's drive (std::fs::canonicalize). Windows opens that root
folder on the drive's server to answer, so the check still contacts
the server once per lookup; no file on it is read, stamped or watched.
When the final path names a share, the link is checked against the
document's path on its share, and a link on another share is refused
as on another computer, as a link stored with that share's own path
is. Links on this computer's drives and on the document's share are
followed as before, and a link on the document's own drive is not
looked up. A document opened as \\?\Z:\... is looked up on its share
as Z:\... is. Other systems are unchanged.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 15:42:35 +07:00
dotCooCoo 82a78e3d58 Save for Web: browser previews go only to a folder the current user owns
Preview in Browser wrote each preview into `VectorCraft Save for Web`,
and before each preview it removed every file and folder there that
was an hour old or older. Where the temporary folder is the shared
/tmp, another user could make that folder first, writable by everyone,
and the app wrote its previews inside it, in folders that user could
read and replace. Where the system follows other users' links in /tmp,
the folder could also be a link, and the app removed the old entries
of the folder it named.

A preview now uses that folder only when it is a folder and not a
link, and on Linux, macOS and FreeBSD only when the current user owns
it and no other user can write to it. On those systems the folder and
each preview's folder are made with mode 0700. The cleanup removes
only preview folders, on those systems only those the current user
owns. When the shared folder can't be used, the preview goes to a new
folder in the temporary folder. A later preview removes such folders
once they are an hour old.

Docs: docs/target-app-parity.md.
2026-10-21 15:42:35 +07:00
Mex 97d133d9d4 M3.57: preserve fractional CSS gradient stop positions 2026-10-21 15:20:15 +07:00
nrq6993-a11y 078e67a386 G2: Effect › Sketch › Water Paper raster filter (#2955) 2026-10-21 15:17:04 +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
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
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 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
Daniel 050f68a030 fix(linux): enable font-free Wayland window controls 2026-10-20 07:34:46 +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
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
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
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
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
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
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
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
Brian TorresandClaude Opus 5.5 ac7f0eebf8 PDF import and export: keep invisible text (rendering mode 3) as type (#2675)
Text drawn in rendering mode 3 (3 Tr), which is how a scanned page carries
its recognized OCR text, was left out on open (since #2988, with a warning),
so a scanned PDF opened and saved again lost its searchable text.

Import: an invisible glyph now comes in as type with neither fill nor
stroke. It skips the glyph-outline checks that guard how visible type looks,
and it joins only other invisible text on a line (Look::takes no longer
lets it into a stroke-only run). INVISIBLE_TEXT_NOTE now only reports text
that can't be type (text opened as outlines, Type 3 fonts), and
tests_reader_compat::invisible_text_is_reported_when_left_out expects the
invisible line as unpainted type when text opens as type.

Export and print: characters with no fill and no stroke are written as real
text inside a form marked Mark::Invisible; forms::finish sets that form's
text rendering mode to 3, so it draws nothing but stays selectable and
searchable (PDF/X-1a and PDF/A-2b too). The writer has no invisible mode of
its own, so this reuses the marked-form mechanism used for layers and
overprint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 04:43:11 +07:00
MarekSpodBiedry 2ff683d5fe G2: Effect › Sketch › Chrome raster filter
Squashed from #2985.
2026-10-20 04:20:50 +07:00
MarekSpodBiedry 7c47692b32 Ask for the .ai options on Save a Copy like Save As (#3141) 2026-10-20 03:41:16 +07:00
BFlat 8a6e4e77f3 Merge pull request #2997 from nrq6993-a11y/stream/vc-feat-g1-guide-alt-orientation-move
G1: Alt-drag an existing ruler guide to swap horizontal and vertical
2026-10-20 03:29:19 +07:00
nrq6993-a11yandCursor 2ceb183bfe G1: restore Alt-drag copy for existing ruler guides (#2997).
Drop the undocumented orientation-swap on moved guides; Alt again previews guide.move with copy, matching new-guide Alt orientation only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 23:17:28 +03:00
bflatastic fe6ef8ade7 Docs: effects parity counts the four Sketch and Artistic filters of 2026-10-19 (36/57) 2026-10-20 03:16:37 +07:00
nrq6993-a11y 11adb72d44 G2: Effect › Sketch › Graphic Pen raster filter
Squashed from #2967 onto #2972 (Chalk & Charcoal): both filters in the effect catalog, PIXEL_EFFECTS and the Sketch label and filter tests.
2026-10-20 02:52:33 +07:00
bflatastic ac83e91c5f Docs: the ROADMAP's 2026-10-19 row, and the rows merges of conflicting PRs left twice
ROADMAP: a progress row and status line for the community PRs of 2026-10-19; the reproducible-bytes and inch-ruler rows appeared twice. gaps.md: G6 held four interleaved versions of its Missing line; it now says what is left (a scripting surface). target-app-parity.md: three copies of the Type evidence bullet, merged into one with every sentence. type-parity.md and ui-parity.md: a row and a shorter copy of it.
2026-10-20 02:41:01 +07:00
nrq6993-a11yandCursor 8f7486b90f G9: .ai art styles read Adobe Feather as live effects
Plain art-style import maps Adobe Feather dictionaries to stylize.feather so styled objects stay editable on open.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 02:21:41 +07:00
bflatastic a52198cefa Appearance: image fills and strokes draw the shape Convert to Shape or Outline Object gives them
Squashed from #2500 (its merges of main dropped).
2026-10-20 02:06:54 +07:00
dotCooCoo 926ca62952 Simplify: Object › Path › Simplify opens a slider with Auto-Simplify and More Options
Squashed from #2555 (its merges of main dropped).
2026-10-20 02:06:54 +07:00
nrq6993-a11yandCursor 4b8e42e517 G1: path.join accepts corner or smooth on the merged anchor
Join can set the surviving junction point to a corner or smooth anchor via `to`, matching Illustrator's join choice without a combined Average+Join command.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 01:48:35 +07:00
nrq6993-a11yandCursor d41af02637 G1: Spacing Guides snap bounding-box resizes to equal gaps
Smart Guides spacing now applies when scaling with the Selection and Free Transform tools, not only on moves.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-20 01:48:35 +07:00
Shayan Horbank 472a6d5359 UI: improve color picker layout (#2008)
Add a centered draggable header, stacked actions and aligned input groups.
Compact the color controls and use vertical scrolling in narrow windows.
Keep the screen sampler above the field and Tab through each color model
before the next. Confirmation, cancellation and live preview stay shared.

Preserve screen-reader button names and channel values, units and ranges.
Add pointer/key, layout, warning and AccessKit regressions, and update the
required progress notes without changing parity scores or estimates. Correct
the stale #1692 gap status while retaining the open Direct Selection gap.
Document dialog rendering, button sizing and the regression helpers.

Validation: cargo xtask ci passed all 10 steps with official Rust 1.99,
including strict Clippy, 8437 workspace tests and all WASM checks. All 17
targeted color picker tests passed. German, enlarged English, narrow-window
and color-warning UI renders were reviewed locally.

Fixes #2008
2026-10-20 01:30:19 +07:00
krinry de985a46e6 Pencil: Alt key toggles to Smooth Tool (#2001) 2026-10-20 01:18:45 +07:00
FractalEncrypt b09e23f4a1 M14: cache status-readout sample widths by display settings 2026-10-19 23:47:01 +07:00
FractalEncrypt 45d9a409d4 M14: keep status messages and readouts stable 2026-10-19 23:46:59 +07:00
Mirko Kerezovic af48dda322 M4: address DXF circle export review (#1299) 2026-10-19 23:34:20 +07:00
Mirko Kerezovic b87cb157ed M4: export DXF circles and arcs as native entities (#1299) 2026-10-19 23:34:19 +07:00
bflatastic d2b927b57b Docs: the canvas-scale gap entry reads as the others do (#3106) 2026-10-19 23:34:18 +07:00
FractalEncrypt 04750ece1b M11: bound cached canvas dimensions to the renderer limit 2026-10-19 23:34:17 +07:00
FractalEncrypt 742a5c1af2 M11: preserve cached canvas scale during viewport changes 2026-10-19 23:34:16 +07:00
bflatastic 6fed871f88 Docs: the live-text PDF Place notes go in the docs' own sections, without update banners (#3042) 2026-10-19 23:14:20 +07:00
KnottyDyes 48a0206adb PDF: placed and pasted pages keep live text
Honor the requested text mode in the single-page importer, as Open does, instead of forcing outlines for Place and PDF clipboard import.

Add synthetic regressions for bytes, unlinked paths, selected pages and explicit outlines. Update format parity and milestone evidence without changing scores.
2026-10-19 22:19:59 +07:00
Ari MarkowitzandClaude Opus 5.5 be7fd07562 #2558: a drag over a wide blur draws it from the still frame's raster
An effect raster too large to keep whole (a wide blur, a shadow or glow,
or any blur zoomed in) is cropped to what can show in the view, or made
at a lower resolution and resampled to the view's pixels, and kept while
the view stays the same. Dragging other art over the object then draws
its effects from the still frame instead of previewing them at the
drag's lower resolution on every frame. An interaction's frames stay on
the render worker once it drew the frame shown, since only its renderer
holds those rasters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:19:57 +07:00
dotCooCoo 007f536c79 Effects: effect art redraws when fonts arrive or the color settings change
The renderer kept the art it made for an object's effects while the
object stayed the same. In the web build, type that a geometry effect
outlines, on its own, in a group or in a symbol's art, kept the
substitute font's outlines after its own font loaded. On the desktop,
Refresh Font List left them on a symbol instance whose art holds the
type. Objects with Revolve, Extrude & Bevel, Inflate or Rotate, and
objects and groups with a color adjustment, kept the colors they were
shaded or recolored with after the color settings changed. Live objects
already followed both.

The renderer now keeps that art with the inputs it keeps for the art
of live objects: the font generation when the art holds type, the art
of the symbols it shows, and the color settings. A temporary release
test timed the added key at 11 microseconds a frame for 400 type
objects with a drop shadow and 0.77 ms for 400 symbol instances of 100
paths each with a drop shadow, against warm frames of 5.3 and 72 ms.

Docs: docs/target-app-parity.md, docs/gaps.md.
2026-10-19 22:19:55 +07:00
sungjea kimandClaude Opus 5.5 515569470c Anchor Point tool: Shift-drag on an anchor moves it, handles and all
With the Anchor Point tool, a drag from an anchor pulls out smooth handles.
Shift held at the press now moves the anchor instead, its handles keeping
their offsets; Shift kept down during the drag keeps the move at 45° steps
(the constrain angle), released it frees it. A Shift-click that doesn't
move leaves the anchor as it was. A selected blend's spine points follow
the same rule (object.blend.spine.moveAnchor).

The move goes through a new engine command, path.moveAnchor
{id, subpath?, anchor, x, y}, since path.moveAnchors only acts on the
direct-selected anchors and the Anchor Point tool keeps no anchor selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:19:53 +07:00