Commit Graph
743 Commits
Author SHA1 Message Date
Fabio William Conceição e3a17716ed G5: built-in Map Markers symbol library 2026-11-10 10:09:21 +00:00
dotCooCoo 7b44ef5737 Merge main into feat/script-run 2026-11-09 23:43:37 -08:00
dotCooCoo 19ce2b2547 Scripts: a command's savepoint copies no undo steps but the newest
Each command a script runs keeps a savepoint of the state it may need
to put back if it fails. The savepoint cloned every open document's
whole undo history, so a script that made N edits did O(N^2) work.

- The savepoint now clones each document's state without its history
  and keeps a mark: the newest undo and redo steps and the serials of
  the steps under them. Until the command ends, the history trims no
  step, and a new step sets the redo steps aside instead of dropping
  them. A failed command truncates both stacks back to the mark.
- file.close, file.closeAll, document.close and file.revert still keep
  a whole copy, since they remove the history or the document.
- When a failed command removed or changed a step under the newest
  one, the history cannot be put back exactly, and the run stops with
  an error; its rollback restores the documents as they were before
  the run.
- run_guarded no longer copies the histories during a script's run,
  and a committed run moves the histories from before it back instead
  of copying them.
- Symbol editing mode shares its saved redo steps between copies of a
  document's state.
- A script cannot run edit.clearHistory. A run keeps each document's
  undo steps from before it, so the command reported a clear that the
  commit then undid.
2026-11-09 23:23:27 -08:00
BFlat c86c535389 Merge pull request #6402 from dotCooCoo/feat/text-glyph-bounds
Type: an optional setting measures point type by its glyph outlines
2026-11-10 14:14:00 +07:00
dotCooCoo 860149a981 Merge main into feat/text-glyph-bounds 2026-11-09 22:03:17 -08:00
dotCooCoo cc03fade6e Scripts: the growth budget counts every art store, with CLI and source fuzz tests
A document a script opens now counts the art in its graph designs and in
the original of the pattern being edited toward the growth allowance.
`Document::art_roots` and `art_roots_mut` list every tree of a document's
art; `any_node`, `edit_nodes_where` and the script's object count use them.

`script.run` checks the depth of `args` without recursion before it copies
or writes them out, so `args` nested 10,000 levels deep is refused with the
place it went past 64 levels instead of overflowing the stack.

New tests: graph-design art and the growth allowance, `args` nested 10,000
levels deep from a script (a RangeError) and from a caller, `run --cmd
script.run` on the command line with a report and with a thrown error, and
a property test that sends arbitrary source through `script.run`.
2026-11-09 22:00:04 -08:00
dotCooCoo 156613941a Type: glyph bounds take in underline and strikethrough bars
With Measure Type by Glyph Bounds on, point type is measured by its
glyph outlines and its underline and strikethrough bars, as the canvas
draws them. A typed W or H gives what is drawn that size, and underlined
or struck-through spaces are measured by their bars instead of the em
box.

The texts now say that each point type object selected as an object
(not inside a selected group) is measured this way, that moved type
snaps by its ink box, and which Arrange Tools commands follow the
option: Swap Objects, Replace with Top Object, Set Size of Each and
Sort Objects. Arrange in Grid and Group Overlapping Objects keep
measuring visual bounds.
2026-11-09 21:53:22 -08:00
dotCooCoo a1949095c6 Type: an option fits a text object's bounds to its glyph outlines
Measure Type by Glyph Bounds (`typeGlyphBounds`, off by default) is in
Preferences > Type > Glyph Bounds and the Transform panel menu. With it
on, the bounding box, the Transform, Control bar and Properties X, Y, W
and H, `object.setBounds`, the transform tools' reference point and the
Arrange commands measure point type selected on its own by the ink of
its glyphs instead of its em box. A typed W or H scales the live type
until its ink has that size.

- The engine caches each type object's ink in text space with its
  layout bounds; the cache is not saved.
- Use Preview Bounds adds the characters' strokes, and type with
  geometry effects is measured as drawn. Skewed type is measured by its
  outlines.
