Commit Graph
1531 Commits
Author SHA1 Message Date
dotCooCoo 2b7b7503eb Fix #6382: PDF export says how a page longer than 200 inches is written
A page with a side longer than 14,400 pt (200 in, ISO 32000-1 Annex C.2)
keeps its size only with a UserUnit, which PDF 1.6 added. Exports now
report such pages and handle the versions without a UserUnit:

- Each such page adds a warning with its size and its UserUnit. Apps
  that ignore UserUnit show the page that many times smaller.
- A PDF 1.5 file with such a page is written as PDF 1.6, with a warning.
- PDF 1.3 and 1.4 files, PDF/X-1a and PDF/X-3 among them, are refused
  with an error that names the page and the limit, instead of
  "PDF writer error: Limit(TooLargeFloat)".
- PDF/X-4 files at PDF 1.5 or 1.6 keep the page in points, with a
  warning that apps that keep to the limit cut it to 200 in.

The warnings and the error have rows in every complete catalog. Docs:
docs/mcp.md, docs/target-app-parity.md and the document.exportPdf
params.
2026-11-09 21:52:13 -08:00
Julius van der Vaart 89482ba940 Stroke: Line Arrow's arms match the line at 100 % only from 0.25 pt (#5421) 2026-11-10 06:48:16 +01:00
Julius van der Vaart c8d7f2cce0 Stroke: Line Arrow's arms take the head's weight, at least 0.25 pt (#5421) 2026-11-10 06:42:19 +01:00
dotCooCoo 9ccea9cfcb Commands: engine.apiReference returns the command API reference in Markdown
engine.apiReference {} -> {markdown} writes a reference from the built-in
commands' parameter schemas each time it runs: a section per command that
has a schema, in registry order, with its label, menu path, API levels,
what it writes, cost, summary and return, and a table of its parameters
(type, range with included or excluded ends, unit, JSON or worked-out
default, required, levels and description; object members as dotted
rows). Commands without a schema, plug-in commands included, are left
out. Schema text is escaped so it stays plain text in its cell, control
characters become spaces, and a reference past 1 MiB is an error rather
than a cut-short text. The command needs no document.

MCP serves the same text as the fixed resource
vectorcraft://commands/reference (text/markdown; charset=utf-8); the
other resources still read as JSON.

The command's own schema raises the schema API level to 2. Its label and
its size error have rows in every catalog that needs them.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-09 21:24:13 -08:00
Julius van der Vaart fe487d8226 Merge branch 'main' of github.com:storytold/vectorcraft into 5421-line-arrowhead
# Conflicts:
#	docs/target-app-parity.md
2026-11-10 06:17:42 +01:00
dotCooCoo 49129c565b Export: exported objects keep the clips, masks and effects around them
Export Selection, Export for Screens by selection or by layer, Asset
Export and CSS Properties' rasterized pictures put the objects on a new
plain layer. An object inside a clip group, or on a clipping layer, was
written without its clip, in SVG, PNG and the other formats, and the
opacity masks, effects, opacity and blend modes of the groups and
layers that held it were dropped too.

These exports now keep each object in the layers and groups that hold
it, and crop the file to the bounds the renderer measures for that art
(a clip group by its clip). Those layers are drawn even when they are
hidden, template or non-printing layers, as the objects were before. A
PSD of the export names its layers after the document's layers instead
of one layer named Selection or after the asset.

document.export with selectedOnly uses the same code: an object of a
blend, envelope, repeat or intertwine is now written on its own instead
of inside a blend of one object, and a Shaper group keeps its source
art. A text export of the selection still holds only the selected type,
not that of a type clipping path around it.
2026-11-09 21:10:08 -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
BFlat 2e5619db48 Merge pull request #6392 from dotCooCoo/feat/command-input-schemas
MCP: command listings give each command with a schema an inputSchema
2026-11-10 11:42:18 +07:00
dotCooCoo 0a7b48503e Merge main into feat/script-run 2026-11-09 20:30:53 -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
BFlat d06769c362 Merge pull request #6385 from Hassan1305/feat-4936-spelling-context-menu
Part of #4936: right-click an underlined word for its suggestions, Ignore All and Add to Dictionary
2026-11-10 10:51:03 +07:00
Julius van der Vaart b6e49fa4c0 Merge branch 'main' of github.com:storytold/vectorcraft into 5421-line-arrowhead
# Conflicts:
#	docs/target-app-parity.md
2026-11-10 04:40:09 +01:00
dotCooCoo 64da0213e4 Merge main into feat/command-input-schemas 2026-11-09 19:35:27 -08:00
dotCooCoo 9172950ad5 MCP: command listings give each command with a schema an inputSchema
engine.commands, command_list and list_commands now list an inputSchema
next to schema for each command with a parameter schema. It is the same
JSON Schema 2020-12 schema with the root type "object", the form an MCP
tool's inputSchema takes
(https://modelcontextprotocol.io/specification/2026-07-28/schema);
schema keeps the root type ["object", "null"] when no parameter is
required. A command without a schema lists neither field. tools/list is
unchanged, and the commands are still run with command_run or
run_command.

brief now leaves out schema and inputSchema as well as the params text;
a listing filtered to the command's id gives them.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-09 19:34:24 -08:00
dotCooCoo 16da351e5d Merge main into feat/script-run 2026-11-09 19:29:33 -08:00
BFlat 4dbaa10e0a Merge pull request #6365 from dotCooCoo/fix/issue-3617
Revolve: type, groups and layers revolve, and the profile and face limits are higher
2026-11-10 10:23:52 +07:00
BFlat 59cecd3d28 Merge pull request #6381 from dotCooCoo/fix/mcp-schema-keywords
MCP: docs list every JSON Schema keyword in a command's schema
2026-11-10 09:59:14 +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
HassanKhan 9560b7cfe9 Spelling in the canvas context menu: suggestions, Ignore All and Add to Dictionary on a word Auto Spell Check underlined (part of #4936) 2026-11-10 07:46:05 +05: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 db56690f48 MCP: docs list every JSON Schema keyword in a command's schema
The Parameter schemas section of docs/mcp.md now names every keyword
ParamSchema::to_json writes: $schema, description and deprecated at the
root, items for lists, prefixItems, items: false and minItems for a
point, anyOf and minItems for a color, and description, default and
deprecated on a parameter.
2026-11-09 18:19:24 -08:00
dotCooCoo c770e3bfea Revolve: mesh export cuts an edge where it crosses another contour
Mesh export decided whether an edge walls a hole at the edge's middle alone, so an edge that crosses another closed contour turned its whole length toward the hole, or none of it. Each sampled piece of a closed contour is now cut where it crosses another closed contour, and each part faces a hole only where it walls one.
2026-11-09 18:19:22 -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 ecb2fbe609 Revolve: the search for holes looks only at contours near each point
Mesh export tested the middle of every segment against every closed contour's bounds, so a profile of thousands of separate contours took hundreds of millions of comparisons before the work limit counted any. The closed contours now go in a grid over their bounds, a point is tested only against the contours of its cell, and placing a contour in a cell and each candidate looked at count toward the limit.
2026-11-09 18:12:27 -08:00
BFlat 51498975b4 Merge pull request #6368 from dotCooCoo/fix/measure-snap-to-glyph
Measure: both ends land on glyph guides with Snap to Glyph on
2026-11-10 08:58:14 +07:00
dotCooCoo cddebdf7f6 3D: a group's fill or stroke takes no 3D effect
Revolve, Extrude & Bevel, Inflate and Rotate on a group or layer draw only as the group's own effect. On the group's fill or stroke they drew nothing, yet effect.apply and effect.setParams accepted them. Both now refuse that placement.
2026-11-09 17:51:22 -08:00
dotCooCoo 32ddc21045 Merge main into fix/issue-3617 2026-11-09 17:39:37 -08:00
Julius van der Vaart db6e776e87 Merge branch 'main' of github.com:storytold/vectorcraft into 5421-line-arrowhead
# Conflicts:
#	docs/target-app-parity.md
2026-11-10 02:23:24 +01:00
dotCooCoo bb142cc063 Revolve: the fills and strokes of one object share the paint-copy budget
Each fill or stroke with its own Revolve counted the copies of its paint from zero, so an object with several of them could draw past the 256 MiB budget. They now share one count, and past it the source path draws, as for the members of a group.

effect.apply and effect.setParams on a fill or stroke now check the Revolve in that item's paint, after the item's geometry effects before it, together with the object's other items that carry a Revolve. Before, they checked the object's top fill.

docs/revolve.md marks the axis position, type handling and member paint as VectorCraft's own rules, and docs/mcp.md says how the budget is counted.
2026-11-09 17:19:13 -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
dotCooCoo 28fc436ef3 Measure: both ends land on glyph guides with Snap to Glyph on
The Measure and Measure & Inspect tools ignored View › Snap to Glyph:
their press and end points snapped only to targets that hold no glyph
guides.

With Snap to Glyph, Smart Guides and Alignment Guides on, and Snap to
Grid and Snap to Pixel off, a drag's press and end land within the
snapping tolerance on the guides of live type that the Character
panel's options allow, except Anchor Point. A Shift-drag's end slides
along its 45° step to them, and the guide shows during the drag. With
Snap to Glyph off, the tools snap to the same targets as before and
show no guide. The targets are kept while the document, the window and
the snapping options stay the same, so a drag lays out the type once.
2026-11-09 16:59:54 -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
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
dotCooCoo b07c808093 Revolve: type, groups and layers revolve, and the profile and face limits are higher
Type sweeps as its glyph outlines and stays editable. On a group or layer every visible member sweeps about one axis on the edge of the members' bounds together, each in its own paint.

A surface now takes up to 16,384 profile points and 163,840 faces. Curves take fewer samples, down to 4, when a surface passes those limits, and the command reports the failure below that, after the geometry effects before the Revolve and with the cumulative size of its paint copies counted. Mesh export turns the walls of holes toward them. Expand Appearance works on type with Extrude as well as Revolve.

Fixes #3617
2026-11-09 16:09:03 -08:00
dotCooCoo cf8b6cb3b8 Docs: the params text equals the text the schema writes
The params literals are kept in the source, and a test checks that each equals the text its schema writes.
2026-11-09 16:06:46 -08:00
dotCooCoo 27c9fc8dd2 Merge main into feat/command-param-schemas 2026-11-09 15:16:23 -08:00
BFlat 25cfa9d3cf Merge pull request #6355 from dotCooCoo/fix/issue-6125
Fix #6125: raster exports keep a Screen glow on the bare page
2026-11-10 06:05:32 +07:00
dotCooCoo d186d73c42 Commands: a schema with no required parameter allows null params
The engine reads null params as {}, but engine.commands listed every
schema with the root type "object", so a client that validated a call
against the schema refused one the engine accepts. A schema with no
required parameter now has the root type ["object", "null"]. A schema
with a required parameter keeps "object", because the engine refuses
null params for it as a missing parameter. Nested objects are unchanged.
2026-11-09 14:56:42 -08:00
dotCooCoo 93f75def04 Merge main into feat/command-param-schemas 2026-11-09 14:38:20 -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
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 39d538dadf Canvas: Cmd/Ctrl-wheel input finer than a whole notch zooms
With Scroll Sideways with Cmd/Ctrl + Wheel on, wheel() turned all
Cmd/Ctrl wheel motion into a sideways scroll unless it arrived as a
pinch event. A wheel turn now zooms about the pointer once it holds an
event from a trackpad source, point deltas from an unnamed source in the
desktop app, or line deltas that are not whole notches. Whole notches
keep scrolling sideways, and the option's default is unchanged. The same
rule applies with Zoom with Mouse Wheel on. egui-winit reports a wheel
source only on macOS, so the delta decides elsewhere.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-09 13:48:07 -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
dotCooCoo f688129ef2 Merge main into feat/command-param-schemas 2026-11-09 12:34:02 -08:00
Christian Lappin ef25dffe01 fix(mcp): validate and cancel Rotate View gestures (#6322) 2026-11-09 15:31:35 -05:00
Julius van der Vaart 7d73882d3a Stroke: Line Arrow arrowhead (#5421)
Add Line Arrow, a 41st generated arrowhead: two arms as thick as the line
(at 100 % scale) meeting at right angles in a sharp tip, cut square at their
ends. It is saved by the new name `LineArrow`, appended so existing names
stay stable, and listed third in the arrowhead menus after Arrow (open).

Its arms spread four weights either side of the line, twice as wide as the
other heads, so Arrowhead now states its own reach and half width. Visual
bounds and clicks on the canvas (head_reach, head_contains) use them instead
of one fixed 4.6 x 2 box; every other head keeps the sizes it had.

Tests: the outline test checks each head against its own reach and half
width; a new test checks the line arrow's tip, corners, open middle and arm
thickness; outset_covers_arrowheads checks that the wider head grows the
outset; the saved-name list includes LineArrow.
2026-11-09 20:47:31 +01:00
Christian Lappin 1c8503f3e2 fix(mcp): route Rotate View gestures to the canvas (#6322) 2026-11-09 13:50:36 -05: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 84e7d55bc8 G5: finish Eyedropper and undo wording for a picked part
copyFrom only writes paints enabled for pickup and apply, and paint, Transparency and the Eyedropper record an undo step only when the override changes.
2026-11-09 14:42:18 +00:00