Commit Graph
9138 Commits
Author SHA1 Message Date
BFlat 0a0ff46e9c Merge pull request #6187 from dotCooCoo/fix/generic-cmyk-solver-converges
Color: Generic CMYK separates nearby colors alike when an ink sits at 0 or 100%
2026-11-08 14:41:59 +08:00
BFlat 3fe5314fcc Merge pull request #6186 from dotCooCoo/fix/modal-dialogs-fit-content
Dialogs: New Workspace and New Toolbar fit their contents instead of opening 420 pt tall
2026-11-08 14:41:45 +08:00
BFlat 8c772c8a62 Merge pull request #6184 from dotCooCoo/feat/plugin-progress-cancel-log
Plug-ins: API level 2 lets a plug-in report progress, see Cancel and write log lines
2026-11-08 14:41:30 +08:00
BFlat cd4a23246d Merge pull request #6183 from dotCooCoo/feat/ai-legacy-binding-punctuation
.ai import: legacy binding punctuation opens as Burasagari's Force setting
2026-11-08 14:41:15 +08:00
BFlat 0633fe29de Merge pull request #6182 from dotCooCoo/feat/relink-replace-scopes
Links: Relink can replace all instances, the folder's missing links or each link separately
2026-11-08 14:41:00 +08:00
BFlat 158a1de4f7 Merge pull request #6177 from dotCooCoo/perf/swatch-used-colors
Swatches: Add Used Colors, Select All Unused and new swatch naming use hash lookups for colors and names
2026-11-08 14:40:44 +08:00
BFlat 0dbbfaa743 Merge pull request #6176 from dotCooCoo/feat/plugin-permissions
Plug-ins: manifests list permissions, the user grants them at install, and the host enforces the grant
2026-11-08 14:40:29 +08:00
BFlat 30d0ce15f2 Merge pull request #6173 from dotCooCoo/fix/envelope-type-glyph-bounds
Envelopes: type maps from its glyph outline bounds
2026-11-08 14:40:17 +08:00
BFlat 7acea20e3d Merge pull request #6172 from dotCooCoo/feat/hold-to-use-tool-keys
Shortcuts: holding a tool's single-key shortcut uses the tool until the key is let go
2026-11-08 14:40:01 +08:00
BFlat 21d3253a9c Merge pull request #6163 from dotCooCoo/fix/targets-id-param
Commands: an id that is not an object id is refused by commands that otherwise use the selection
2026-11-08 14:39:51 +08:00
BFlat d70db21882 Merge pull request #6159 from dotCooCoo/fix/refpoint-one-tab-stop
Reference point: the locator is one Tab stop, and the arrow keys move the point
2026-11-08 14:39:36 +08:00
BFlat 735fad865e Merge pull request #6155 from dotCooCoo/feat/js-parser
JavaScript: vectorcraft-js parses ES5 scripts into a syntax tree, checked with cargo xtask test262
2026-11-08 14:39:22 +08:00
dotCooCoo 1ea4eefa80 Color: Generic CMYK separates nearby colors alike when an ink sits at 0 or 100%
The Generic CMYK solver clamps each ink to 0..1 after a step. When an
ink sits at 0 or 1 and the step pushes it further out, the clamp
removes most of the step, and the solve can stop after a step under
1e-6. Where it stopped depended on the input's last float bits. For hex
ed1e79, magenta ranged from 93.2% to 93.9% and yellow from 15.3% to
16.3% over 4,001 f32 values of its red, with 230 jumps over 0.001
between neighboring values.

An ink at 0 or 1 whose descent direction points out of the range now
stays where it is, and the step moves the other inks. The solve stops
when every ink is held. Two new tests step a channel one f32 at a time
and check that every ink stays within 0.001: the red of ed1e79 (cyan
held at 0) and the blue of sRGB (0, 0, 0.25) (cyan and magenta held at
1).

