Plug-ins: unlinking a gradient stop's spot swatch also clears its plates

A filter or importer that changes a gradient paint unlinks each stop from
its swatch and resets its tint, but left the stop's process plates. The
PDF and EPS writers read plates only with a spot swatch, so the stop kept
a plates array with no ink, saved with the document and compared in later
edits. Unlinking now clears the plates too, in fills and strokes; a paint
returned unchanged keeps its link, tint and plates.

The plate clamping test now checks the decoded output, before admission.
This commit is contained in:
dotCooCoo
2026-11-13 19:56:38 -08:00
parent 1e4e3534f5
commit 7c40cd2df2
4 changed files with 67 additions and 12 deletions
+2 -2
View File
@@ -617,7 +617,7 @@ An **importer** gets the bytes of the file being opened, as they are (not JSON),
`paint`, `width` in points, `join` (`"miter"`, `"round"` or `"bevel"`), `cap` (`"butt"`, `"round"` or `"square"`)
and `miterLimit`. Paints use the native file format's form: `{"type": "solid", "color", "swatch"?, "tint"?}`,
`{"type": "gradient", "gradient": {"kind": "Linear"|"Radial"|"Freeform", "stops": [{offset, color, opacity,
midpoint}], "dither"?, "method"?: "Perceptual"|"HueClockwise"|"HueCounterclockwise"}, "geom"?, "angle", "freeform"?}`, `{"type": "pattern", "pattern",
midpoint, swatch?, tint?, plates?}], "dither"?, "method"?: "Perceptual"|"HueClockwise"|"HueCounterclockwise"}, "geom"?, "angle", "freeform"?}`, `{"type": "pattern", "pattern",
"xf"?}`. Colours are `{"model": "rgb", r, g, b}`, `{"model": "cmyk", c, m, y, k}`, `{"model": "gray", k}` (ink: 0
white, 1 black) or `{"model": "lab", l, a, b}`; components 0–1 except Lab's.
- `effects` (filters): the object's own effects in order, as its effect records: `id` (as `effect.list` lists it, or
@@ -742,7 +742,7 @@ most 100,000 objects and 4,000,000 anchors, 256 fills, strokes or gradient stops
clamped into range, stroke weights to 0–1,000 pt and miter limits to 1–500; a pattern must exist in the document. A
gradient stop's four process `plates` are clamped to 0–1 as well: a value that is not a number becomes 0, and an
infinite one becomes 0 or 1 by its sign. A paint returned unchanged keeps its swatch link; any other paint is
unlinked. One refused effect record refuses the whole output.
unlinked, and each of its gradient stops then has no swatch, a tint of 1 and no `plates`. One refused effect record refuses the whole output.
A filter's output may also hold `documentData`: any JSON of at most 1 MiB, which replaces what the plug-in keeps with
the document (`null` removes it), in the same undo step as its objects. Settings that belong to the whole document
+1
View File
@@ -1929,6 +1929,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- **Live-effect plug-ins before a fill's or stroke's own 3D effect:** a live effect listed before a fill's or stroke's own Extrude & Bevel, Inflate or Rotate effect is given `_context.item` for that fill or stroke (`{"fill": i}` or `{"stroke": i}`), not `"object"`, in the drawn art, in the 3D meshes and in Expand as Wireframes; a deferred stage there is listed at that fill's or stroke's place. Regressions: `crates/effects/src/tests/solidcontext.rs`. Scores and hours unchanged.
- **Filter plug-ins set effects and stroke styles (G17):** a filter is given each object's effect records and its strokes' `join`, `cap` and `miterLimit`, and may return them; the records are admitted as `effect.apply` admits them (a known effect or installed live-effect plug-in, parameters it takes, one 3D effect that fits, no texture read from a file), and one refused refuses the output. Regressions: `crates/engine/src/tests/plugins.rs`, `crates/plugins/src/objects/tests.rs`. Scores and hours unchanged.
- **Gradient stop plates from filter plug-ins are clamped (G17):** the four process `plates` of a gradient stop in a filter's or importer's output are clamped to 0–1, as its other colors are; a value that is not a number becomes 0, and an infinite one becomes 0 or 1 by its sign. Painted live effects still refuse such a stop. Regressions: `crates/plugins/src/objects/tests.rs`. Scores and hours unchanged.
- **Changed plug-in gradients lose spot plates (G17):** a gradient that a filter or importer changes loses each stop's swatch link, tint and process `plates` together, in fills and strokes, and keeps the stop's color, offset, opacity and midpoint; a gradient returned unchanged keeps all three. Regressions: `crates/plugins/src/objects/tests.rs` (`changed_gradient_fill_and_stroke_drop_spot_plates`, `unchanged_gradient_paint_keeps_spot_plates`). Scores and hours unchanged.
- **Plug-in data kept with the document (G17):** a filter writes JSON of at most 1 MiB with `documentData` (one undo step with its objects; `null` removes it) and reads it back in `_context.documentData`, which no other filter is given. It is the document's foreign data (`unknown.pluginData`, by plug-in id), as the perspective grid is, so the native file keeps it with no section of its own; an exporter is given the whole document, so it can read every plug-in's. Regressions: `crates/engine/src/tests/plugins.rs`, `crates/engine/tests/plugin_export.rs` (the exporter), `crates/doc/src/plugin_data.rs`, `crates/plugins/src/objects/tests.rs`. Scores and hours unchanged.
- **Heavy filter plug-ins (G17):** a filter's manifest may ask for `"budget": "heavy"`: 1 G instructions (20 times a light run's) and 300 s a run, for routing, packing or simulation. Install Plug-in… and File › Open ask the user first, `plugin.install` needs `allowHeavy: true` (`plugin.inspect` tells beforehand), the Additional Plug-ins Folder skips such filters, and the plug-in store keeps the answer. Its dialog has no Preview; the app runs it on a thread of its own with a progress window (Cancel, Continue in Background) and applies the result as `plugin.run` with the same parameters, one undo step, unless the document or the plug-in changed meanwhile. Regressions: `crates/plugins/tests/abi.rs`, `crates/plugins/src/manifest.rs`, `crates/engine/src/tests/plugins.rs`, `crates/ui-egui/src/tests/plugin_runs.rs`. Scores and hours unchanged.
- **Safe Mode for plug-ins (G17):** `vectorcraft --safe-mode` and `vectorcraft-cli --safe-mode <subcommand>` start without plug-ins. When a launch stopped while a plug-in was loading or drawing its first live effect, the next launch names that plug-in, without saying it caused the stop, and offers Disable and Continue, Start Normally or Start in Safe Mode. Help › Safe Mode lists the disabled plug-ins with Enable on Re-launch, which takes effect at the next launch, and the app bar and the window title show Safe Mode. The help page (https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf, pages 65 to 69) describes the same choice between diagnosing and starting normally, a Safe Mode with the problem item off and the rest of the app working, and turning the item back on at the next launch. In Safe Mode no plug-in loads or runs, and a live-effect plug-in draws its object's geometry unchanged while the object keeps the effect, which the Appearance panel marks "(Safe Mode)" with the plug-in's id in its tooltip and `effect.list` marks `skipped`; Expand Appearance refuses such an object. The web build keeps no plug-ins between page loads and has no marker. Each desktop launch with preferences keeps a marker in the `Launch` folder next to them; `vectorcraft-cli` and a desktop launch without preferences keep none, so a stop there leads to no choice at the next launch. Disabled plug-ins are listed in `Plug-ins/disabled.json`. `plugin.safeMode` reports Safe Mode and the disabled plug-ins, and `plugin.setEnabled` changes the list. Regressions: `crates/plugins/src/safe_mode.rs`, `crates/plugins/tests/safe_mode.rs`, `crates/engine/tests/plugin_safe_mode.rs`, `crates/ui-egui/tests/safe_mode.rs`. Scores and hours unchanged.