Commit Graph
3019 Commits
Author SHA1 Message Date
dotCooCoo fdbf85d4c8 Stores: saving a preset, profile or plug-in reads at most one byte past it from the file it replaces
Before writing a PDF preset, ICC profile, plug-in, Color Settings,
disabled list or grants file, the store compared the new bytes with the
file already there by reading the whole file into memory, so a large file
left in the folder was read in full. The comparison now reads at most one
byte more than the new content.
2026-11-10 22:24:39 -08:00
BFlat b34c652a60 Merge pull request #6514 from ruchirguitar/fix/5820-general-units-for-unitless-files
#5820: files that record no units open in Preferences › Units › General
2026-11-11 12:35:44 +07:00
BFlat 22ec820569 Merge pull request #6513 from dotCooCoo/fix/break-link-keeps-hidden-locked
Symbols: Break Link keeps a hidden or locked instance's art hidden or locked
2026-11-11 12:00:47 +07:00
BFlat acc6acfdb2 Merge pull request #6266 from Silvertree2010/fix/h13526-graph-csv-round-trip-drops-a-bla
Keep blank value row in single-column graph CSV round trip
2026-11-11 11:52:07 +07:00
BFlat bc814012d9 Merge pull request #6511 from dotCooCoo/feat/artboard-color-labels
Artboards: an artboard's guide label colors its own guides
2026-11-11 11:27:16 +07:00
ruchirguitar f5daa27a2f #5820: files that record no units open in Preferences › Units › General
A PDF, an EPS or .ai without ruler units, a raster image, a metafile, an
Affinity file or an importer plug-in's document opened (and reverted) in
points whatever the General unit was. They now open in the General unit,
as the reference app opens files that record no unit. Native, SVG and DXF
files and a .ai or EPS with %AI5_RulerUnits keep their own units. Only the
displayed units change, not the art.
2026-11-11 09:56:19 +05:30
NetterSilverandClaude Haiku 5.5 aaf27e1ec6 Keep blank value row in single-column graph CSV round trip
Found while testing crates/engine/src/cmd/graph/data.rs:51 (parse_csv); written by crates/doc/src/graph.rs:497 (GraphSpec::to_csv)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 04:16:30 +00:00
dotCooCoo 2faadd28f4 Symbols: Break Link keeps a hidden or locked instance's art hidden or locked
Break Link to Symbol on a selected group, and Delete Symbol with its
instances expanded, turned a hidden or locked instance into visible,
unlocked art. The art now keeps the instance's hidden and locked state,
as Object > Expand does, and Break Link leaves hidden or locked art out of
the selection.
2026-11-10 20:09:28 -08:00
BFlat 98fe060bb0 Merge pull request #6499 from dotCooCoo/feat/script-console
Scripts: Window › Script Console shows script output, errors and run reports
2026-11-11 10:48:06 +07:00
dotCooCoo beef32b8a3 Artboards: an artboard's guide label colors its own guides
Each artboard can carry a guide label: one of the layer colors or a
custom RGB. Guides that belong to an artboard draw in its label color,
and guides without an artboard, or on an artboard without a label, keep
the guide color from the preferences. Selected guides still draw in the
selection color.

- Artboard Options has a Guide Label row (None, the layer colors with
  swatches, or Custom with a color swatch).
- artboard.new and artboard.setProps take guideLabel: null, a layer
  color's name, or #rrggbb. Any other value is an error. The inspect
  summary lists guideLabel when one is set.
- The native format saves the label and skips it when unset; a preset
  index outside the layer colors reads as no label.