The solver still minimizes plain Lab distance to the gamut-mapped
target. The CMYK values of many RGB colors change; hex 00ff00 now
separates to C=61 M=0 Y=100 K=0 (cyan 0.6235 before, 0.6064 now), and
the swatch test that pins its name is updated.
2026-11-08 13:23:18 +07:00
dotCooCoo b0ef9d610d Dialogs: New Workspace and New Toolbar fit their contents instead of opening 420 pt tall
New Workspace, Manage Workspaces, New Toolbar and Manage Toolbars put
their OK and Cancel row in a right-to-left layout directly in the modal
window. That layout took all the height left in the window's default
420 pt content area and centered the buttons in it, so each dialog
opened 420 pt tall with the buttons floating in an empty band.

The button row is now as wide as the fields above it and as tall as the
buttons, as the shared dialog shell already does (dialogs.rs), so each
window ends just below its buttons.
2026-11-08 13:23:14 +07:00
dotCooCoo 960bc64197 Plug-ins: recording a grant keeps the grants file entries it can't read
A grants.json entry this version can't use (a permission it doesn't know,
an invalid id) is now skipped on its own instead of dropping every grant in
the file, and recording or removing a grant writes it back unchanged,
except an entry for that plug-in. A grants.json that is too large, can't be
read or is not a JSON list is left as it is, and a rewrite that would pass
the 1 MiB read limit is written compactly, or not at all.
2026-11-08 13:21:16 +07:00
dotCooCoo f2cd22e0c3 i18n: French rows keep the no-break space before punctuation 2026-11-08 13:21:16 +07:00
dotCooCoo 0383c9f408 Plug-ins: a run keeps what a failing start function reported
A module's start function runs before vc_run. When it logged or reported progress and then failed, the run's report came back empty. The report now holds what the start function reported.
2026-11-08 13:21:16 +07:00
dotCooCoo b071fb109c Plug-ins: the host import fuzz test reaches vc_log's text checks
The fuzz test for hostile host import arguments drew vc_log's level from every i32, so almost
every call stopped at the level check and never read memory. Levels are now also drawn from -1
to 4. The module docs, a test doc comment and docs/plugins.md now say that with an unknown total
each change of done is passed on.
2026-11-08 13:21:16 +07:00
dotCooCoo a5867c8077 Plug-ins: a new progress total at the same tenth of a percent is not passed on
A plug-in that changed its total while its share stayed the same had
every report passed on, up to the 2,000-report limit. Reports with a
known total are now compared by their tenth of a percent alone.
2026-11-08 13:21:16 +07:00
dotCooCoo 1c676fc372 Plug-ins: API level 2 lets a plug-in report progress, see Cancel and write log lines
A plug-in whose manifest gives "api": 2 and lists the new `progress`
permission may import three host functions from module `env`:
vc_progress(done, total), vc_cancelled() -> i32 and
vc_log(level, ptr, len) -> i32. Installing checks every import and
refuses, by name, one the host doesn't offer, one with another type and
one the manifest doesn't allow.

- Progress is clamped and passed on once per tenth of a percent (past
  2,000 reports, once per 1,000 changes). A heavy filter's progress
  window and the export's progress window show it as a percentage bar,
  and their Cancel makes vc_cancelled return 1. A live effect's progress
  is not shown.
- A run cancelled before it returns ends as cancelled, and one that
  returns after its time budget ran out ends at that budget, so what a
  plug-in returns after it sees vc_cancelled changes nothing. This also
  applies to a plug-in of API level 1 that finishes after a Cancel or
  past its time budget.
- Log text must lie in the module's memory and be UTF-8. Lines go to the
  app's log as "<id>: <text>" with hidden characters escaped, at most
  1,024 characters a line, 200 lines and 32 KiB a run, and the host reads
  at most 1 MiB of log text a run.
- plugin.list and plugin.info give the manifest's `api`; the install
  prompt names the new permission in plain words.

Docs: docs/plugins.md, docs/mcp.md, docs/target-app-parity.md.
2026-11-08 13:21:15 +07:00
dotCooCoo c8701a18cd .ai import: legacy binding punctuation opens as Burasagari's Force setting
The legacy reader skipped `bindrepeat Tg` (section 15.3.4 of the version 7
file format specification). It now reads the binding punctuation bit:
1 and 3 set the paragraph's Burasagari to Force, and 0 and 2 set it to
none, which is VectorCraft's own mapping. The value is modal, lasts into
the next text object, and an operand other than 0 to 3 leaves it as it
was. The repeat character, `TG`, `Xu` and `Tu` are still skipped.

