Commit Graph
9191 Commits
Author SHA1 Message Date
dotCooCoo 9f528da663 Merge main into feat/plugin-importers 2026-11-08 09:15:04 -08:00
BFlat 8cd371e1fc Merge pull request #3995 from dotCooCoo/feat/document-compare-image
Document: document.compare measures how far an artboard's render is from an image
2026-11-09 01:14:08 +08:00
dotCooCoo 65659a5dd0 Merge main into feat/plugin-importers 2026-11-08 09:14:02 -08:00
BFlat 90506ff84e Merge pull request #3946 from dotCooCoo/feat/flat-logo-named-colors-only
Image Trace: Flat Logo can trace only the named colors, without a white of its own
2026-11-09 01:13:52 +08:00
BFlat 38a019a47e Merge pull request #2642 from dotCooCoo/fix/one-text-variable-per-object
Variables: a second text variable on one type object is refused
2026-11-09 01:13:32 +08:00
dotCooCoo 3d80b6a133 Plug-ins: the kind-mismatch error reads correctly for importers and exporters, and a failed import leaves the open document alone
A plug-in of the wrong kind now gets "is a plug-in of kind `exporter`, not an importer", with rows in every complete-message catalog. The importer failure test also covers an open, edited document. docs/plugins.md states that Open Recent does not remember Open With's importer or parameters.
2026-11-08 09:13:30 -08:00
dotCooCoo 80770bb5fe Plug-ins: an install changes the revision with the plug-in set locked
A cache keyed by registry::revision could read the new revision before
the plug-in was in the set and keep the old list under it, so File > Open
and document.formats left out a newly installed importer until the next
install or removal. The revision now changes while install holds the
write lock, and a reader that sees it waits for the set.
2026-11-08 08:55:08 -08:00
bflatastic 743b8031d3 Document: the compare test expects main's wording for one missing linked image 2026-11-08 23:50:26 +07:00
dotCooCoo 8888128822 Image Trace: Flat Logo can trace only the named colors, without a white of its own
Squashed from #3946 onto main.
2026-11-08 23:50:25 +07:00
dotCooCoo 7c813e7171 Document: document.compare measures how far an artboard's render is from an image
Squashed from #3995 onto main.
2026-11-08 23:50:25 +07:00
dotCooCoo 4b4a97ce1e Variables: a second text variable on one type object is refused
Squashed from #2642 onto main.
2026-11-08 23:50:24 +07:00
dotCooCoo d485558f44 Plug-ins: Export As leaves out importers' read-only formats
Export As listed every plug-in format, so an importer appeared there and
picking it ended in an error that its files can be opened but not written.
The list now takes only plug-in formats that write. document.formats still
lists importers.