- Type that draws nothing, area type, type on a path and type inside a
  selected group are measured as before. Align and Distribute keep
  following Align to Glyph Bounds.

Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md,
object.setBounds params text.
2026-11-09 21:07:55 -08:00
HassanKhan e33906976d Spelling right-click: only the topmost type's word; dictionary tests one at a time (part of #4936) 2026-11-10 10:00:03 +05:00
dotCooCoo 0a7b48503e Merge main into feat/script-run 2026-11-09 20:30:53 -08:00
dotCooCoo 62ed510c1e Scripts: a failed run puts the Caps Lock state back
view.capsLock joins the state a run puts back when it fails or is
stopped. A finished run keeps the state it set, and keeps the state the
app reported during the run when the script did not change it. The
pointer over the canvas is refreshed when a run's end changes Caps Lock.
2026-11-09 20:30:33 -08:00
BFlat 02de4ad5b6 Merge pull request #6367 from dotCooCoo/feat/lock-guides-by-default
Guides: Lock Guides by Default in Preferences locks guides when the app starts
2026-11-10 11:23:42 +07:00
dotCooCoo 16da351e5d Merge main into feat/script-run 2026-11-09 19:29:33 -08:00
dotCooCoo 2d5fc74cbf Scripts: a document a script opens counts toward the run's growth limit
A document that document.open or file.new adds during a run counts whole
toward the growth allowance: every object in its layers, symbols and
patterns, opacity masks included, and its document data.
2026-11-09 19:29:02 -08:00
BFlat ef81d550e4 Merge pull request #6371 from dotCooCoo/fix/issue-6361
Part of #6361: Cut takes direct-selected anchors out of the path
2026-11-10 10:07:20 +07:00
BFlat 695721f9df Merge pull request #6380 from dotCooCoo/fix/preview-bounds-butt-line
Transform: butt-capped lines measure to their end points with Use Preview Bounds
2026-11-10 09:59:06 +07:00
dotCooCoo dd55adb701 Merge main into feat/script-run 2026-11-09 18:39:42 -08:00
dotCooCoo 2c0d09e079 Scripts: File › Scripts › Other Script… runs JavaScript files
File › Scripts › Other Script… picks a .js or .jsx file, shows a warning
that can be turned off in Preferences › General, and runs the script
against the open documents through the `vc` object. The new
`script.run` and `script.stop` commands run and stop a script over MCP.

- A run is one transaction. Each document it changed gets one undo step
  named Run Script. An uncaught error, a limit or a stop puts every
  document, the paint, the clipboard, menu state and preferences back.
- A command that fails inside a run, or whose result is too large to
  reach the script, is undone and throws an error the script can catch.
- Files a command wrote stay on disk and the report names them, also
  when the run is stopped from the quit question.
- While a script runs, other commands that change anything fail with a
  busy error; MCP reports it as JSON-RPC error -32003.
- In the app the run advances one slice per frame with a Stop button in
  the status bar. A run that adds a great many objects asks whether to
  continue; Stop and Escape end it.
- Commands that change state outside the documents, such as prefs.*,
  plugin.* and file.place.queue, are refused to scripts.

Docs: docs/mcp.md, docs/gaps.md, docs/roadmap.md,
docs/target-app-parity.md.
2026-11-09 18:35:41 -08:00
dotCooCoo bed632cc65 Transform: butt-capped lines measure to their end points with Use Preview Bounds
With Use Preview Bounds on, the visual box of a butt-capped stroke on an
open path grew by half the stroke weight past each end of the path, where
a butt cap paints nothing. A 2 pt vertical line set to H: 10 mm in the
Transform panel drew about 9.3 mm long.

The box of an open subpath with butt caps no longer reaches past its
ends. A straight segment's box comes from the four corners of its stroked
rectangle, and a curve's box comes from the stroke at its ends and where
its tangent is square to an axis. Bevel joins, and miter joins the limit
bevels, are measured by the stroked corners, and round joins by the arc
they paint outside the turn, instead of half the weight all round. Round
and projecting caps, arrowheads, closed paths and miter tips measure as
before. Two cases can still measure larger than the stroke paints: a
curve that bends more tightly than half the weight, and a butt-dashed
path whose first or last dash is a gap, which is measured to the path's
ends.