docs/mcp.md and the `ParaStyle::burasagari` doc comment name the legacy
`.ai` case as the exception to imported text keeping `none`.
2026-11-08 13:21:08 +07:00
dotCooCoo b7203714be Links: Relink can replace all instances, the folder's missing links or each link separately
The Links panel's Relink... button and menu item open a Relink dialog
(ui.relinkDialog) with a Replace menu. Selected Instances relinks the
selected images and placed documents, as Relink did before. All
Instances also relinks every other object in the layers linked to the
same file. Missing Links from This Folder relinks the selection to the
chosen file, then every other missing link to the file of its name in
that file's folder, and the status bar names the files not there. Each
Link Separately asks for a file for each selected link in turn, one OK
each, and relinks them all at the last one: the dialog has one row per
linked file, and each selected embedded image gets a row of its own.
Every choice is one undo step.

links.relink takes scope: selected (default), all, missingInFolder
(with path) or each (with paths: [{ids, path}] in place of ids, path
and folder). A scope or paths that doesn't fit is an error and relinks
nothing. With copy, every file is read and checked before any is
copied, copies made before a failed copy are removed, and
missingInFolder looks for missing images only.

Docs: params text, docs/mcp.md, docs/control-protocol.md,
docs/target-app-parity.md. Catalog rows for the new labels and messages
in every catalog.
2026-11-08 13:21:01 +07:00
dotCooCoo 7b8185ed33 Swatches: global Add Used Colors keeps Registration links
A paint linked to the built-in Registration swatch is no longer relinked
to a new global swatch of the same color, so it keeps printing on every
plate. The same rule applies to New Color Group from artwork with
global swatches.
2026-11-08 13:20:41 +07:00
dotCooCoo cd95a8a6d1 Swatches: linked paints find their swatch in one name map
Add Used Colors, New Color Group from artwork and Select All Unused
looked up each linked paint's swatch by searching every swatch. They now
build one map of swatch names first, with the built-in Registration
swatch, and the first swatch of each name as before.

