Commit Graph
12247 Commits
Author SHA1 Message Date
BFlat ea3a4ec373 Merge pull request #3145 from adambelis/feat/width-smooth-profile
Width profiles run smooth between their points
2026-11-12 11:41:15 +07:00
makesomethingshit 7a5486d15d Merge upstream main into Live Paint Bucket fix 2026-11-12 13:39:41 +09:00
dotCooCoo b172e3672d JavaScript: for each evaluates its target before reading the value
A for each loop read the property's value before evaluating its target,
so a target such as (o.p = 2, t).v, t[(o.p = 3, 'w')] or a bare
@[(o.p = 2, 'k')] stored the value from before the target ran. The
target's object and name are now evaluated first and the value is read
after them (ECMA-357 section 12.3).
2026-11-11 20:39:02 -08:00
dotCooCoo e2eb364f83 Scripts: operators, JSON.stringify and join count a flattened string against the heap limit
Flattening a concatenated string copies its units into a new buffer. The
string built-ins reserve that copy with flat_charged, but several other
paths flattened without it, so a script could build copies the heap limit
never saw:

- the relational operators (`<`, `>`, `<=`, `>=`) on two strings, `==`
  between a string and a number, unary `+` and every other ToNumber of a
  string, and reading a character by index (`s[0]`)
- a property name made from an object whose toString returns a long
  string, and a long string used as a property name, which now reserves
  its copy before it is made instead of after
- direct `eval` of a concatenated source
- `JSON.stringify` of a string value and its `space` argument
- `Array.prototype.join`'s separator and each string element it writes

Property names and eval source reserve the copy's heap bytes without
taking fuel.
2026-11-11 20:35:33 -08:00
dotCooCoo 3fd653a25a EPS: Save As shows the saved artboard as the range when Use Artboards was on
A save that set useArtboards: true and named an artboard with artboard
or artboards, without range, wrote that artboard, but file.formatOptions
reported the default range. The Save As dialog then opened on All, and
OK saved every artboard. file.formatOptions now reports those artboards
as the range unless Use Artboards was saved off.
2026-11-11 20:35:16 -08:00
dotCooCoo 8b768bb27b Merge main into fix/inspect-library-ids-whole 2026-11-11 20:32:17 -08:00
dotCooCoo 66d21471ae Gradients: diamond gradients run the stops from the center out to a square
A new gradient kind, `diamond`, runs its stops from the center (the
start) out to a square with a corner at the end of the vector; the
aspect stretches the square across the vector into a rhombus. The
Gradient panel's Type row has a Diamond button, the Gradient tool draws
the square with the aspect handle and center ring, and a gradient
fitted to an object covers its box.

- Model and canvas: the level is |x| + |y| in the square's frame; a
  square with no area (no vector, an aspect of 0, numbers out of range)
  paints the last stop. The canvas draws it as pixels, out to a
  stroke's miters, caps and arrowheads. Transforms keep the vector and
  take the mapped size across it as the aspect.
- Commands: `paint.setFill`, `paint.editGradient` and
  `paint.setGradientGeom` take `kind: "diamond"`; a negative aspect or
  a focal point is an error. A conic or diamond gradient that a plug-in
  returns with a focal point loses the focal point.
- Gradient tool: dragging the aspect handle of a radial or diamond
  gradient whose file gave it a negative aspect now sets a positive
  aspect; only a conic's negative aspect mirrors it.
- Export: PDF, SVG, EPS and legacy .ai write four linear gradients, one
  for each quarter of the square, clipped to the shape (PDF as axial
  shadings). PDF and SVG write an image for translucent stops, a
  dithered fill or a flat square, and for the Perceptual method unless
  PDF writes the stops as spot color shadings. SVG writes a diamond on
  type characters as a radial gradient through the corners, and so
  does CSS. A swatch library saved as CSS gets four corner layers, a
  value for the `background` property (a circle when the swatch is
  turned by an angle that is not a multiple of 90 degrees). The .ai
  editing data holds a radial gradient through the corners. The
  document exports give a warning.
