Commit Graph
180 Commits
Author SHA1 Message Date
BFlat 1b9aa07e65 Merge pull request #6995 from dotCooCoo/feat/actions-insert-select-path
Actions: Insert Select Path stores the selected path as a step
2026-11-13 12:27:58 +07:00
dotCooCoo f6d8af3ed6 Merge main into feat/dcs-linked-spot-plates 2026-11-12 19:09:51 -08:00
dotCooCoo 2b975d620e Actions: Insert Select Path stores the selected path as a step
The Actions panel menu has Insert Select Path, after Insert Menu Item.
With one whole path or compound path selected, it inserts a Select
Path step into the action being recorded, or after the selected action
or step. Playing the step selects that path again.

- New engine command `select.storedPath {id, fingerprint}`. The step
  stores the path's object id and a fingerprint of its geometry and
  fill rule ("v1:" and 16 lowercase hex digits, FNV-1a 64). Playback
  selects the path only while it exists unchanged and can be selected;
  otherwise the step fails and the selection stays as it was. A path
  on a locked artboard is refused in isolation mode too.
- Capture refuses an empty selection, several objects, selected
  anchors or segments alone, groups, type, guides, a path selected
  together with ruler guides, a member of a compound path, and hidden,
  locked or template art.
- New UI command `ui.action.insertSelectPath {set?, action?, step?}`.
  Step rows show "Select Path".
- `.vcactions` files that hold a Select Path step are written as
  version 4. Reading accepts versions 1 to 4 and refuses a Select Path
  step with invalid params.
- Rows for the new labels and messages in every catalog.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-12 16:02:00 -08:00
dotCooCoo 18aed1da4e Merge main into feat/dcs-linked-spot-plates 2026-11-12 12:45:18 -08:00
dotCooCoo 7dd161cc49 Place: DCS files link with their spot plates as spot swatches and separate on their own inks
File › Place with Link reads a DCS 1.0 or 2.0 file (an EPS file whose
header names one EPS program per printing plate) as one linked image of
its plates. Each spot plate adds a spot swatch in the CMYK values the
file gives, and the clipping path the file's composite is drawn through
clips the image. document.open, file.place without link or from bytes,
and links.embed refuse a DCS file and change nothing.

- The screen, composite print and exports show the plates mixed into
  CMYK. Separations Preview and print separations show each plate on
  its own ink; a spot plate prints as its CMYK values when spots print
  as process.