New tests and test cases: New Color Group from artwork with repeated,
near-equal and -0.0 colors, a Registration link and names that need
numbering, and New Swatch and Edit Color Group with near-equal and
-0.0 colors; each checks the names, their order and one undo step.
2026-11-08 13:20:41 +07:00
dotCooCoo 346f80dcc7 Swatches: New Swatch, New Color Group and Edit Color Group name swatches from one set of taken names
New Swatch with several colors, New Color Group and Edit Color Group's
extra colors searched every swatch and group for each candidate name
of each new swatch. They now build the set of taken names once
(FreeSwatchNames, with take_new for New Swatch's default names and
insert for the group name and the group's own swatches) and keep it up
to date as they add swatches. The names they give are unchanged.
2026-11-08 13:20:41 +07:00
dotCooCoo da5f6e0db9 Swatches: Select All Unused looks gradients up by their whole definition
swatch.unused grouped the gradients in use by their stop colors and
compared each gradient swatch with every gradient in its group, so
gradients that share stop colors but differ in offsets, midpoints,
opacity, kind, method, dither or links still took quadratic time. Each
gradient now has a key that is equal exactly when the gradients are
equal: kind, method, dither, and each stop's offset, color, opacity,
midpoint, link, tint and process plates. The gradients in use go into
one hash set, and each gradient swatch is looked up in it.
2026-11-08 13:20:41 +07:00
dotCooCoo 39f36dc4ba Swatches: Add Used Colors and Select All Unused look colors up in hash sets
swatch.addUsedColors and swatch.unused searched lists inside loops over
colors and swatches, checking each color against every color or swatch
before it. They now keep the same ordered lists and use hash sets and
maps for membership and lookup. A color's key is its model and the bits
of its components with -0.0 read as 0.0, so the key agrees with `==`; a
color with a NaN component matches nothing, as before.

Add Used Colors names its new swatches through
Document::free_swatch_names, which collects the taken swatch and color
group names once and gives the same names free_swatch_name gives when the
swatches are added one at a time. The added swatches, their names, order
and links, the undo step, and the swatches Select All Unused lists are
unchanged.
2026-11-08 13:20:41 +07:00
dotCooCoo 874ae85fab Plug-ins: manifests list permissions, the user grants them at install, and the host enforces the grant
A manifest may list `permissions` (doc.read, doc.write, state,
effect.render, export.write) and an `api` level. doc.read, doc.write
and state follow the capability model proposed in
storytold/photocraft#662; the other names are VectorCraft's own. An
unknown, reserved, duplicate or wrong-kind name is refused by name. A
manifest without the key gets the permissions its kind implies and
installs as before.

- Install Plug-in... and File > Open show the name, id, version, file
  SHA-256 and each permission in plain words, with control and
  bidirectional characters escaped and each string cut to 64
  characters. OK installs the module only if it still has the SHA-256
  shown; Cancel installs nothing. The heavy-budget prompt and the
  status line show the escaped name too, and OK keeps the `replace` it
  was given.
- `plugin.install` needs `grant` listing every permission, and
  `plugin.inspect` reports `needsApproval`. `hash` pins the module:
  one with another SHA-256 is refused. The grant is kept with the
  module's hash in the store's `grants.json`, and only that store's
  grants count while it is attached; the same set does not ask again,
  more permissions do, and `plugin.remove` forgets it.
- The Additional Plug-ins Folder and `plugin.reload` install only
  plug-ins the attached store has granted and report the others as
  needing approval. At startup the store skips an ungranted plug-in.
- A filter run without doc.read is given no objects; without state it
  neither reads nor writes `documentData`; without doc.write an output
  that changes the document is refused and the document is unchanged.

Docs: docs/plugins.md, docs/mcp.md, docs/target-app-parity.md.
2026-11-08 13:20:32 +07:00
dotCooCoo c94aec3fff Envelopes: type maps from its glyph outline bounds
An envelope mapped type from its laid-out box: ascent to descent for
point type, the frame for area type. The distorted glyphs then filled
less of the envelope than other art does, with room above the capitals
and below the baseline.

The envelope now maps type from the bounds of its glyph outlines,
underlines and strikethroughs when the type is on its own, in groups
and layers, or used as a clipping path. Inline graphics count by their
boxes. Rotated type and type in perspective map from their outlines as
they are drawn. Make with Mesh,
Make with Top Object and Reset with Mesh without Maintain Shape use
those bounds. Type that draws nothing keeps its laid-out box, and so
does type inside any other object, such as a blend, a repeat or a
symbol instance. Shrink Text to Fit type is measured as it is laid out
on the page.

vectorcraft-text's envelope_ink measures the type, and
register_envelope_ink registers it with the document crate. The engine
session, Renderer::new, live evaluation in vectorcraft-effects and the
.ai reader register it. The document crate keeps the measured boxes per
thread, by object and by a hash of the content, and measures again when
the type, the fonts, the hyphenation exceptions or the transform's
scale or rotation change.

This is VectorCraft's own rule. Envelopes of type made in VectorCraft
and in saved files map from the glyph outline bounds instead of the
type's laid-out box, so they redraw differently.
2026-11-08 13:20:19 +07:00
dotCooCoo 97c31e076f Shortcuts: holding a tool's single-key shortcut uses the tool until the key is let go
Holding a single-key tool shortcut becomes temporary: releasing it
returns to the previous tool (a quick tap still switches for good).
With the Pen chosen, holding S chooses Scale; letting go of S after
0.25 s or more, or after Scale was used while S was down, chooses the
Pen again. A press shorter than 0.25 s, with nothing done in between,
keeps Scale chosen as before. The 0.25 s threshold is VectorCraft's own
value. A Cmd/Ctrl click, which the selection tool takes, does not count
as using Scale. A drag of the Hand, Zoom or Rotate View tool chosen
this way counts as using it.

A key let go during a drag, with the pointer button held or with a
dialog open, goes back once that ends. A key let go while the tool
waits for its next click (the Dimension tool after its first point)
goes back once the tool finishes or is canceled. With S held from the
Pen and then V held, letting go of V returns to Scale, and letting go
of S then returns to the Pen. Choosing a tool on the toolbar or from a
menu while the key is down, the held tool included, ends the hold and
keeps that tool, and so does the window losing the keyboard.
While the preference is on, the key's repeats do not choose the tool
again.

The new General preference Hold a Tool Shortcut to Use the Tool
Temporarily (`holdToolKeys`, on by default) turns the behavior off.
With it off, every press and every repeat chooses the tool, as before.
Its label has a row in every catalog.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-08 13:20:11 +07:00
dotCooCoo c6cb5d9de0 Commands: an id that is not an object id is refused by commands that otherwise use the selection
Commands that read `id` and otherwise act on the selection, the highlighted
layer row or a new object read it with a lenient helper, so `{"id": "abc"}`,
`true` or `{}` acted on the selection instead. They now read it with
`object_param`, which refuses any value that is not a non-negative integer
with "`id` must be a whole number" under the command's name and changes
nothing; an omitted or null `id` keeps the default.

Commands: object.isolate, layer.locate, layer.releaseToLayers,
graphicStyle.new and redefine, charStyle and paraStyle new, apply and
redefine, image.info, links.info, links.goTo, links.unembed, shaper.release,
shaper.expand (whose errors now carry their own name),
transparency.editOpacityMask, transparency.viewOpacityMask, symbol.edit, the
object.blend.spine commands, path.curvature and the type commands that read
type ranges.
2026-11-08 13:17:30 +07:00
dotCooCoo 864e606505 Commands: an id that is not an object id is refused instead of acting on the selection
targets() read `id` as an object only when it was a number. Any other
value ("abc", "5", true, {}, [1]) fell through to the selection, so a
command such as css.export, whose `id` the shared check does not read,
acted on the selected objects instead of failing. targets() now reads
`ids` and `id` through named_ids(): a value that is not a non-negative
integer is an error, and an omitted or null `id` still means the
selection. A numeric string is refused, as it already is in `ids`.

effect.apply and appearance.addEffect accept the effect as `id` when
`effect` is left out; they now read their targets with that `id` set to
null, so the effect still applies to the selection as before.
2026-11-08 13:17:30 +07:00
dotCooCoo 31dd69ff4e Reference point: the locator is one Tab stop, and the arrow keys move the point
Each of the locator's nine squares was a Tab stop of its own, so Tab from
the field before it in the Transform panel, the Properties panel, the
Control bar and the dialogs that show it stopped up to nine times before
it reached the next field. The locator is now one Tab stop. While it has
the focus it shows an outline, and the arrow keys move the reference
point one square, stopping at the edges. A click on a square works as
before.

Screen readers get a radio group holding the nine squares as radio
buttons, with the current point as its value. The group is named after
the label next to the locator: Reference Point, or Pivot, Alignment,
Registration or Placement where the locator sets one of those.
2026-11-08 13:17:20 +07:00
dotCooCoo 5a3de875fa JavaScript: identifiers follow the Unicode categories of section 7.6, and label and parameter checks are linear
is_id_start accepts the letter categories (Lu, Ll, Lt, Lm, Lo, Nl) and
is_id_part adds Mn, Mc, Nd and Pc, read with unicode-general-category.
A superscript digit (No) inside an identifier and a mark such as U+09BE
at its start are now SyntaxErrors, and identifiers with marks outside
the old list, such as the Bengali and Tamil viramas, now parse.

Labels in scope are kept in a map, and duplicate labels in a chain and
duplicate parameters of a strict function are found with a hash set, so
long chains, many labeled statements and long parameter lists are
checked in linear time.
2026-11-08 13:17:13 +07:00
dotCooCoo fe92583e13 JavaScript: a for-in variable initializer is a SyntaxError in strict mode code
Current test262 (ECMA-262 2017 Annex B.3.6) accepts `for (var x = 1 in o)` only outside strict
mode code. The parser now rejects the initializer in strict code, with the error at `in`.
2026-11-08 13:17:13 +07:00
dotCooCoo e592e8277b JavaScript: parser for ES5 scripts, with cargo xtask test262
New crate vectorcraft-js reads ECMAScript 5.1 source into a syntax tree
with source positions: a UTF-16 lexer (line and column tracking, automatic
semicolon insertion, the regexp and division goals), preprocessor lines
read as directive tokens, a recursive-descent parser to an arena tree, and
the ES5 early errors, strict mode included. Nesting deeper than
Options::max_depth (200 by default) gives a SyntaxError with its line and
column rather than overflowing the stack. Nothing runs scripts yet.

cargo xtask test262 downloads test262 at a pinned commit, checks its
SHA-256, unpacks it into the gitignored corpus/ folder and runs the ES5
syntax tests. A differential test in the engine compares the lexer with
scriptcheck on line counts, directives and unterminated literals.
2026-11-08 13:17:13 +07:00
BFlat b72dc56660 Merge pull request #6153 from dotCooCoo/feat/three-d-panel
3D and Materials: Window › 3D and Materials edits the selection's 3D effect in place
2026-11-08 14:15:56 +08:00
BFlat e77c410f71 Merge pull request #6151 from dotCooCoo/fix/tab-one-press
Fields: Tab moves from a field to the next past its spinner and preset chevron
2026-11-08 14:15:40 +08:00
BFlat d573b25d68 Merge pull request #6150 from dotCooCoo/feat/ai-editing-data-s6b
.ai save: the editing copy keeps live effects
2026-11-08 14:15:30 +08:00
BFlat a3c3164958 Merge pull request #6142 from dotCooCoo/fix/pdfimport-params
PDF import: cropTo, password, colorMode and textAs reject a value of the wrong type
2026-11-08 14:15:15 +08:00
BFlat 623164ad5b Merge pull request #6141 from dotCooCoo/feat/text-styles-load-from-document
Text styles: load character and paragraph styles from another document
2026-11-08 14:14:59 +08:00
BFlat a2a3c06f3c Merge pull request #6133 from dotCooCoo/fix/ai-warped-type-compare
.ai import: warped type that draws differently from the file opens as its drawn art
2026-11-08 14:14:43 +08:00
BFlat 8ae6f15903 Merge pull request #6130 from dotCooCoo/feat/smooth-color-step-count
Blend: Smooth Color counts a step per level of color change
2026-11-08 14:14:26 +08:00
BFlat 4e1d1ab8c6 Merge pull request #6129 from dotCooCoo/fix/colormgmt-params
Color management: commands reject a param of the wrong type
2026-11-08 14:14:10 +08:00
BFlat 8dab326d41 Merge pull request #6127 from dotCooCoo/fix/flatten-region-limit-early
Flattening: stop building a group's regions at the first one past the region limit
2026-11-08 14:13:55 +08:00
BFlat 1a28f998cc Merge pull request #6126 from dotCooCoo/fix/issue-6110
Fix #6110: pattern brush corner tiles line up with the side tiles
2026-11-08 14:13:44 +08:00
BFlat d7acf687e9 Merge pull request #6124 from dotCooCoo/fix/envelope-params
Envelopes: commands reject a style, orientation or preserveShape of the wrong type
2026-11-08 14:13:33 +08:00
BFlat 2c442f642c Merge pull request #6122 from dotCooCoo/feat/linesweeper-craft-libs
Build: linesweeper from craft-libs' vendored copy at 481f66d
2026-11-08 14:13:15 +08:00
BFlat 45d31fc197 Merge pull request #6120 from dotCooCoo/fix/relative-paths-nested-links
Links: links inside symbols, patterns and masks follow a moved folder and are updated like links in the layers
2026-11-08 14:13:00 +08:00
BFlat 9e8bfd87f9 Merge pull request #6119 from dotCooCoo/feat/trace-preset-buttons
Image Trace: buttons above the Preset menu choose the Auto Color, High Color, Low Color, Grayscale, Black and White and Outline presets
2026-11-08 14:12:45 +08:00