- artboard.setProps no longer adds an undo step when nothing changes.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-10 19:37:17 -08:00
BFlat ec89f3f26e Merge pull request #6501 from dotCooCoo/feat/prefs-export-import
Preferences: export and import preferences as a JSON file
2026-11-11 10:01:01 +07:00
BFlat 4c2b8df865 Merge pull request #6498 from dotCooCoo/fix/smart-guides-snap
Smart Guides: tools do not snap to Smart Guides while Pixel Preview is on
2026-11-11 09:26:08 +07:00
dotCooCoo d426c70a92 Preferences: export and import preferences as a JSON file
Part of #6435. An optional VectorCraft feature: the Preferences dialog's
Export Preferences... saves the applied preferences to a JSON file (the
web build downloads it), and Import Preferences... applies such a file and
shows the imported values. They run the new commands prefs.export {path?}
and prefs.import {path | data}.

The file holds every preference prefs.list lists and the eyedropper,
perspectiveWidget, pathfinder and glyphSnap groups, without the four
folder preferences and saved presets. prefs.import reads at most 1 MB and
checks every value first: another format, a newer version, an unknown
preference, a bad value or a folder preference is an error, and nothing
changes. It keeps the open document's units, records no undo step, and is
refused as a command.batch step. prefs.set now rejects a group value that
is not an object.
2026-11-10 18:11:01 -08:00
BFlat af66951c41 Merge pull request #6496 from dotCooCoo/feat/ruler-artboard-guides
Guides: an option makes Cmd/Ctrl ruler drags artboard guides
2026-11-11 09:04:35 +07:00
BFlat 69d36e961d Merge pull request #6494 from dotCooCoo/feat/dimension-horizontal-vertical
Dimension tool: linear dimensions measure horizontally, vertically or aligned
2026-11-11 08:50:56 +07:00
dotCooCoo 4060480ef1 Smart Guides: tools do not snap to Smart Guides while Pixel Preview is on
With View > Pixel Preview on, the tools still snapped to Smart Guides
and showed their guides; only the Object Highlighting outline was
hidden. Work with Smart Guides says Smart Guides cannot be used while
Snap to Grid or Pixel Preview is on.

ViewInfo has a pixel_preview field, and Session::with_tool_cx turns
Smart Guides (and Snap to Glyph, which needs them) off in the tools'
context while it is set. VectorcraftApp::view_info passes View > Pixel
Preview. View > Smart Guides keeps its setting.

Docs: docs/target-app-parity.md.
2026-11-10 17:43:29 -08:00
BFlat 4302b7bde4 Merge pull request #6492 from dotCooCoo/fix/reshape-stretches-whole-path
Reshape: a drag stretches an open path from the dragged point to its two ends
2026-11-11 08:31:45 +07:00
dotCooCoo df9c8d992d Merge main into feat/script-console 2026-11-10 17:27:35 -08:00
dotCooCoo 342d082c2b Scripts: Window › Script Console shows script output, errors and run reports
Scripts write to a new Script Console with $.write(...values) and
$.writeln(...values), each value as String(value) gives it. vc.log
entries, a run's uncaught error as name:line:column: message, and a
start, end and report line for each run go there too. The report line
gives the run's time, host calls and slowest commands.

- The console keeps at most 2,048 lines and 1 MiB of text, dropping the
  oldest and counting what it dropped. A line is cut at 4,096 bytes and
  a script's name at 256 bytes, with a count of cut lines. Line feeds,
  carriage returns and CRLF break lines; other control characters show
  as Unicode Control Pictures.
- script.run's result and scriptReport gain runTimeMs, timingAvailable,
  hostCallCounts, slowestCommands and the run's own console. A failed
  run's report has them too.
- script.console and script.console.clear read and empty the console;
  ui.copyScriptConsole {first?, last?} copies lines as the panel shows
  them.
- The panel lays out only the rows in view, selects with click and
  Shift-click, and has Clear, Copy Selected and Copy All. Output goes on
  while it is closed, and Clear works during a run.