- links.list answers the format DCS, and with dcsTransparency marks the
  DCS images that are transparent or overlap transparent objects (the
  Links panel menu's Show DCS Transparency Interactions).
- swatch.unused leaves out a spot swatch while a DCS image has a plate
  of its ink.
- links.update and links.relink read the plates and the clipping path
  again, and the link shows as modified when one of its plate files
  changes or is gone.
- The plates go on the first plate's pixel grid, grown to hold every
  plate where it prints; a plate of another size or position is
  resampled onto it.
- A file may name 64 plates, a plate file may be 1 GB, and the plates
  together may hold 512 MB of ink amounts. A file naming more plates is
  an error.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/file-format-parity.md.
2026-11-12 09:30:20 -08:00
dotCooCoo fe34c2b818 Merge main into feat/custom-arrowheads 2026-11-12 09:23:20 -08:00
dotCooCoo 5e80810605 Merge main into feat/custom-arrowheads 2026-11-12 05:00:21 -08:00
dotCooCoo 67ebbf0b53 Merge main into feat/warp-images 2026-11-12 04:59:37 -08:00
dotCooCoo a1a23c46e4 Warp: the Warp effects bend an image's pixels with its frame
A Warp effect on an image, or on a group that holds one, bent the image's
frame but drew the pixels unbent, cut by the bent frame. The image now
bends with the frame as a raster mesh warp, as an envelope bends it: after
the last effect in the list, the image is cut into 10 x 10 cells of two
triangles, each a clipped piece of the image under its own affine map,
through every Warp it took and the Transform moves between them. Effects
after a Warp reshape only the frame, and a placed gradient on a group
that holds the image follows the group's bounds as the pieces draw them.
The image itself is unchanged, so editing or removing the warp redraws
it. The images stay unbent when their pieces would make the art pass 2
million objects, and an image stays unbent when its clipping frame keeps
its shape because the warp would pass the effects' anchor budget.

- SVG export writes the image once and draws the pieces with <use> when a
  group holds pieces of one image clipped by triangles. PDF, EPS, .ai and
  PNG exports write the bent pieces.
- Envelopes: a grayscale image's fill color now colorizes the pieces an
  envelope cuts. Before, the pieces drew the gray image.
- Geometry effects: an image under any geometry effect keeps its fill
  color and its smoothing setting. Before, it drew the gray image,
  smoothed.

Docs: Warp params text, docs/mcp.md, docs/target-app-parity.md.
2026-11-12 01:18:57 -08:00
Sven Lafebre f757f9ef43 Merge remote-tracking branch 'origin/main' into fix/5111-kern-paragraph-start
# Conflicts:
#	docs/target-app-parity.md
2026-11-12 09:29:30 +01:00
Sven Lafebre ab17837cba Merge remote-tracking branch 'origin/main' into fix/5111-kern-paragraph-start
# Conflicts:
#	docs/target-app-parity.md
2026-11-12 09:24:22 +01:00
BFlat 354a12be3f Merge pull request #6805 from dotCooCoo/fix/read-roots-prefs
MCP: preferences replies leave out folders outside a confined client's read roots
2026-11-12 14:16:11 +07:00
dotCooCoo 264eeec475 Merge main into feat/custom-arrowheads 2026-11-11 22:30:03 -08:00
dotCooCoo 00624da7ad MCP: preferences replies leave out folders outside the read roots
Under --automation-read-root, prefs.get, prefs.list, prefs.set and
prefs.reset returned the folder preferences (fontsFolder, pluginsFolder,
scratchPrimary, scratchSecondary, recoveryFolder, templatesFolder) and
the export presets' folder even when they were outside the read roots.
Such a folder preference is now null, as ui.inspect leaves it out, and
such a preset's folder is left out. An empty folder and the scratch disk
choices stay; without roots the replies are unchanged.
2026-11-11 22:13:53 -08:00
dotCooCoo c1f7e2eeaf Stroke: custom arrowheads from an arrowheads file join the arrowhead menus
The Stroke panel menu's Edit Arrowheads… (stroke.arrowheads.edit) opens
Arrowheads.vectorcraft next to the preferences, creating it with one
example head when it is missing. Each symbol in the file is one head,
listed by name after the built-in heads in both arrowhead menus: it
points right, its registration point is the tip, and its height is drawn
as wide as the built-in heads. The file is read at startup and again
each time VectorCraft saves it; stroke.arrowheads.reload and
stroke.arrowheads.list read and list it.

Arrowhead gets a Custom variant, appended after the built-in names, that
carries the head's name and outline, so a document that uses one draws,
saves, reopens and exports it without the arrowheads file. stroke.set
takes {"custom": name} or the {"Custom": {name, path}} form that
document.inspect reports.

Part of #5421.
2026-11-11 21:52:46 -08:00
Sven Lafebre 46e8f576cc Merge remote-tracking branch 'origin/main' into fix/5111-kern-paragraph-start
# Conflicts:
#	docs/target-app-parity.md
#	docs/type-parity.md
2026-11-12 03:56:06 +01:00
dotCooCoo 6eeff01625 Merge main into test/select-skips-raster-effects 2026-11-11 18:32:38 -08:00
dotCooCoo 2a5f8d8d5d Selection: a click on a drop shadow or glow selects the object under it
Clicks, the hover pointer and marquees with the selection tools already
leave out the area that raster effects paint beyond an object. New tests
in crates/engine/src/tests/select_effects.rs hold that in place for drop
shadows, Outer Glow and Gaussian Blur on paths, groups, point type,
symbol art and images, for Shift-click, Direct Selection, the hover
pointer, a drag from a selected object's shadow and a marquee. hit_leaf
gets a doc comment, and docs/target-app-parity.md gets a line citing
https://helpx.adobe.com/illustrator/using/selecting-objects.html.
2026-11-11 18:31:51 -08:00
BFlat 66896602d6 Merge pull request #6674 from dotCooCoo/fix/malformed-ids-test-isolation
Tests: malformed id cases check and restore isolation mode and the shown mask
2026-11-12 09:23:23 +07:00
BFlat 8719585cc6 Merge pull request #6663 from dotCooCoo/feat/script-target-engine
Scripts: #targetengine runs a script in a named engine that keeps its globals
2026-11-12 09:22:45 +07:00
BFlat e7a64097a0 Merge pull request #6725 from dotCooCoo/fix/read-roots-other-replies
MCP: library lists leave out paths outside a confined client's read roots
2026-11-12 09:11:19 +07:00
dotCooCoo 4591653f33 MCP: library lists leave out paths outside a confined client's read roots
swatch.library.list, brush.libraries, symbol.libraries and
graphicStyle.libraries returned every library's file path and the user
library folder to a client confined by --automation-read-root, including
paths outside its read roots. They now return null for those paths, and a
library loaded from a file outside the roots is listed as
loaded/<file name>, numbered (2), (3) when names repeat. That id finds the
library in the other library commands, and their replies show it too.

- The naming moves from control.rs into
  swatchlib::concealed_library_ids, which numbers the session's loaded
  libraries first, so ui.inspect, ui.docking and the library commands show
  a library under the same id.
- swatch.library.copyToUser refuses a library whose file is outside the
  read roots without naming the file; it and the library saves with
  `user: true` leave out `path` when the user library folder is outside
  the read roots.

Docs: docs/mcp.md (read roots), docs/target-app-parity.md.
2026-11-11 15:58:01 -08:00
dotCooCoo afe2a71e59 Tests: the empty-params command sweeps leave plug-in caches alone
every_command_survives_empty_params and
read_only_commands_read_the_instance_of_a_picked_object run every command
with {}. That includes plugin.clearCache, which without an id empties the
cache of every plug-in installed in the process. When either sweep ran
during plugin_storage_is_capped_and_survives_a_damaged_settings_file
(called from plugins_keep_settings_and_a_cache_of_their_own_in_the_store),
it deleted that test's cache entries Tile and tile. The test's
st.clear_cache() == MAX_CACHE_BYTES + 3 assertion after the
MAX_CACHE_SCAN folders then failed with 536870912 bytes freed.

Both sweeps now leave plugin.clearCache out.
tests/plugin_clear_cache.rs already runs it with {} in a test binary of
its own. A new test runs the other plug-in commands with {} in the
selection states that every_command_survives_empty_params uses, and checks
that an installed plug-in's cache keeps its 3 bytes.
2026-11-11 15:26:46 -08:00
dotCooCoo 5ce2337715 Tests: malformed id cases check and restore isolation mode and the shown mask
malformed_ids_are_refused_by_commands_that_default_to_the_selection put back
the document, the selection and the undo history after a case that changed
them, but not isolation mode or the opacity mask view. If object.isolate
entered isolation mode while refusing a bad id, the test did not report it,
and the later transparency.editOpacityMask and transparency.viewOpacityMask
cases failed with "exit isolation mode first" instead.

Each case now also compares isolation mode and the shown mask before and
after the command, and puts them back when they changed, so a failure is
reported against the command that caused it.
2026-11-11 13:20:45 -08:00
Sven Lafebre 6ae7051e4e Fix #5111: kerning at the caret before a line's first character moves it
At the Type tool's caret, kerning goes on the character before the caret.
At the start of the text there is none, so Alt+arrows, the Kerning field
and Reset Tracking and Kerning did nothing; at a later paragraph's start
the kerning landed on the invisible line break.

CharStyle::kerning_before (kerningBefore) is the space before a character
that starts a line by itself: the first character of the text, of a
paragraph, or after a forced line break. Shaping gives its cluster the
space and draws it that much later along the line (right to left the
space is at its right; in vertical type it moves down the column). The
caret at such a line's start sets it (edit::caret_kerning_target), in
type.step, type.resetTracking and the Character panel; text.setRangeStyle
takes it. SVG export starts the line where the character is drawn. The
Eyedropper and character styles leave it alone.
2026-11-11 21:43:31 +01:00
dotCooCoo 3a7c46975c Merge main into feat/script-target-engine 2026-11-11 12:16:55 -08:00
dotCooCoo 7fbac74885 Merge main into feat/script-target-engine 2026-11-11 12:09:30 -08:00
BFlat f9ff1caf54 Merge pull request #6648 from dotCooCoo/feat/dynamic-text-on-path
Type: Dynamic Text sizes type on a path to fill its brackets
2026-11-12 02:45:39 +07:00
dotCooCoo f1c17603bb Scripts: #targetengine runs a script in a named engine that keeps its globals
A script whose first lines name an engine with `#targetengine name` (or
`//@targetengine name`) runs in that engine, and a later script that names
it sees the globals and functions earlier runs left, until the engine is
dropped or the session ends. A script without the line, and
`#targetengine main`, still get a new engine for each run.

- A session keeps at most 8 named engines, each with a 64 MiB heap. A run
  that goes past the heap ends with the heap limit, and the engine stays
  usable. Names are 1 to 64 bytes. The line comes once, in the script
  itself, before its code and its #include lines.
- An uncaught error, a limit or script.stop puts the documents back and
  keeps the globals the run set. An internal error drops the engine. A run
  stopped at a dialog or left paused ends where it was before the engine
  is kept. An error in a function an earlier run defined is reported
  without a line and column.
- `vc`, `$`, `alert`, `confirm` and `prompt` are made once for each engine
  and act for the run that calls them, including copies an earlier run kept
  in a global. `vc.args` gives a read-only copy of the calling run's
  `args`, and what a run sets on `$` lasts for that run only.
- `script.engines` lists the named engines with their heap, and
  `script.engine.drop {name}` drops one. Scripts cannot run either.
- The engines stay on the thread that made them. A session used on
  another thread starts there with no named engines, and the old ones are
  dropped on their own thread.
- The web build has no named engines: a run that names one other than
  `main` ends with an error before any of it runs.
- `Realm::end` ends a run where it is, `Realm::error_source` counts from
  the realm's last script, and `HostItem::FrozenGetter` gives a frozen
  value each time it is read.
- The script checker lists `#targetengine` as a supported directive.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/gaps.md.
2026-11-11 11:17:42 -08:00
dotCooCoo 2606b70604 Type: Dynamic Text sizes type on a path to fill its brackets
Type › Dynamic Text (`type.dynamicText`, also on the Contextual Task Bar
for type on a path) scales the text by one factor so it fills the span
between its start and end brackets, and the layout fits it again after
each edit to the text, the path or a bracket. The first character's
size moves in 0.1 pt steps within 0.1 to 1,296 pt. Turning it off, or
threading the type, keeps the sizes it was drawn at.

- The model saves `dynamicText` only when on.
- SVG, the legacy .ai writer and the .ai editing data write the drawn
  sizes through `vectorcraft_text::drawn`; the editing data warns that
  the setting is not written.
- Shrink Text to Fit and Dynamic Text step the size the Character panel
  shows when a size measures the cap height, x-height or ICF box.

Docs: docs/mcp.md, docs/type-parity.md, docs/target-app-parity.md.
Part of #4134.
2026-11-11 10:42:49 -08:00
BFlat 1e76791ae1 Merge pull request #6615 from dotCooCoo/feat/script-file-folder
Scripts: File and Folder read files in the script's own folder
2026-11-12 01:35:45 +07:00
BFlat f83d916d8a Merge pull request #6631 from Hassan1305/docs-6340-rotation-direction
Commands: every angle and rotation param says which way it turns (#6340)
2026-11-12 01:32:17 +07:00
dotCooCoo 5ac7d97d85 Guides: ruler guides are document objects on their layers
Ruler guides were a list kept beside the layers. Each guide is now an
object of its layer or sublayer, always directly in it and above the
layer's art. These behave as on main:

- Guides draw on screen only. Exports (SVG, PDF, PNG, PSD), Fit All,
  Select All, the art bounds, clicks and marquees, and a layer's
  Pathfinder and Transform effects leave them out. The Layers panel
  lists them as guide rows of their layer, the highest guide.list
  index first.
- Deleting a layer deletes its guides, and merging layers keeps their
  guides in the merged layer.
- The guide commands number guides as guide.list does, in the order
  they were made.

New: each guide takes an id from the document's object ids. An object
command given a guide's id fails with an error, and node.move cannot
put a guide into a group or out of its layers. A guide made while an
opacity mask, a symbol or a pattern is edited moves to the top
visible, unlocked layer when editing ends.

File format: native files still save the guides in the document's
guides list, in every file version, so older builds read them. Each
entry may now also carry the guide's id, name, locked and hidden
flags; older builds ignore those fields. Opening a file puts each
saved guide on its layer (on the top visible, unlocked layer when the
file names none or a layer it does not have), and each saved guide
counts against the object limit for opening a file. A ruler guide node
that a file holds below a layer's art moves up to the layer's guides,
and one anywhere else (in a group or another object, beside the
layers, or in a symbol) is removed. Ruler guides read from an .ai file
become guide objects on the layer they were on, numbered in the order
the file lists them.
2026-11-11 09:52:28 -08:00
HassanKhan b3860bf7d0 Commands: every angle and rotation param says which way it turns (#6340)
The params docs now state the direction (and the zero) of each angle or
rotation a command takes; behaviour is unchanged. Shape creation, text.create,
the grids, add-on shapes, saved views, Liquify, Puppet Warp pins, Mirror and
Radial Repeat and Vector Halftone turn clockwise; text character rotation,
gradients, the eraser tip and print screen angles turn counter-clockwise.

view.saved.new's rotation note changes its schema, so the API level goes to 3.

A test walks every command's params doc and fails when a param named like an
angle or rotation doesn't say clockwise or counter-clockwise.
2026-11-11 21:40:27 +05:00
dotCooCoo ba05554c64 Merge main into feat/script-file-folder 2026-11-11 07:47:42 -08:00
dotCooCoo b7a6196994 Scripts: File and Folder read files in the script's own folder
Scripts get the File and Folder classes as built-ins of the script
engine. A script run from a file can test whether files exist, read
their properties, list folders with getFiles (a mask or a function),
and open files to read them with read, readln, readch, seek and tell.

- Reads stay inside the script's folder and, when automation read
  roots are set, inside them too. A script given as text reads no
  files. Network and device paths, and paths outside the folder by
  their names, are refused before anything is looked up. Links and
  junctions that lead out are refused, and getFiles leaves them out.
- The encoding comes from the byte order mark, then the encoding the
  script set, then UTF-8 when the bytes are valid, then CP1252.
- Limits: 8 MiB a file, 32 MiB a run, 256 files open at once, 2,048
  getFiles entries and 65,536 names looked at, and paths of at most
  32,767 characters.
- Writing, the file dialogs and execute are not available yet: they
  return false or null and set error.

The report's hostCallCounts gains fileCalls and fileExecutes.
Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-11 07:46:33 -08:00
dotCooCoo 0e4010d26b Merge main into feat/script-dollar-object 2026-11-11 02:35:43 -08:00
BFlat 9a28264cfe Merge pull request #6557 from dotCooCoo/feat/script-alert-confirm-prompt
Scripts: alert, confirm and prompt open dialogs with Stop, and headless runs take their answers from script.run
2026-11-11 17:13:46 +07:00
dotCooCoo 179bd81566 Scripts: the $ object's other members, localize and toSource
A script's $ object now has the other members the Dollar object page
lists: about, appEncoding, bp, build, buildDate, colorPicker,
decimalPoint, engineName, error, flags, gc, getenv, setenv, global,
hiresTimer, level, line, locale, localize, memCache, os, screens, sleep,
stack, strict and version. Scripts also get the global
localize(object, ...values), toSource(value) and
Object.prototype.toSource().

Each run starts with the interface's locale and its own $ state;
$.setenv changes only the run's copy of LANG, LC_MESSAGES, LC_ALL and
OS, and $.getenv returns null for every other name. $.sleep waits from
0 to 1,000 ms between frames, and script.stop ends the wait. toSource
writes plain data only and throws on cycles, getters, host objects and
values past the data limits.

The script checker reports $.localize as a $ member instead of the
global localize function.
2026-11-11 01:59:37 -08:00
dotCooCoo c0adc6e9d7 Merge main into feat/script-alert-confirm-prompt 2026-11-11 01:09:54 -08:00
dotCooCoo 6684650425 Reshape: an empty focal list cannot be combined with radius
path.reshape refused focal with radius only when focal named an anchor.
Since an empty focal list chooses the focal-point rule, a call with
"focal": [] and a radius was accepted and used the radius falloff,
although the params text and docs/mcp.md say the two cannot be used
together. Any focal other than null now goes with radius as an error.
2026-11-11 00:01:50 -08:00
BFlat df7a185cb2 Merge pull request #6534 from dotCooCoo/fix/reshape-earlier-steps
Reshape: a step recorded without focal points plays by the earlier rule
2026-11-11 14:48:26 +07:00
dotCooCoo 2fc07fe3c0 Merge main into feat/script-alert-confirm-prompt
# Conflicts:
#	crates/engine/src/tests.rs
#	crates/ui-egui/src/app.rs
#	docs/target-app-parity.md
2026-11-10 22:40:45 -08:00
dotCooCoo 08387a22ec Merge main into feat/script-include-directives 2026-11-10 22:39:32 -08:00
BFlat b421c03d03 Merge pull request #6525 from dotCooCoo/feat/scripts-folder
Scripts: File › Scripts lists the Scripts folder, subfolders as submenus
2026-11-11 13:33:56 +07:00
dotCooCoo 4110f8c24e Reshape: a step recorded without focal points plays by the earlier rule
Before the Reshape tool had focal points it sent path.reshape without
focal or radius, and the command stretched an open path from the grabbed
point to its ends whatever the selection, and moved the anchors of a
closed path within half the diagonal of its bounding box with a
falloff. Such a step, in a saved action or a script, now moved a closed
path whole. A step without focal (or with focal null) and without
radius now plays by that earlier rule. The Reshape tool always sends
focal, an empty list included, so its drags keep the focal-point rule.
2026-11-10 22:30:19 -08:00
dotCooCoo 58623b34b1 Merge main into feat/script-alert-confirm-prompt 2026-11-10 22:14:59 -08:00
dotCooCoo df0373aa26 Scripts: alert, confirm and prompt open dialogs with Stop, and headless runs take their answers from script.run
alert(message[, title, errorIcon]), confirm(message[, noAsDflt, title])
and prompt(message, preset[, title]) are globals in a script run. In the
app the run waits at the call for the person's answer, spends no
instructions while it waits, and every dialog has Stop, which ends the
run and puts every document back. A run over MCP or the command line
takes its answers in order from script.run's answers, a list of
{kind, value} records; a missing, mismatched or unused answer ends the
run. The report lists each dialog with its answer in dialogs, with
dialogCount and dialogsTruncated.

The interpreter gains a host call that waits at the call for its
result, from the script's own code only; a dialog called in a callback
of a built-in, a getter or a conversion throws in the app.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-10 22:14:01 -08:00
dotCooCoo 693031c509 Scripts: #include and the other preprocessor lines run, and $.evalFile runs a file
Before a script runs, `script.run` reads its preprocessor lines (`#` or
`//@` at the start of a line, outside strings and comments):

- `#include` and `//@include` put another file's text in place of the
  line, looked for beside the including file, then in each
  `#includepath` folder, then in each `JSINCLUDE` folder. A later
  `#includepath` replaces the list.
- `#script` names the run unless `name` is given; `#target` takes any
  value; `#strict off` is taken. `#strict on`, `#targetengine` and
  `#engine` are refused as unsupported, and any other `#name` as
  unknown.

`$.evalFile(path, timeout?)` runs a file in the same global environment
and returns its last value, `$.fileName` is the file being run and
`$.includePath` the include folders.

A script run from a file reads these files only in its folder and the
folders below it. Paths with a `..` name, links or junctions that lead
out, device names, streams and network paths are refused; a script
given as text reads none. A run reads at most 64 files 16 levels deep,
32 MiB of source in all and 1,024 paths looked up. An error in an
included or evaluated file is reported at that file's line and column,
and the report's new `file` field names it.

Docs: docs/mcp.md (script.run, Script Console), docs/target-app-parity.md.
2026-11-10 21:59:22 -08:00
BFlat 0695db3ec0 Merge pull request #6519 from dotCooCoo/feat/reshape-focal-points
Reshape: a click sets a focal point and a drag moves every focal point
2026-11-11 12:53:37 +07:00