A horizontal or vertical butt line now measures one stroke weight less
along its length in W or H, and Align places it by its end points. A
slanted line's box shrinks by less: the stroke's corners still reach
past the end points across the line. object.setBounds measures as drawn,
after each scale it tries, a butt-capped open path with a segment at a
slant, symbol instances' art included.
2026-11-09 18:16:48 -08:00
dotCooCoo 8d4ef8a202 Merge main into fix/issue-6361 2026-11-09 17:03:53 -08:00
dotCooCoo cb086ef1b7 Part of #6361: Cut takes direct-selected anchors out of the path
With anchors picked with Direct Selection, Edit > Cut removed only the
segments between them, so the picked anchors stayed in the path. Cut now
removes picked anchors and the segments attached to them from a path or
a compound path's member, as Clear does. Picked segments still lose only
those segments. When a path has picked segments, Copy and Cut leave out
anchors picked on its other contours, as Clear does.

Docs: docs/target-app-parity.md.
2026-11-09 17:03:43 -08:00
dotCooCoo dd914586ff Guides: Lock Guides by Default in Preferences locks guides when the app starts
Preferences > Guides & Grid gets a Lock Guides by Default checkbox
(lockGuidesByDefault), off by default. With it on, the app starts with
View > Guides > Lock Guides on. Changing the checkbox sets the lock at
once; an OK that leaves it unchanged keeps the session's lock, and
View > Guides > Lock Guides still toggles the lock for the session
without changing the preference. Reset Preferences turns it off and
unlocks guides when it was on. Headless sessions start unlocked unless
prefs.set turns it on, and older preference files read it as off.

Lock Guides works as before: new guides can still be dragged out of a
ruler or added with guide.add while guides are locked. The lock code
moves from view.guides.lock into Session::set_guides_locked so the
preference and the menu share it.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-09 17:03:20 -08:00
BFlat bca98461c4 Merge pull request #6366 from dotCooCoo/fix/issue-5632
Part of #5632: Convert to Grayscale stores an image with one gray channel
2026-11-10 07:50:43 +07:00
BFlat c7c6c12ca8 Merge pull request #6364 from dotCooCoo/feat/command-param-schemas
Commands: the View commands check their params against machine-readable schemas
2026-11-10 07:42:57 +07:00
dotCooCoo 7ff3d25697 Part of #5632: Convert to Grayscale stores an image with one gray channel
Edit Colors > Convert to Grayscale wrote the new image as an RGBA PNG, so
a PDF wrote it as DeviceRGB, in a CMYK document too. The image is now
stored as a gray PNG, with alpha only when some pixel is not opaque. Adjust
Colors with mode gray and convert does the same.

An RGB image whose pixels are already neutral now converts, and an image
already stored as gray with no visible change is left as it is.
2026-11-09 16:15:12 -08:00
Drew Dibble b242f5acf5 Fit to Artwork Bounds: a change in place is measured again; the fix's evidence names EPS export
A new test changes the document in place (Arc::make_mut, with no other
holder and no change of revision) and checks the answer is measured
again: keyed on the document's address alone, it would keep the old
answer. The kept answer's doc says what it holds after the measure,
and that the fonts set the bounds of all type. Should measuring ever
ask again while it runs, it is measured without being kept, rather
than borrowed twice.