- SVG export: a stroke drawn as an image, as conic wedges or as diamond
  quarters now covers the miters, caps and arrowheads that reach past
  its paint box.
- PDF import reads a function-based shading whose color is the same
  along each square about the origin of its space as a diamond.
- Object > Expand gives nested squares or a mesh of rings.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-11 20:27:48 -08:00
dotCooCoo bd41117153 Merge main into perf/flatten-opaque-chain 2026-11-11 20:24:15 -08:00
dotCooCoo eff5765b07 Docs: Flatten Transparency's evidence line states when opaque art joins a group in its first sentence 2026-11-11 20:12:22 -08:00
dotCooCoo 69c3da175c Actions: Batch refuses options of the wrong type before it writes
action.batch read its true/false options with a fallback, so
{"stopOnError": "false"} kept stopping on the first error, and a
number for a string option such as label or datasetFileNaming was
dropped or turned into an empty string. The source check also ran
after the destination folder was made.

includeSubdirs, overrideOpen, overrideSave, overrideExport and
stopOnError now take only true or false, and source, folder,
destination, destinationFolder, datasetFileNaming and label take only
a string. Any other value is an error naming the key, given before the
destination folder is made or a file is opened. A null reads as a key
left out. The params text and docs/mcp.md say so.
2026-11-11 20:11:50 -08:00
dotCooCoo bd717a9018 Merge main into fix/script-sleep-reads-doc-cancel 2026-11-11 20:01:26 -08:00
dotCooCoo 8bd437687a Scripts: a canceled request stops a sleeping or waiting run at its next slice
script_pump read vectorcraft_doc::cancel only after the guard that keeps
a run waiting, so a run asleep in $.sleep went on waiting until its wake
time (up to 1,000 ms), and a run waiting at a dialog or for approval to
grow waited until someone answered. The check now runs before that
guard, after the dialog-failure check, so a canceled request ends the
run at the next slice and every document goes back.
2026-11-11 20:01:25 -08:00
dotCooCoo 903a626be6 Merge main into feat/learn-panel 2026-11-11 19:59:52 -08:00
dotCooCoo 225535b369 Merge main into feat/document-setup-color 2026-11-11 19:59:50 -08:00
dotCooCoo a9e7dfb2f8 Merge main into fix/options-dialog-menus 2026-11-11 19:59:50 -08:00
dotCooCoo d1ceb73b39 Merge main into fix/recovery-retake-lock 2026-11-11 19:59:49 -08:00
dotCooCoo 9197094803 Merge main into fix/plugin-approval-held-input 2026-11-11 19:59:35 -08:00
dotCooCoo c073ae2cb8 Merge main into fix/js-finally-sites-gc-test 2026-11-11 19:59:34 -08:00
dotCooCoo ccaaac26b0 Merge main into fix/e4x-xml-declaration-check 2026-11-11 19:59:33 -08:00
dotCooCoo 2ca196cb9c Merge main into fix/e4x-scope-name-assign 2026-11-11 19:59:32 -08:00
dotCooCoo f3b089f83f Merge main into feat/styles-textures-library 2026-11-11 19:59:30 -08:00
dotCooCoo a447b78ac4 Merge main into fix/plugin-grants-delete-test-portable 2026-11-11 19:59:24 -08:00
dotCooCoo d5044de8b1 MCP: another platform's absolute path is outside the read roots everywhere
Outside Windows, a path that starts with a drive (`C:`) or a `\` is read
as a name in the working folder, so with the working folder inside a
read root it passed the read check. ui.inspect then kept it in recorded
action steps and replies did not hide it when a request sent it. The
check now counts such a path as outside the read roots wherever the
control channel and the library commands check a path.
2026-11-11 19:59:09 -08:00
dotCooCoo ff3067c7f7 Layers: the thumbnail drag test measures renders by a test clock
row_thumbnails_wait_for_the_end_of_a_drag expected every row's
thumbnail to render in the first pass. The UI thread renders row
thumbnails for at most 8 ms of wall-clock time per pass, so on a busy
machine the first pass rendered only some rows, the next pass rendered
the rest, and the test failed at "and stay".

The thumbnail cache now takes a clock in tests (as the app's
now_override does for idle scheduling). The drag test gives every
render no time, and a new test gives each render 5 ms and checks that
three rows render two in the first pass and one in the next. The 8 ms
budget and the release build are unchanged.
2026-11-11 19:58:59 -08:00
dotCooCoo 7c28138ce3 Commands: pointer packets are read whole before any event runs
A headless or control channel pointer packet now checks every event's kind,
coordinates and mods before it sends any of them, so a malformed later event
no longer leaves the earlier ones applied. A mods value that is not an object,
such as an array, is refused. Rotate View takes null mods as omitted. The MCP
docs name the params that refuse null.
2026-11-11 19:57:39 -08:00
BFlat fd6b8f6c97 Merge pull request #6762 from storytold/fix/3d-part-tuple
3D: the shadow passes read the six-field solid parts again
2026-11-12 10:55:41 +07:00
dotCooCoo d151f3f5d0 JavaScript: source that eval and Function compile takes fuel before it is parsed
Direct and indirect eval, Function and host evaluators parsed and compiled
their source before taking any fuel or checking the heap, so a script with a
small fuel limit could make the interpreter parse megabytes of source in a
handful of instructions. Interp::prepare_source now takes one unit of fuel for
each UTF-16 code unit and checks that the source text fits in the heap before
the source is flattened, parsed or compiled. A host evaluator's string takes
the fuel for a third of its bytes before it is encoded, and the rest after.

Limits::fuel and the maxFuel row in docs/mcp.md say so.
2026-11-11 19:52:06 -08:00
dotCooCoo c792bdf0d3 Plug-ins: a key or button held before the approval's OK took input approves nothing
The install approval's OK now also waits for these to be let go of:

- Enter or Space held down while OK was disabled, whose key repeat
  clicked OK once it had the keyboard focus.
- A pointer button held while OK was disabled, even after another
  button is pressed once OK takes input.
- A pointer button held on the question before, when a new question
  takes its place.

The wait also starts over when the window lost the focus since the
frame before, even if it has the focus again by the next frame.
2026-11-11 19:49:06 -08:00
dotCooCoo f65f3029a6 Image Tools: Remove Background clears an image's background in its pixels
A new built-in add-on, Image Tools (Object › Plug-ins › Image Tools),
adds Remove Background… (`addon.image.removeBackground`). It makes the
background of the selected images transparent by lowering the alpha of
their own pixels, in one undo step, with no service or model. The
background is the most common color along the image's edges, the
average of the image's corners, or colors the user picks. Contiguous
(on by default) clears it only where it reaches in from an edge; off,
it is cleared wherever it is. Tolerance and Softness (0 to 100, 10 by
default) set how near a color must be to be cleared and the band over
which pixels fade out. Match measures that nearness as the RGB
distance or as the largest difference on one channel. It is a
VectorCraft addition.

- The image stays an image: an embedded PNG with alpha, the source's
  ICC profile and resolution, and 16 bits or gray when the source is.
  A linked image is embedded and keeps its Placement Options.
- When the edges hold at least as many clear pixels as pixels of their
  most common color, edge mode clears nothing, so a second run leaves
  the subject alone. Corner mode leaves clear corners out of the
  average and clears nothing when all four are clear.
- With Match set to Each Channel, a tolerance of t / 255 × 100 clears
  the colors within t 8-bit steps on every channel.
- A CMYK image, a linked image that shows its preview, an image that
  would need more than the decode allowance (checked from its header)
  and a result with no pixel left are refused, and the document stays
  as it was. When no pixel changes, no undo step is added. The result
  reports `cleared`, the pixels made fully clear.
- Picked colors are hex colors, with or without `#`, or black, white,
  red, green or blue.