Docs: docs/mcp.md, docs/gaps.md, docs/roadmap.md,
docs/target-app-parity.md.
2026-11-10 17:26:30 -08:00
dotCooCoo ed17e8fc17 Dimension tool: linear dimensions measure horizontally, vertically or aligned
A linear dimension between two clicked points, or dragged out from a
segment, always measured the straight distance between its points and
drew its line parallel to them, so two points at different heights gave
only a slanted dimension. The linear dimensions help describes aligned,
horizontal and vertical linear dimensions: after the second click,
clicking again gives an aligned one and moving the pointer gives a
horizontal or vertical one.
https://helpx.adobe.com/illustrator/desktop/measure-and-align/plot-and-measure/plot-linear-dimensions-of-objects.html

While a linear dimension is being placed, the pointer's position sets
whether it is aligned, horizontal or vertical. The rule is VectorCraft's
own. With the pointer beside the two points (the nearest point of the
line through them lies between them) or on that line, the dimension is
aligned. Past either point, the line through the points and the
perpendicular through that point bound a quarter on each side of the
line. The quarter that holds the direction straight up or down gives
the horizontal distance, and the quarter that holds the direction
straight left or right gives the vertical distance. A click within
reach of the second point, or of where the pointer was for the second
click, places it aligned. Points on one horizontal or vertical line
give an aligned dimension. The dimension stays linked to the anchors it
measures.

- Dimension recipes carry an orientation (aligned by default; files
  without it open as before). The line runs along the axis, and each
  extension line runs from its point to it.
- dimension.create and dimension.update take orientation: aligned,
  horizontal or vertical, for linear dimensions only. Any other value
  is an error. A new orientation keeps the line on the side of the
  points it was on, and an update that changes nothing records no
  undo step.
- A horizontal or vertical dimension keeps its orientation when it or
  the art it measures turns, and shows 0 when its points come into line
  across its axis. A line that lay outside the points stays outside
  them after a turn.
- The hint bar names the two-click placement.

Docs: docs/dimensions.md, the Evidence list in docs/target-app-parity.md.
2026-11-10 16:54:35 -08:00
BFlat 7ea056c7bd Merge pull request #6489 from dotCooCoo/fix/perspective-snap-first-unattached
Perspective grid: Snap to Grid reads a flat first object at the depth it attaches to
2026-11-11 07:51:50 +07:00
BFlat 6dbbdb769c Merge pull request #6477 from dotCooCoo/fix/text-style-param-types
Type: character formats, Ruby and Move Type Path Handle reject a param of the wrong type
2026-11-11 07:39:31 +07:00
dotCooCoo a2d13cd6a8 Merge main into feat/ruler-artboard-guides 2026-11-10 16:27:09 -08:00
BFlat e60da871bc Merge pull request #6485 from dotCooCoo/fix/spot-gradient-cmyk-doc-docs
Docs: spot gradients with RGB or Lab stops keep the ink in a CMYK document
2026-11-11 07:26:40 +07:00
dotCooCoo 91509f1776 Guides: an option makes Cmd/Ctrl ruler drags artboard guides
Preferences > Guides & Grid gets Artboard Guides with Cmd/Ctrl + Ruler
Drag (cmdRulerDragArtboardGuides), off by default. With it on, Cmd/Ctrl
held when a drag leaves a ruler makes a guide of the active artboard,
with any tool but the Artboard tool; with the Artboard tool it makes a
canvas guide instead. The key is read when the drag starts, so letting
it go during the drag keeps the kind of guide. Such a guide shows and is
made only where it lies within the active artboard; released elsewhere,
it makes no guide and no undo step. A ruler click still makes no guide.