The stack overflow fixed in d5d04f868 also hit EPS export, and the
parity rows now say so. Two test docs are corrected: the fill-only
clipping is an older bug on main, and the outline-view difference runs
both ways on a few dozen hairline pixels.
2026-11-09 16:50:36 -07:00
Drew Dibble a9a061c8a9 Merge main into type-3d-redraw
# Conflicts:
#	docs/target-app-parity.md
2026-11-09 16:36:25 -07:00
dotCooCoo 27c9fc8dd2 Merge main into feat/command-param-schemas 2026-11-09 15:16:23 -08:00
dotCooCoo 2e6ba25598 Commands: parameter schemas cover every route, bound hostile values and translate their errors
Check a new call's params against the schema in execute_spec, after the
key check and before the parser, and check every command.batch step
before the first one runs. menucmds::run_raw and the two other direct
runs go through the same check. Saved steps keep keys the schema does
not name and have the values it names checked.

Limit nesting to 16 levels, cut error text and paths to a fixed length,
and copy nothing before it has passed. Treat a whole number written
with a fraction as an integer. Check declared defaults and duplicate
names once at first use.

Give the five schema errors rows in the 22 complete-message catalogs.
2026-11-09 14:19:22 -08:00
Drew Dibble 78b31831c2 Artboards: Fit to Artwork Bounds' kept answer kept by the document, with the plug-ins and fonts it reads
The answer whether Fit to Artwork Bounds has art to fit moves from a
function-local thread_local to DocState, beside any_empty_artboards, and
drops with the document. It is kept for that version of the document
(held weakly, as any_empty_artboards holds its own) and the artboards
asked about, and measured again when the installed plug-ins, whose
effects draw art, or the installed fonts, which set type drawn by its
effects' art, change: their generations are an explicit input of the
measure. A change of the selection or the view keeps it.

The test counts the measures, as the Delete Empty Artboards check's
does: a later frame, select.set and a view change keep the answer; an
edit, other artboards, and each generation measure again. Taking any
part out of the key fails it.
2026-11-09 15:13:41 -07:00
dotCooCoo 5eb6b62632 Flatten: blend modes do not read the white under the art
Flatten Transparency composited each region over white, so a blend mode
on the bare page read that white. Raster renders now draw their
background under the finished art, and the flattened color no longer
matched them: a Color Burn object on the bare page flattened to white
and rendered in its own color. The region now composites the art alone
and puts the white under it.

- Tests that relied on the white under the art now draw art under the
  style (`every_builtin_style_paints_visibly_on_a_rectangle`) or compare
  only whole-pixel pans with a fresh render
  (`a_coarser_glow_kept_whole_draws_after_a_pan_as_made_again`).
- New tests cover every blend mode on white and blue backgrounds, a
  thumbnail and the ink planes with a Screen glow, and
  `over_background` at background and pixel alpha 0, 128 and 255.
2026-11-09 14:13:08 -08:00
dotCooCoo faea85f772 Fix #6125: raster exports keep a Screen glow on the bare page
A raster export filled its background into the page before the art, so
blend modes read it as art underneath: an Outer Glow in Screen mode, the
default, turned white on a white background and was missing from PNG and
JPEG files while the canvas showed it. Blending against nothing also
differed from the canvas for Difference and the other modes.

The background now goes under the finished art, in frames and in object
thumbnails. A blend mode reads only the art below it. A white object
under a Difference object still inverts.