- The dialog opens with Preview off; with Preview on, the canvas shows
  the result, and Cancel puts the image back. While the dialog
  previews, the session keeps the images' decoded pixels (at most
  128 MiB, 32 MiB in the web app) so each preview step does not decode
  them again, and releases them when the dialog closes with OK or
  Cancel. A run that is not a preview step keeps nothing.

Docs: docs/mcp.md (Image Tools add-on), docs/target-app-parity.md.
2026-11-11 19:47:28 -08:00
dotCooCoo 9d9cb52017 MCP: hidden library paths are matched before the paths a request sent
A sent path whose text overlapped part of a hidden library's path could
keep that path from being hidden; the hidden libraries' ids and paths
are now matched first. A hidden relative path is also hidden where a
reply shows the absolute path it reads as from the working folder, and
a hidden library sent by that other form shows as the sent path does.
2026-11-11 19:44:43 -08:00
dotCooCoo e609b26a95 Warp: bend each style as one Bézier patch, and keep saved warps' look
Each of the fifteen Warp styles now bends the bounds as one Bézier patch.
The style sets the patch's control points from the bend and the size of
the bounds, the horizontal distortion scales each column of control
points about its middle, and the vertical distortion scales each row.
The Warp effects and warp envelopes share the map (`WarpNet`). Content
with no width or no height takes the middle of the patch along that
side.