The importer parameters test used the same plug-in id as an exporter test
in the same process. It has an id of its own.
2026-11-08 08:33:34 -08:00
dotCooCoo f3d4233394 Merge main into feat/plugin-importers 2026-11-08 08:15:31 -08:00
dotCooCoo 2772a44efd Plug-ins: Open With runs an importer off the UI thread, and Cancel stops it
File › Open With reads a picked file on the thread File › Open uses, with the importer and its parameters, so a slow importer does not freeze the window. A call that names path still gets document.open's result. Cancel in the progress window now raises a cancel token on that thread, which stops a running importer between two slices of its budget.
2026-11-08 08:09:23 -08:00
FredApps 65adb7b211 Merge pull request #2480 from vangel76/fix/2414-svg-classes
SVG: keep CSS classes of imported elements on export (#2414)
2026-11-08 17:01:22 +01:00
FredApps ef4740f6f0 Merge pull request #5284 from vangel76/perf/web-wasm-simd128-2786
Fix #2786: the web build uses WebAssembly SIMD, so the canvas renders about 2.5x faster
2026-11-08 16:54:58 +01:00
dotCooCoo edd6acd9c0 Plug-ins: File › Open lists only the importer extensions it opens with an importer
An importer may declare an extension that File › Open handles another
way: a plug-in, shortcut set, script, preset, library or swatch library
such as .ase, or one a built-in format reads. "All readable files" and
the importer's own filter no longer list those extensions, and an
importer with none left gets no filter. Open With still opens them.
2026-11-08 07:31:40 -08:00
dotCooCoo d65207dc62 Plug-ins: only File › Open lists importers' files, and imported art at the canvas edge keeps an editable artboard
New from Template, Load Character/Paragraph/All Styles, Recolor's Other
Library… and the library panels' Other Library… read files with the
built-in readers, so their open dialogs now use builtin_open_filters(),
which leaves out the extensions only importer plug-ins read. File › Open
keeps open_filters() with the importers' files.

The artboard of a document an importer opens is now kept within
±4,000,000 pt. Art at the edge (a zero-width path at x = 4,000,000, or a
stroke past the edge) gave an artboard beyond it, and every later edit
of the document was rejected.
2026-11-08 05:35:23 -08:00
dotCooCoo 58f5789e9a Plug-ins: Open With passes an importer its parameters and adds the file to Open Recent
File › Open With asks for the parameters of an importer that declares
any, at their defaults, once the file is picked (dialog `pluginImport`),
and opens the file with the values set. `file.openWith`, `app.open` and
MCP's `open_file` take `params` and pass them to the importer;
`app.open` without them runs the importer on their defaults, with no
dialog. A file opened with Open With is added to Open Recent, as File ›
Open adds the files it opens.
2026-11-08 05:07:08 -08:00
dotCooCoo 36eec28617 MCP: open_file takes an importer plug-in to open the file with
open_file passes `importer` (an importer plug-in's format id,
plugin.<id>) on to app.open. Headless, it becomes document.open's
`importer`; in the app, app.open with `importer` runs file.openWith, so
the file opens with that importer as File > Open With does, also when a
built-in format reads its extension.
2026-11-08 04:40:23 -08:00
dotCooCoo a3c37ea1f4 Merge main into feat/plugin-importers 2026-11-07 23:49:11 -08:00
dotCooCoo fd94ebe86c Plug-ins: importer plug-ins open file types VectorCraft does not read
An importer plug-in (kind "importer", permission import.read) lists 1 to 8
file extensions and turns a file's bytes into objects, which open as a new
document whose artboard is their bounds. File › Open lists its extensions,
File › Open With (file.openWith) opens a file with a chosen importer, and
document.open takes importer and params. A built-in format that reads the
file by its content or extension always opens it; among importers listing
the same extension, the one whose id sorts first opens it. An importer reads
at most 128 MiB, and Place still reads only the built-in formats.

Docs: docs/plugins.md, docs/mcp.md, docs/gaps.md, docs/target-app-parity.md.
2026-11-07 23:05:09 -08:00
BFlat 0a702cc0a5 Merge pull request #6190 from dotCooCoo/fix/plugin-bulk-memory-fuel
Plug-ins: a run keeps the fuel left in a slice at a bulk memory instruction
2026-11-08 14:42:28 +08:00
BFlat cc8770a57d Merge pull request #6188 from dotCooCoo/fix/ai-scatter-tints-brush-frame
.ai import: Scatter brushes in Tints open in Tints, and brush art drops its frame
2026-11-08 14:42:14 +08:00
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 9ebf258b7c Docs: list the bulk memory fuel line beside the exporter plug-ins entry 2026-11-08 13:23:35 +07:00
dotCooCoo 1bcd19e6b6 Plug-ins: a run keeps the fuel left in a slice at a bulk memory instruction
wasmi pauses a run before an instruction the store's fuel can't pay for
and charges nothing for it. A memory.fill or memory.copy is charged for
its whole length, so a pause can leave up to that cost in the store.
Plugin::call replaced the store's fuel with the next slice and compared
the instruction's cost with the rest of the budget alone, so a run whose
budget covered its work could end with Limit("instruction budget").

Each pause now tops the store up to the slice size, keeping what it
held, and ends the run only when the store's fuel and the rest of the
budget together can't pay for the next instruction.
2026-11-08 13:23:34 +07:00
dotCooCoo c85b286994 .ai import: Scatter brushes in Tints open in Tints, and brush art drops its frame
A Scatter brush in a .ai or EPS file's editing data always came into
the Brushes panel keeping its art's colors. A Scatter brush whose last
15 numbers start with 1 now opens in Tints, and one whose numbers start
with 0 keeps its art's colors. Other values are not read and keep the
art's colors. The field before the art's name does not decide it: an
object whose first field is 1 is read by its 15 numbers, and a Scatter
object with 8 numbers is still left out.

A brush's art usually comes in a group after its frame, an unpainted
rectangle around the art. The reader kept that rectangle as part of
the brush's art. It now leaves out a frame at the art's bounds (within
1/1000 pt): an unnamed, unpainted rectangle with its four corners in
order, closed, or open with its first corner again at the end, and no
settings of its own (shown, unlocked, at full opacity in Normal, with
no effects, note or other attributes). A wider frame stays in the art,
and so do a path that crosses the corners, an open path along three
sides and a rectangle with settings of its own. A group left with one
object becomes that object, unless it has a state or look of its own
(hidden, locked, isolated, knockout, an opacity, a blend mode, a name,
a mask, a note or another setting, or an appearance); then it stays a
group without the frame. The rule applies to every brush art section.

Existing files with such brushes now open with Tints and without the
extra frame path in the brush's art. With an exact frame, the pieces
that a Scatter or Art brush draws along a path stay where they were.

The Scatter, Art and Pattern brush colorization options, Tints among
them, are described on pages 281-282 of
https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf#page=281

Tests cover Scatter brushes whose numbers start with 1 and 0, with a
first field of 1 and 2, and an 8-number object. They cover frames kept
or left out (exact, wider, open, filled, named, crossing the corners,
along three sides, with a note, Show Center or hidden), a group's own
state and note kept, and the pieces a Scatter and an Art brush draw
along a path with and without the frame. The import fuzz test reads
brush art after frames of any corners and paint.
2026-11-08 13:23:23 +07: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