Page Isolated Blending no longer changes a raster export on a bare page.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-09 13:02:28 -08:00
HassanKhan ff1079b3ce Auto Spell Check: underline the unknown words of the type on the canvas (part of #4936) 2026-11-09 23:41:41 +05:00
Fabio William Conceição a668f44780 G5: assert default 9-slice on every UI Frames symbol
Keep the library test aligned with SymbolNineSlice::default() for Panel, Button and Bar.
2026-11-09 16:40:13 +00:00
Fabio William Conceição e5ef855903 G5: size UI Frames so default 9-slice keeps round corners
Button and Bar were shorter than 4x their corner radius, so the default guides cut through the curves on a vertical resize.
2026-11-09 16:29:22 +00:00
Fabio William Conceição bd9b1696e0 G5: built-in UI Frames symbol library with default 9-slice
Add Panel, Button and Bar HUD chrome as a second original symbol library so games can drop resizable UI frames without inventing art each time.
2026-11-09 16:29:22 +00:00
Fabio William Conceição 6def1c6b04 Merge remote-tracking branch 'upstream/main' into feat/g5-dynamic-symbol-part-appearance
# Conflicts:
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-09 13:00:35 +00:00
Fabio William Conceição 1e278a3dff G5: dynamic symbol parts take opacity, blend and non-solid paint
Direct Selection of a path, compound path or type inside a dynamic instance now applies transparency and gradient or pattern fills through the usual paint commands, and the G5 docs catch up on Overlap and Pattern auto corners.
2026-11-09 12:14:14 +00:00
HassanKhan f0a4d59c93 Read the dictionary file whole before trusting it; keep other windows' words on a failed save 2026-11-09 16:23:08 +05:00
HassanKhan 11779d6d46 Merge remote-tracking branch 'upstream/main' into feat-4936-custom-dictionary
# Conflicts:
#	docs/gaps.md
#	docs/roadmap.md
#	docs/target-app-parity.md
2026-11-09 15:04:56 +05:00
HassanKhan a22cbd9d16 Part of #4936: never write over an unreadable dictionary; undo a failed save
- A custom dictionary file that is there but can't be read whole (not a
  file, unreadable, or larger than the 1,000,000 bytes craft-proof reads)
  is not loaded and never written over: attaching it logs why, and every
  add, delete or change is refused with that reason. A missing file is
  still created by the first save.
- When a save fails, the command takes back only its own change, so memory
  and file agree; words the save took in from the file stay.
2026-11-09 15:04:39 +05:00
BFlat c314ea25b9 Merge pull request #6293 from slafebre/fix/new-art-colorized-image
Follow-up to #6252: a selected colorized image gives new art its fill
2026-11-09 17:05:00 +08:00
HassanKhan 5bef0ed79a Part of #4936: custom dictionary and Ignore All commands
text.spelling.addWord, removeWord and changeWord are Check Spelling's Add
and Edit Custom Dictionary's Add, Delete and Change; text.spelling.dictionary
lists the words. They live in craft-proof, which every language's check
reads. The desktop app and vectorcraft-cli keep them in
`Custom Dictionary.txt` next to the preferences (none without preferences);
elsewhere they last until the app closes.

text.spelling.ignoreAll is Ignore All: the active document's checks skip the
word while it is open; not saved, not undoable, and not a change to the
document's revision.

Behaviour: https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/check-and-correct-spelling.html
2026-11-09 13:54:08 +05:00
BFlat 036b19402c Merge pull request #6285 from dotCooCoo/fix/plugin-grant-app-approval
Plug-ins: permissions and a heavy budget need a person's approval or a start-up flag
2026-11-09 16:43:58 +08:00
BFlat 1844904504 Merge pull request #6212 from Hassan1305/feat-4936-spell-check
Part of #4936: text.spelling.check lists unknown words, text.spelling.suggest suggests
2026-11-09 16:01:56 +08:00
dotCooCoo eebbe496d4 Merge main into fix/plugin-grant-app-approval 2026-11-08 23:53:57 -08:00
Sven Lafebre 8206f21912 Follow-up to #6252: a selected colorized image gives new art its fill
A grayscale image colorized with a fill color keeps that color outside its
appearance, so the check for art without paint treated it like an uncolorized
image and new art kept its old fill while the Fill proxy showed the color.
Count a colorize paint as paint: the image then loads like any object with a
fill and no stroke.
2026-11-09 08:52:02 +01:00
BFlat 0f2857e1a1 Merge pull request #6286 from dotCooCoo/fix/plugin-stale-grant
Plug-ins: replacing a granted plug-in with one that lists no permissions removes the grant
2026-11-09 15:41:04 +08:00
dotCooCoo 0efd9a9ccc Merge main into fix/plugin-grant-app-approval 2026-11-08 23:26:53 -08:00
BFlat c67cc90036 Merge pull request #6282 from dotCooCoo/fix/plugin-read-only-input-held-once
Plug-ins: a filter's read-only objects stay in its input and failed decodes count
2026-11-09 15:06:39 +08:00