A warp carries the map it draws with as `version`. New warps, the
built-in graphic styles' Warps and warps opened from .ai files get 1.
Warps in .vectorcraft documents saved before the version existed read
as 0 and keep the earlier map, so those documents keep their look.
Edits keep a warp's version, and Warp Options editing several objects
keeps each one's own. effect.apply, effect.setParams and the warp
envelope commands take `version` 0 or 1 (`1.0` counts as 1) and refuse
any other value. An .ai file holds a warp's settings and not its
version, so a version 0 warp saved to .ai opens again with version 1.
The .ai writer compares a style's settings without the version, so
saved warps still export as editable effects.

Docs: docs/mcp.md (Envelopes), docs/effects-parity.md (Warp),
docs/target-app-parity.md.
2026-11-11 19:40:01 -08:00
dotCooCoo db97cc1a8f JavaScript: a bare E4X name inside with or a filter can be assigned
With Options::e4x set, an E4X name standing alone (@a, @ns::a, @[expr],
ns::a, *) is now a valid target for =, compound assignment, ++, --,
delete and for/for each loops, as well as a read. The target is the
nearest XML or XMLList value in scope whose [[HasProperty]] accepts the
name (ECMA-357 section 11.1). The name is evaluated once, and the
target resolves before the right-hand side runs.