This is VectorCraft's own option. The documented way to limit a guide
to one artboard, dragging it out with the Artboard tool, is unchanged.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-10 16:26:14 -08:00
BFlat b3bb905bb6 Merge pull request #6484 from dotCooCoo/fix/batch-folder-stem-case-tests
Actions: test Batch folder names that differ only in case or are empty
2026-11-11 07:21:26 +07:00
dotCooCoo 1d41174876 Reshape: a drag stretches an open path from the dragged point to its two ends
A Reshape drag on an open path moved only the anchors within half the
diagonal of the path's bounding box, measured along the path. On a
zigzag, a wave or a spiral that is one or two anchors, so dragging an
end moved the end and left the rest of the path where it was (#2723).

path.reshape now stretches an open path from the grabbed point to both
ends. The end anchors stay in place. Each anchor between an end and the
grabbed point takes u² (3 - 2u) of the drag, where u is its distance
from that end along the control polygon divided by the grabbed point's
distance from that end. A handle takes the share on the tangent line of
that curve at its anchor, so the handles of a smooth anchor stay in
line with it. A handle that is long beside the span takes a share below
0 or above 1.

This changes the default for every caller that leaves radius out on an
open path. A closed path still moves the anchors within half the
diagonal of its bounding box, and a call that gives radius uses that
radius on open and closed paths.

- A negative radius is refused ("`radius` must be 0 or more"). It gave
  anchors beyond the radius a share above 1.
- A drag of no length on an anchor no longer records an undo step.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-10 16:11:52 -08:00
dotCooCoo 3fc083d732 Perspective grid: Snap to Grid reads a flat first object at the depth it attaches to
When the first object of a perspective move was not yet in perspective
and a later one was attached deeper on the same plane, Snap to Grid and
Shift read the shared offset at the later object's depth and left the
first object out of the joint bounds. The first object then moved by
that offset instead of the one under the pointer at its own depth.

The move now reads Snap to Grid and Shift with the objects that are not
in perspective attached to the active plane, as the move attaches them,
so the result matches attaching them first and then moving.
2026-11-10 16:09:01 -08:00
BFlat 745fb4edd7 Merge pull request #6482 from dotCooCoo/fix/prefs-reset-keeps-document-units
Preferences: Reset Preferences leaves the open document's units alone
2026-11-11 07:04:48 +07:00
dotCooCoo ea612dbf19 Type: use pathops' number and length helpers; refuse a number with trailing text
text.setStyle now reads `size` and `tracking` with `number_given` and
`length_given` from pathops.rs instead of its own copies.

Both helpers read a string only when all of it is a number, or for
`length_given` a length with a unit (`"5 mm"`). Before, `"20abc"` read
as 20, so `text.setStyle` applied a 20 pt size and Offset Path a 10 pt
offset for `"10abc"`. Such a string is now refused with "`size` must be
a number, not \"20abc\"" and the document is left as it was.
2026-11-10 16:00:07 -08:00
BFlat c6f861808b Merge pull request #6481 from dotCooCoo/fix/repeat-param-types
Repeat, Pattern: the Repeat and pattern commands reject a param of the wrong type
2026-11-11 06:57:27 +07:00
BFlat fb10a6092f Merge pull request #6479 from dotCooCoo/fix/cut-ellipse-end-drag
Scissors: a cut leaves one of the two new end points selected
2026-11-11 06:52:41 +07:00
dotCooCoo ab27448921 Docs: spot gradients with RGB or Lab stops keep the ink in a CMYK document
Two PDF export passages in docs/target-app-parity.md said a gradient
that mixes a spot color with RGB or Lab stops is always written without
the spot ink, or as an image when Perceptual. A CMYK document, or an
export that converts colors to a CMYK destination profile, writes those
stops in CMYK first, so the gradient is a DeviceN shading that keeps the
ink; an RGB destination profile keeps them in RGB. The passages now say
so, as docs/mcp.md and the paint.editGradient params text already do.

A new test checks the Classic case in an RGB document, a CMYK document,
with a CMYK destination and with an RGB destination.
2026-11-10 15:34:06 -08:00
dotCooCoo 74450ca52b Actions: test Batch folder names that differ only in case or are empty
Batch with Override Export compares output names without case, so
`a.svg` and `sub/A.svg` get the Export for Screens folders `a` and
`A-2`. A source whose name without its extension is empty (` .svg`)
gets the folder `file`, and a later `file.svg` gets `file-2`. Two tests
cover these cases.
2026-11-10 15:29:30 -08:00
dotCooCoo 272dab7b0a Merge main into fix/text-style-param-types 2026-11-10 15:27:58 -08:00
dotCooCoo 6732dbab1e Preferences: Reset Preferences leaves the open document's units alone
Reset Preferences put Points in Units > General, and OK sent it to
prefs.set, which also set the open document's units as an undo step.
OK after Reset now resets the General preference and leaves the
document's units as they are. Choosing a General unit after Reset, in
the dialog or with ui.dialog.set, still sets the document's units.

prefs.set takes documentUnits (default true); false makes unitsGeneral
set only the preference. Docs: params text, docs/mcp.md.

Part of #6435
2026-11-10 15:21:18 -08:00
dotCooCoo 81dc541d5d Repeat, Pattern: the Repeat and pattern commands reject a param of the wrong type
object.repeat.radial, object.repeat.grid, object.repeat.mirror and
object.repeat.options, and object.pattern.make, object.pattern.edit,
object.pattern.saveCopy, pattern.options and pattern.delete, read their
params with helpers that fell back to the default for a value of the
wrong type: "instances": "8" made 8 instances whatever the text said,
"rows": "2" made 3 rows, "edit": "false" entered pattern editing mode,
and a `name` that was not a string edited the selected object's pattern.
Each such value is now an error naming the param, and the document, the
selection and the undo history stay as they were. An omitted or null
param keeps its default.

- pattern.options refuses an `overlap` that is not an object, and an
  `overlap.h` other than left or right or an `overlap.v` other than top
  or bottom.
- object.repeat.options refuses a `center` that is not [x, y].
- object.repeat.radial refuses a negative `radius`; it used the
  automatic radius before.
- object.repeat.grid reads `rows` and `cols` as object.repeat.options
  does: a fraction is cut to a whole number, within 1 to 500.
- object.repeat.options applies `brickOffset` given without `gridType`
  to a brick grid, and its params text lists the key.

Docs: docs/mcp.md.
2026-11-10 15:08:03 -08:00
dotCooCoo 7eb1006abb Scissors: a cut leaves one of the two new end points selected
After a Scissors cut (`path.split`) the whole path stayed selected: both
pieces of an open path, or the opened path of a closed one. A Direct
Selection drag from the cut then moved every anchor, so an ellipse cut
at an anchor could not be pulled open by dragging its end.

- `path.split` selects the first anchor of the piece after the cut and
  nothing else, as `path.cutAtAnchors` does: anchor 0 of an opened
  closed subpath, of the new path when an open path becomes two, or of
  the subpath added to a path of several subpaths.
- A Direct Selection press on anchors of a selected path that lie on top
  of each other takes the selected one. It took the first in path order,
  which in a path of several subpaths was the unselected end.

Docs: docs/mcp.md, docs/target-app-parity.md, the `path.split` params
text.
2026-11-10 15:04:50 -08:00
dotCooCoo bf09404c33 Type: character formats, Ruby and Move Type Path Handle reject a param of the wrong type
`text.setStyle` read `font`, `style` and `justify` with `str_param`,
`size` with `len_param` and `tracking` with `num_param`, so a value of
the wrong type was skipped: `{"size": 30, "style": ["Bold"]}` set the
size and ignored the style, and `{"font": "Inter", "size": [12]}` set
the font and ignored the size. `features` dropped an entry that was not
a string and applied the rest. `text.setRangeStyle` skipped a `font` or
`style` that was not a string, `type.ruby` skipped such a `text`,
`position` or `align`, and `text.setPathHandle` moved the out handle
for any `which` other than "in".

Each of these now returns an error that names the param, before the
command edits the document. Omitting a param or giving null keeps its
default. `size` and `leading` still take a length with a unit
("12 pt", "5 mm"), and `tracking` still takes a number written as a
string ("20").

- `text.setStyle`: `font`, `style`, `justify` (`text_param`), `size`
  and `tracking` (checked reads in `typecmd.rs`), `features`.
- `text.setRangeStyle`: `font`, `style`, `features`.
- `type.ruby`: `text`, `position`, `align`.
- `text.setPathHandle`: `which` must be "in" or "out".

Docs: docs/mcp.md.
2026-11-10 14:56:13 -08:00
BFlat 667237ab47 Merge pull request #6473 from dotCooCoo/fix/opacity-mask-param-types
Transparency: opacity mask commands reject a param of the wrong type
2026-11-11 05:55:33 +07:00
BFlat 811bce73ae Merge pull request #6468 from dotCooCoo/fix/compound-member-copy-style
Edit: a copied compound member takes the compound's graphic style link
2026-11-11 05:33:49 +07:00
BFlat 3c55b60e52 Merge pull request #6467 from dotCooCoo/fix/pathops-param-types
Path commands: Offset Path, Simplify, Split Into Grid, Clean Up, Trap and Contour reject a param of the wrong type
2026-11-11 05:25:49 +07:00
dotCooCoo 68b9f6305c Transparency: opacity mask commands reject a param of the wrong type
transparency.makeOpacityMask read `clip` and `invert` with bool_or,
transparency.setOpacityMask read `clip`, `invert`, `disabled` and
`linked` with as_bool, and the New Opacity Masks and page toggles read
`value` with bool_or, so a value of the wrong type was dropped:
"clip": "false" made a clipping mask, "invert": "yes" next to another
flag was ignored, and "value": "on" toggled the page knockout group.
They are now read with switch_or, and such a value is an error that
names the param, before anything changes. Omitting a param or giving
null keeps its default.
2026-11-10 14:14:31 -08:00
BFlat 349385fd56 Merge pull request #6418 from dotCooCoo/fix/3d-more-object-kinds
3D: effects build from clipping groups, blends and symbol instances
2026-11-11 05:13:37 +07:00
BFlat 2f117ba967 Merge pull request #6466 from dotCooCoo/fix/expand-param-types
Expand: object.expand rejects a param of the wrong type
2026-11-11 05:13:27 +07:00
BFlat 92b61461f9 Merge pull request #6457 from slafebre/fix/6335-remember-color-settings
Fix #6335: Color Settings are remembered across restarts
2026-11-11 04:51:27 +07:00
BFlat 3cb6f44c9b Merge pull request #6464 from dotCooCoo/fix/issue-6382
Fix #6382: PDF export says how a page longer than 200 inches is written
2026-11-11 04:46:38 +07:00
BFlat 54092a0a01 Merge pull request #6462 from dotCooCoo/feat/plugin-call-commands
Plug-ins: an API level 2 plug-in calls the commands its manifest lists
2026-11-11 04:37:37 +07:00
dotCooCoo bf3e6b12ed Path commands: Offset Path, Simplify, Split Into Grid, Clean Up, Trap and Contour reject a param of the wrong type
object.path.offsetPath, object.path.simplify, object.path.splitIntoGrid,
object.path.cleanUp, object.pathfinder.trap and addon.contour.set read
their params with helpers that fell back to the default for a value of
the wrong type: "offset": true offset by 10 pt, "joins": 3 gave mitered
joins, "addGuides": "yes" added no guides. Each such value is now an
error naming the param, and the document and the undo history stay as
they were. An omitted or null param keeps its default, and a distance
still takes a string with a unit ("5 mm"). An unknown `joins` name is
refused as well.
2026-11-10 13:31:54 -08:00
dotCooCoo c92dad3838 Edit: a copied compound member takes the compound's graphic style link
Make Compound Path leaves each member's graphic style id in place. A
member copied or cut on its own took the compound's appearance but kept
that old id, so the clipboard carried an unrelated style and the pasted
path was linked to a style that did not match its look.

The copied member or piece now takes the compound's link while it has
that style's look, and no link otherwise (no style on the compound, or
a gradient pinned to the compound's bounds). It also takes the
compound's knockout setting with the rest of its transparency.
2026-11-10 13:28:28 -08:00