When no XML value in scope holds the name, a read or write throws a
ReferenceError and creates no child or attribute. A read no longer
falls back to the first XML value in scope, so a filter such as
list.(@id == '1') throws when an item has no @id and no outer XML
value holds one, instead of treating that item as not matching.
2026-11-11 19:35:59 -08:00
dotCooCoo 781357921e Scripts: E4X checks the XML declaration and takes fuel for every run the parser scans
An XML declaration at the start of XML or XMLList text was skipped up to
the next "?>" without being read. It is now checked against XML 1.0's
grammar (https://www.w3.org/TR/xml/#NT-XMLDecl): a version of "1." and
digits, then an optional encoding name and an optional standalone "yes"
or "no", in that order, with optional white space before "?>". Any other
declaration gives "SyntaxError: invalid XML declaration", and a character
XML does not allow gives "invalid character in XML text". White space
before the declaration is still accepted.

The parser now takes fuel for the length of the declaration and the
white space before it, element and attribute names, white space in open
and closing tags, and the text it searched when an attribute value, CDATA
section, comment or processing instruction is not closed, or when "]]>"
or "<" stops a text run or attribute value. A long run in any of these
used the same fuel as a short one.
2026-11-11 19:35:39 -08:00
dotCooCoo bfabe4f980 Merge main into perf/flatten-opaque-chain 2026-11-11 19:18:07 -08:00
dotCooCoo f95c4b51c9 Flatten Transparency: opaque art that touches only opaque art stays vector
Grouping for flattening joined every pair of objects whose 1 pt reach
boxes touched, transitively, so a row or grid of opaque objects next to
one see-through object became one group and was flattened with it.

Now a group holds each see-through object and every object its reach
touches. An object without transparency joins only where its reach
touches a member and it lies above that member but below the group's
topmost object, whose place the group takes, so the stacking order
stays the same. In files without transparency with Clip Complex Regions
on, art under a group within one image pixel of a member joins it too,
with the pixel size taken from the image's actual resolution. Objects in
an overlap set that includes one inside a clip group or clipping layer
keep the previous grouping.

The 1 pt antialiasing margin now goes around a group's art once instead
of once per nested group (a stroked path's plain art is a group, so it
had 2 pt).

On 3,000 stroked rectangles, 60 with a drop shadow, the Flattener Preview
counts 180 affected objects where it counted 3,000, and Flatten
Transparency with the Medium preset makes 240 flat regions and 2 images
where it made one image of the whole grid.
2026-11-11 19:17:06 -08:00
dotCooCoo 50efa16303 MCP: a hidden library keeps the id it was first shown under
A library loaded from outside a confined client's read roots keeps its
`loaded/<file name>` id for the session, in ui.inspect, ui.docking and
the library commands, so an id taken from an earlier reply still finds
the same library after another library with the same file name is
loaded. swatch.library.copyToUser leaves out `path` when the file is
outside the read roots, as the saves do.

The ids come from the engine's concealed_library_ids, which now reads a
path written with either separator, counts another platform's absolute
path as outside the roots, and keeps each id in the session.
2026-11-11 19:16:17 -08:00
dotCooCoo dea30d6328 Scripts: a finally block left early drops its saved throw site
A finally block that break or continue leaves no longer keeps the saved
site of the exception it ran for. A Jump now removes the sites at or
above the stack's length, as a return already does, so a collection
after the stack grows back over that slot does not root and count the
old code.

New tests in crates/js/src/host_tests.rs: an error passed on by a
finally block that collects garbage under a 1 MiB heap is reported at
its throw; the saved site keeps direct eval code live through a
collection; a host abort inside a finally block leaves no saved site;
and break, continue and return out of a finally block drop the site.
2026-11-11 19:14:05 -08:00
dotCooCoo 1603e78afa Merge main into fix/inspect-library-ids-whole 2026-11-11 19:13:35 -08:00
bflatastic c58bd01525 3D: the shadow passes read the six-field solid parts again
#6716 added the member index to each solid part after #6754's shadow
code was written against the five-field tuple, so main stopped compiling
once both landed. The Inflate, Extrude and shadow-drawing loops now
destructure all six fields.
2026-11-12 10:11:01 +07:00
dotCooCoo 396de45501 Merge main into feat/learn-panel 2026-11-11 19:10:42 -08:00
dotCooCoo 4f08f19ce4 Learn: a Learn panel lists tutorials by skill, opened from Explore tutorials on the app bar
Window › Learn and a new Explore tutorials button beside the search icon on
the app bar show and hide the Learn panel. Its For you tab holds a Get started
section with a Quick skill guide card that opens the Skills tab. The Skills tab
lists VectorCraft's own skills with their tutorial counts and empty progress
rings; a skill lists its tutorials, a tutorial shows its numbered steps, and
Back goes up one level. The tutorials live in a data catalog whose test checks
unique ids, that every named tool and command exists and that no skill is
empty.

- window.learn {show?} shows, hides or toggles the panel; showing it also
  shows a hidden dock, and the panel counts as hidden while the dock is hidden
  or a full screen mode hides the panels.
- learn.list lists the skills and tutorials; learn.show {tutorial} opens one.
  A missing, non-string or unknown id is an error and changes nothing.
- New labels and messages have rows in every catalog. The tutorial text is
  English.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/ui-parity.md.
2026-11-11 19:10:02 -08:00
dotCooCoo 7559607037 Document Setup: a Color tab sets the color mode and the assigned profiles
Document Setup gets a Color tab with the document color mode (RGB Color
or CMYK Color), a Convert to CMYK or Convert to RGB box for a mode
change (cleared, the colors keep their numbers), and the RGB and CMYK
profiles assigned to the document, with a working-space entry for none.

When the tab changes something, OK runs document.setup, then
edit.assignProfile, then file.documentColorMode in one command.batch,
and the document's changes are one undo step. Assign Profile runs first,
so a mode change converts through the profile picked with it. An
unchanged Color tab adds no color commands, and Cancel, or OK with
nothing changed, records no undo step. A Color tab field with a value of
the wrong kind is an error, and the dialog stays open with nothing
changed. File › Document Color Mode and Edit › Assign Profile… stay
where they are.

Catalogs: "Working RGB (don't tag)" and "Assign Profile" rows in every
complete catalog, and the "`{key}` must be a profile name or null"
message in every complete-message catalog. Docs: docs/mcp.md,
docs/control-protocol.md, docs/target-app-parity.md.
2026-11-11 19:08:06 -08:00
BFlat 6b8be09699 Merge pull request #6688 from dotCooCoo/fix/plugin-call-reprepare-test
Edit: a command that ends a drag keeps its unapplied preview
2026-11-12 10:06:12 +07:00
dotCooCoo 935ebccc25 EPS: Save As options are menus with readable names, and Export As's Range shows an example
Save As to an EPS file showed Preview Format, Overprints and Flattener
Preset as text fields holding their ids (tiffColor, preserve, medium),
the PostScript level as a number field, and labeled every EPS option
except Use Artboards from its name ("Cmyk Post Script", "Thumbnails").

The dialog now shows the options Export's EPS Options shows. Preview,
Overprints, Flattener preset (the built-in presets, then saved ones) and
PostScript are menus of readable names. Transparent and Opaque apply to
the color TIFF preview only. Embed Fonts shows checked and disabled. The
PostScript level comes last. A pick writes the same values as before
(previewFormat "tiffColor", a built-in preset's id, a saved preset's
name, the level as a number). A value that the save would reject shows
as it is.

Every option the dialog shows has a written label. "Preview:" has a row
in every catalog, and the de, it, ja, ko, uk, vi and zh-hant catalogs
gain the menu names, which Export's EPS Options also showed in English.

Export As's Range field shows the "1-3, 5" example when emptied, as
Save As's does.
2026-11-11 19:04:27 -08:00
dotCooCoo 17a51fc591 Data Recovery: a running app locks its area again when the lock file is removed
While an app runs, its Data Recovery area's .lock file, or the whole area
folder, can be removed by the user, a cleaner or another program. The app
kept writing copies into an area that no lock held, and another app could
lock that area and offer the copies as a crash's copies.

Each recovery save and heartbeat now checks that the file at the area's
.lock path is still the file the app locked. When the lock file or the
folder is gone, or another file is in its place, the app makes the area
again if it is gone, locks it again, and writes again the copies that went
with the folder. A lock file that can't be read for another reason still
counts as held. When another app holds the area by then, the app takes a
new area and writes every copy there. The app does not offer or discard
its own copies in the old area as a crash's copies. It removes each one
when its document has a copy in the new area, or is saved, reverted or
closed, and removes all of them when it quits with nothing unsaved. The
app that holds the old area also removes them when it quits with nothing
unsaved.

A copy that was being written when the Data Recovery folder preference
changed is now removed from the folder it was written to. Before, the
document took that copy as its current one, and the copy stayed in the old
folder after the document was saved or closed.
2026-11-11 19:04:16 -08:00
dotCooCoo c6d9712b9e Merge main into fix/plugin-grants-delete-test-portable 2026-11-11 19:03:22 -08:00
BFlat 433a3009b7 Merge pull request #6701 from dotCooCoo/fix/xml-namespace-scope-fuel
JavaScript: XML namespace lookups take fuel for each ancestor they climb
2026-11-12 10:03:17 +07:00
BFlat bac3be03cb Merge pull request #6716 from dotCooCoo/fix/3d-fill-walls-behind-stroke
3D: a stroke's walls cover the fill's walls at concave corners
2026-11-12 10:03:08 +07:00
dotCooCoo 1716f10790 Merge main into feat/type-path-center-on-top 2026-11-11 19:00:23 -08:00
dotCooCoo 3e0d5a0aaf MCP: a library saved with another platform's absolute path is hidden under a read root
Outside Windows, a library path that starts with a drive or a backslash counts as outside the read roots, since the file access check would read it as a name in the working folder.
2026-11-11 18:59:31 -08:00