mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 11:55:23 +00:00
Merge remote-tracking branch 'upstream/main' into fix/ai-type-pattern-paint
# Conflicts: # docs/file-format-parity.md
This commit is contained in:
@@ -111,7 +111,7 @@ the two can still win that race.
|
||||
| `app.export` with `useArtboards` | `{path, useArtboards: true, range? \| artboards?, …}` | Export As: PNG, JPEG and WebP write one file per chosen artboard (default all; SVG does so for a `range`), `<path stem>-<artboard>.<ext>`, and return `{path, files: [path…]}`; a PDF keeps them as pages of `path`. `useArtboards: false` covers the bounds of the visible art. The menu's Export As… is `file.exportAs`: the `exportAs` dialog (`format`, `useArtboards`, `all`, `range`), then a save dialog and the format's options: `pngOptions` / `jpgOptions` / `webpOptions`, `svgOptions`, or `savePdf` for a PDF with Use Artboards |
|
||||
| `ui.dialog.*` on `gradientStop` | `{field: "color" \| "opacity" \| "location", value}` | double-clicking a stop on the Gradient tool's annotator opens its popover (fields `index`, `x`, `y`, `tab`); set `color` (hex), `opacity` or `location` (percentages) and `ui.dialog.confirm` to apply them to the selected stop. `gradient.selectStop {index}` picks the stop the annotator, the panels and Delete/←/→ (`ui.key`) act on |
|
||||
| `engine.execute` `ui.savePdfDialog` | `{path?, preset?, range?, …document.exportPdf options}` | opens the Save PDF dialog (`file.export.pdf` with no params opens it too). Its fields are the `document.exportPdf` options: set `compatibility` with `ui.dialog.set {field: "compatibility", value: "1.5"}`, a whole section with `{field: "compression", value: {…}}`, the artboards with `__allArtboards: false` and `range`, the visible section with `__section` (General, Compression, Marks and Bleeds, Output, Advanced, Security, Summary). `ui.dialog.confirm` writes `path` (else asks for one), returns `{path, bytes, warnings}` and opens the file when `viewAfterSaving` is set |
|
||||
| `file.save` / `file.saveAs` / `file.saveCopy` / `file.saveAsTemplate` (via `engine.execute`) | `{path?, format?, options?, svg?}` | Save writes the document's own file in its own format (a document opened from or saved as SVG/PDF saves as that again; the result's `warnings` say what the format loses). With `path` the file is written at once (Save As or a Copy to an SVG path without SVG options first opens SVG Options, below). Without one (never saved, a converted older file, Save As, a Copy, a Template) the app shows a save panel with one file type per save format (`vectorcraft`, `template`, `pdf`, `svg`, `svgz`, `ai`; for a Template, `template` and `ait`), then the format's options: SVG opens the `svgOptions` dialog (below; returns `{"pending": "svgOptions", "path"}`), PDF the `savePdf` dialog (its `__save` field names the save command); `ui.dialog.confirm` finishes the save. A name typed in the panel without the file type's extension gets it, and a name the panel returns with the extension of a format the panel does not list has it replaced (in a native save, `card.png` and `card.ait` are saved as `card.vectorcraft`, and `notes.v2`, whose extension names no format, is saved as `notes.v2.vectorcraft`); when a file of that name exists, which the panel did not ask about, the save (and every export or other write through a save panel) answers `cancelled` and opens a `replaceFile` dialog (fields `message`, `detail`, `path`): `ui.dialog.confirm` writes that file, `ui.dialog.cancel` writes nothing. On the web a `saveOptions` dialog (fields `path`, `format`) first names the file and picks the format. `file.formatOptions {format?}` reads the options a save would use. `file.revert` opens a `confirm` dialog (`{"pending": "confirm"}`); `ui.dialog.confirm` reloads the saved file in the same tab |
|
||||
| `file.save` / `file.saveAs` / `file.saveCopy` / `file.saveAsTemplate` (via `engine.execute`) | `{path?, format?, options?, svg?}` | Save writes the document's own file in its own format (a document opened from or saved as SVG/PDF saves as that again; the result's `warnings` say what the format loses). With `path` the file is written at once (Save As or a Copy to an SVG path without SVG options first opens SVG Options, below). Without one (never saved, a converted older file, Save As, a Copy, a Template) the app shows a save panel with one file type per save format (`vectorcraft`, `template`, `pdf`, `svg`, `svgz`, `ai`; for a Template, `template` and `ait`), then the format's options: SVG opens the `svgOptions` dialog (below; returns `{"pending": "svgOptions", "path"}`), PDF the `savePdf` dialog (its `__save` field names the save command); `ui.dialog.confirm` finishes the save. A name typed in the panel without the file type's extension gets it, and a name the panel returns with the extension of a format the panel does not list has it replaced (in a native save, `card.png` and `card.ait` are saved as `card.vectorcraft`, and `notes.v2`, whose extension names no format, is saved as `notes.v2.vectorcraft`); when a file of that name exists, which the panel did not ask about, the save (and every export or other write through a save panel) answers `cancelled` and opens a `replaceFile` dialog (fields `message`, `detail`, `path`): `ui.dialog.confirm` writes that file, `ui.dialog.cancel` writes nothing. A save or export that writes other files beside that path (a file per artboard, linked images, image maps) checks them before it writes anything: when any of them exists, it writes nothing and opens a `replaceFiles` dialog (fields `message`, `detail` (each file's name when there are several), `files`, `__command`, `__params`; the reply is `{"pending": "replaceFiles"}`, or comes once a Background Save or Export ends): `ui.dialog.confirm` runs the save or export again and replaces them, `ui.dialog.cancel` writes nothing; a dialog open when a background job asks stays open, and the question follows it. A save does not ask about the files the document's last Save or Save As in this session wrote. On the web a `saveOptions` dialog (fields `path`, `format`) first names the file and picks the format. `file.formatOptions {format?}` reads the options a save would use. `file.revert` opens a `confirm` dialog (`{"pending": "confirm"}`); `ui.dialog.confirm` reloads the saved file in the same tab |
|
||||
| SVG Options (`engine.execute` of `file.export.svg`, `file.saveAs`, `file.saveCopy`) | `{}` / `{path?, svg?: {…}}` | `file.export.svg` without params, and Save As or Save a Copy to an `.svg`/`.svgz` path without SVG options, open the `svgOptions` dialog (`ui.dialog.set` its fields: `styling`, `outlineText`, `images`, `objectIds`, `decimals`, `minify`, `responsive`, `fewerTspans`, `metadata`, `preserveEditing`, `useArtboards`, `allArtboards`, `range`, `showCode`, `profile` (`svg11`/`tiny12`), `encoding` (`utf8`/`utf16`/`latin1`), `embedFonts`; `ui.dialog.confirm` exports or saves and remembers the choices). With `svg: {…}` (the `document.formats` SVG options) they run directly. `app.save {path?, svg?}` takes the same options; a document saved as SVG saves as SVG again with the same options |
|
||||
| `engine.execute` `file.place` | `{paths?}` / `{path \| name+dataBase64, …file.place params}` | with no file, picks files and opens the `place` dialog; with `paths`, opens it for them (fields `files`, `link`, `template`, `replace`; `__replace` says whether Replace applies, `__info` describes each file). `ui.dialog.confirm` places one file centered in the view (or in place of the selected object) and loads the place cursor with several (`file.place.queue`). In the desktop app the dialog reads its files off the UI thread: `__info` is empty until they are read, `ui.dialog.confirm` returns `{background: true, status}`, `ui.inspect` lists the files still being read in `placing`, and the file is placed (or the cursor loaded) once it is read. With a file, it runs `file.place` centered in the view, at once. Files dropped on the window with no document open open. On the web, files dropped on the canvas are placed at the drop point (linked; Shift embeds) and files dropped off it (the tab bar) open, as in Illustrator; desktop drops carry no pointer position, so documents (native, SVG, PDF, `.ai`, EPS, DXF, EMF/WMF) open as tabs of their own and pictures and text are placed in the middle of the view. Swatch libraries and color books (`.vcswatches`, `.gpl`, `.ase`, `.acb`), graphic style libraries (`.vcstyles`), Libraries panel files (`.vclibrary`), presets (`.vcflattener`, `.vcpdfpresets`, `.vcprintpresets`, `.vcperspective`, `.vcexportpresets`), plug-ins (`.wasm`) and shortcut sets (`.kys`, in Import Shortcut Set) open wherever they are dropped, as File › Open opens them. With the cursor loaded (`ui.inspect` → `tool: "place"`, `toolOptions: {count, current, name, names}`), `ui.pointer` clicks and drags place files (in the desktop app each file is read off the UI thread and placed once it is read, and `ui.inspect` lists it in `placing` until then) and `ui.key` ←/→/↑/↓ and Escape cycle and discard them |
|
||||
| `app.export` raster options | `{path?, format: png \| jpg \| webp, ppi?, width?, height?, background?, antiAlias?, interlaced?, quality?}` | `ppi` sets the pixel size (72 = one pixel per point) and is stored in PNG and JPEG files (WebP files store no resolution); `width` or `height` sets the size of each file in pixels instead (`width` wins), and PNG and JPEG files store the resolution that results; `background` is `transparent`, `white`, `black` or `"#rrggbb"`; `antiAlias` is `none`, `art` or `type` (text snapped to pixels); `interlaced` writes an Adam7 PNG. The PNG Options dialog (`pngOptions`; `jpgOptions` / `webpOptions` for the other raster formats) has the same fields: fill them with `ui.dialog.set` and press OK with `ui.dialog.confirm` |
|
||||
|
||||
+6
-1
@@ -1,6 +1,6 @@
|
||||
# Development
|
||||
|
||||
> **Last updated:** 2026-11-06 · **Change:** Hindi catalog and zero/one plural rule; desktop Devanagari fallback and optional craft-fonts UI face; native review pending.
|
||||
> **Last updated:** 2026-11-12 · **Change:** minor (clipboard failure helper follows its Linux/BSD and test callers; previously: Hindi catalog and zero/one plural rule; desktop Devanagari fallback and optional craft-fonts UI face; native review pending).
|
||||
> **Last updated:** 2026-11-01 · **Change:** Linux: Automatic Linux Display Server opens the window through XWayland when a pen is attached on a Wayland session, so the pen draws (#4967); `VECTORCRAFT_NATIVE_WAYLAND=1` keeps Wayland.
|
||||
> **Last updated:** 2026-10-31 · **Change:** files opened while the app runs open in its window on Windows and Linux (#4892); `--new-instance`.
|
||||
> **Last updated:** 2026-10-29 · **Change:** Linux clipboard: the Flatpak on GNOME copies and pastes text through the core Wayland protocol.
|
||||
@@ -351,6 +351,10 @@ parameters. Shipped code therefore never panics. Anything that can fail returns
|
||||
|
||||
### Enforced by lints
|
||||
|
||||
The clipboard timeout failure helper is compiled for Linux/BSD workers and unit tests; production
|
||||
macOS reads synchronously. Keep helper target gates aligned with their callers instead of disabling
|
||||
dead-code warnings across the workspace.
|
||||
|
||||
`Cargo.toml` denies these clippy lints for the whole workspace, and `cargo xtask ci` runs clippy with
|
||||
`-D warnings`. The gate targets the current stable Rust (`rust-version` in `Cargo.toml` is only the
|
||||
minimum that builds the app, and no toolchain is pinned): when a new stable adds lints, they are fixed
|
||||
@@ -508,6 +512,7 @@ Scores and hour estimates are unchanged.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-12 | minor | Compile the clipboard failure helper only for Linux/BSD worker callers and unit tests; keep production macOS free of the unused helper without suppressing workspace dead-code warnings. |
|
||||
| 2026-11-06 | minor | Localisation: Hindi catalog, zero/one plurals, desktop and web font requirements; AI/MT draft, native review pending. |
|
||||
| 2026-10-31 | minor | Files opened while the app runs open in its window on Windows and Linux (#4892); `--new-instance` opts out. |
|
||||
| 2026-10-29 | minor | Linux clipboard: core Wayland protocol text fallback where arboard has neither data-control nor X11 (the Flatpak on GNOME).; its reads wait 2 s at most. |
|
||||
|
||||
@@ -28,7 +28,7 @@ Part of [target-app-parity.md](target-app-parity.md); open work is in [gaps.md](
|
||||
| Rasterize | Rasterize… (as a live effect) | 1/1 | — |
|
||||
| Stylize | Drop Shadow, Feather, Inner Glow, Outer Glow, Round Corners, Scribble | 6/6 | Scribble Options' Settings presets |
|
||||
| SVG Filters | Apply SVG Filter… and the filter list | 1/1 | The submenu lists the document's own filters: VectorCraft ships no default filters. A filter that is the object's last effect is written to SVG as a `<filter>` element; otherwise SVG export draws the object as an image. A filter on a turned or skewed object in an imported SVG is left out with a warning |
|
||||
| Warp | Arc, Arc Lower, Arc Upper, Arch, Bulge, Shell Lower, Shell Upper, Flag, Wave, Fish, Rise, Fisheye, Inflate, Squeeze, Twist | 15/15 | warp point editing in envelopes; Arc is a circular arc in the object's own proportions (#1578) |
|
||||
| Warp | Arc, Arc Lower, Arc Upper, Arch, Bulge, Shell Lower, Shell Upper, Flag, Wave, Fish, Rise, Fisheye, Inflate, Squeeze, Twist | 15/15 | warp point editing in envelopes; each style bends the bounds as one Bézier patch (`version` 1), so Arc's edges are cubic curves close to concentric arcs; warps saved without a version keep the earlier map, where Arc is a circular arc in the object's own proportions (#1578) |
|
||||
|
||||
Document Raster Effects Settings exists. When the 64 million pixel cap on an effect image lowers
|
||||
its resolution, PDF export warns once per object with the requested and the lowest achieved ppi
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File format parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-12 · **Change:** minor (`.ai` and EPS import: type filled or stroked with a pattern swatch keeps its pattern, from the art style its text document paint names, #6856; `.ai` and EPS import, and what every export and `.ai` save draw: type's strokes in an envelope paint at their own weight along its bent outlines, Distort Appearance on or off, #6644; `.ai` and EPS import: centred and right-aligned lines of tracked type are aligned without the tracking after their last character, as the file's page aligns them, #6610; `.ai` and EPS import: an envelope saved in Edit Contents whose mesh lies away from its drawn art by a move opens live on that art, #6570; `.ai` save and open: layer colour presets Yellow, Magenta and Cyan keep their colour, as the `Lb` colour index now follows the file's numbering, #6502; `.ai` save: stroked type keeps its stroke's cap, join and miter limit in the text document, #6472; `.ai` and EPS import, PDF import: tracking and manual kerning in horizontally scaled type take the Horizontal Scale, as the file's page spaces such type; `.ai` and EPS import: stroked type in an envelope maps from its glyphs grown by half its stroke, #3179; `.ai` and EPS import: warps of type open as mesh envelopes on their saved grid, #3179; `.ai` and EPS import: stroked type keeps its stroke's cap, join and miter limit from the text document, #6472; `.ai` save: type in a variable font's named instance names its design-space setting, #6388; `.ai` and EPS import: envelopes of type open live where they draw as their drawn art and the layers with them draw as the page, #3179; `.ai` and EPS import: a note for type in an envelope, #3179; `.ai` and EPS import: the live envelope and warped type check sees white art; PDF import: DeviceN images of one ink or of process plates keep their inks, #6348; `.ai` import: images in a spot ink's Separation space keep their ink, #6350; PDF import: Separation images keep their ink, #6314; PDF editable import preserves authored ActualText once, including empty/nested spans and authored ordering (#6165); cross-line/style layout remains approximate; scores and estimates unchanged; PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; PDF import: image masks open colorized by the color they are painted in, #6313; EPS and `.ai` editing data: a ligature the page names by a private-use glyph matches its text object; EPS import: CIDFonts only named, over a Unicode CMap, set as live type; EPS import: CMap mappings to base-font codes and glyph names; EPS import: CID-keyed fonts in FontSets (CFF) draw their glyphs; EPS import: `kshow` procedures kern live type; EPS import: embedded Type 1 fonts' glyphs and widths; EPS import: CID-keyed fonts draw their glyphs; EPS import: Type 42 fonts' characters and widths from the font they carry, show operators' spacing kept as tracking; EPS import: an image decoder's own buffers stop counting against the memory budget once it is decoded; EPS import: sampled functions interpolate between their samples; `.ai` and EPS type from the text document keeps its tab stops; it keeps auto leading and hyphenation options, and may show more of the file's last lines in an area frame; `.ai` and EPS import: Art brushes in the older form of their objects come into the Brushes panel; EPS import: gradients drawn as abutting strips come in as one gradient, without seams; EPS import: composite fonts draw their Type 42 glyphs; SVG import: filter and mask regions clip, #5876, #5875; SVG import: Gaussian blurs keep `color-interpolation-filters`, #5877; PDF and `.ai` export: objects' URLs as links, #5785; SVG import: `color-interpolation="linearRGB"` gradients, #5878; `.ai` import: paths with a Calligraphic brush open brushed, #372) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-11 · **Change:** minor (`.ai` and EPS import: type filled or stroked with a pattern swatch keeps its pattern, from the art style its text document paint names, #6856; newly imported SVG/SVGZ titles are filtered and bounded without changing restored titles, #3626; `.ai` and EPS import, and what every export and `.ai` save draw: type's strokes in an envelope paint at their own weight along its bent outlines, Distort Appearance on or off, #6644; `.ai` and EPS import: centred and right-aligned lines of tracked type are aligned without the tracking after their last character, as the file's page aligns them, #6610; `.ai` and EPS import: an envelope saved in Edit Contents whose mesh lies away from its drawn art by a move opens live on that art, #6570; `.ai` save and open: layer colour presets Yellow, Magenta and Cyan keep their colour, as the `Lb` colour index now follows the file's numbering, #6502; `.ai` save: stroked type keeps its stroke's cap, join and miter limit in the text document, #6472; `.ai` and EPS import, PDF import: tracking and manual kerning in horizontally scaled type take the Horizontal Scale, as the file's page spaces such type; `.ai` and EPS import: stroked type in an envelope maps from its glyphs grown by half its stroke, #3179; `.ai` and EPS import: warps of type open as mesh envelopes on their saved grid, #3179; `.ai` and EPS import: stroked type keeps its stroke's cap, join and miter limit from the text document, #6472; `.ai` save: type in a variable font's named instance names its design-space setting, #6388; `.ai` and EPS import: envelopes of type open live where they draw as their drawn art and the layers with them draw as the page, #3179; `.ai` and EPS import: a note for type in an envelope, #3179; `.ai` and EPS import: the live envelope and warped type check sees white art; PDF import: DeviceN images of one ink or of process plates keep their inks, #6348; `.ai` import: images in a spot ink's Separation space keep their ink, #6350; PDF import: Separation images keep their ink, #6314; PDF editable import preserves authored ActualText once, including empty/nested spans and authored ordering (#6165); cross-line/style layout remains approximate; scores and estimates unchanged; PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; PDF import: image masks open colorized by the color they are painted in, #6313; EPS and `.ai` editing data: a ligature the page names by a private-use glyph matches its text object; EPS import: CIDFonts only named, over a Unicode CMap, set as live type; EPS import: CMap mappings to base-font codes and glyph names; EPS import: CID-keyed fonts in FontSets (CFF) draw their glyphs; EPS import: `kshow` procedures kern live type; EPS import: embedded Type 1 fonts' glyphs and widths; EPS import: CID-keyed fonts draw their glyphs; EPS import: Type 42 fonts' characters and widths from the font they carry, show operators' spacing kept as tracking; EPS import: an image decoder's own buffers stop counting against the memory budget once it is decoded; EPS import: sampled functions interpolate between their samples; `.ai` and EPS type from the text document keeps its tab stops; it keeps auto leading and hyphenation options, and may show more of the file's last lines in an area frame; `.ai` and EPS import: Art brushes in the older form of their objects come into the Brushes panel; EPS import: gradients drawn as abutting strips come in as one gradient, without seams; EPS import: composite fonts draw their Type 42 glyphs; SVG import: filter and mask regions clip, #5876, #5875; SVG import: Gaussian blurs keep `color-interpolation-filters`, #5877; PDF and `.ai` export: objects' URLs as links, #5785; SVG import: `color-interpolation="linearRGB"` gradients, #5878; `.ai` import: paths with a Calligraphic brush open brushed, #372) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
Every format Illustrator opens, places, saves or exports, against VectorCraft's format table (`FORMATS`,
|
||||
`OPEN_EXTS`, `PLACE_EXTS` in `crates/engine/src/cmd/fileio/formats.rs`). Illustrator's list comes from its public
|
||||
@@ -158,7 +158,8 @@ private editing data. VectorCraft:
|
||||
under them, clipped to where they show, along a smooth outline traced at the image's own resolution; that art stays) or omitted (Transparent Images,
|
||||
`transparentImages: "flatten"|"omit"`). Type is written as editable text objects (point type, area type and type
|
||||
on a path, with their fonts' PostScript names, sizes, colours, alignment, indents, leading, tracking and
|
||||
scaling), which the reading app sets in those fonts; characters outside Windows-1252, vertical type, ruby, inline
|
||||
scaling, and the Standard Vertical Roman Alignment and Tate-chu-yoko marks as `Tv` and `TY`), which the reading
|
||||
app sets in those fonts; characters outside Windows-1252, vertical type, ruby, inline
|
||||
graphics and gradient or pattern fills make that type outlines, and so do rotated characters (Character Rotation,
|
||||
which a version 7 text object can't hold: the outlines keep the rotation, and the type is no longer editable text
|
||||
in the file), and Text: Outlines (`outlineText: true`) writes
|
||||
@@ -344,6 +345,9 @@ Stop opacity on translated artboards remains outside this regression coverage.
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-12 | minor | `.ai` and EPS import: type filled or stroked with a pattern swatch keeps its pattern. In the text document a character's fill or stroke is a colour (key 0) and the name of an art style (key 5). In users' files (used locally, never committed) that style is one paint (`/SimpleStyle`): a colour, the one key 0 already has, or a pattern, `(name) … [matrix] p`, whose key 0 is black. Such type came in black, or stroked in black. It now takes the pattern, its matrix in art space as a path's pattern fill has it (the files' PDF parts tile such type with that matrix), placed in the type's text space so that it stays where the document has it. A pattern the file doesn't define or can't place, and a style of any other paint, keep the colour as before. A `.ai` saved again writes such type as its outlines, as it writes all type with pattern paint, with that warning. On 224 of our own files: envelopes of type that open live go from 154 in 45 files to 157 in 46, and no file loses one; no file changes whether it opens from its layers or its PDF part; renders at 40 ppi change in 10 of the 13 files with patterned type, 8 closer to the file's own page (one by under 0.01 points, in a file whose render differs from its page almost everywhere either way), 1 further by 0.03 points of its pixels (an envelope of type there now opens live in place of its drawn art), 1 not compared (its page renders at another size); notes change in 3 files: one loses its type note, one loses the note for type in an envelope and, now compared with its page, gains its type notes, and one that opens from its PDF part names a smaller difference. Tests `ate::tests::a_run_painted_by_a_patterns_art_style_takes_the_pattern`, `a_runs_pattern_stays_where_the_document_has_it`, `tests_native::type_painted_with_a_pattern_takes_it_from_the_art_style_its_paint_names`. Scores and estimates unchanged. (#6856) |
|
||||
| 2026-11-11 | minor | Newly imported SVG/SVGZ titles use the imported-text rules: generated Untitled names and control-only titles give the filename fallback, other control characters are removed, and titles are limited to 65,536 characters. Native and Preserve Editing titles remain unchanged (#3626). |
|
||||
| 2026-11-11 | minor | Native and Preserve Editing restores keep an existing File Info title exactly, including repeated spaces; imported-text fallback titles alone are normalized (#3626). |
|
||||
| 2026-11-03 | minor | An imported document's own title goes into File Info only (#3626): an SVG's root `<title>`, a PDF's, `.ai`'s or `.ait`'s XMP `dc:title` else its Info `Title` is File Info's title (`metadata.title` in the native format, left out when unset so other documents save unchanged and older versions ignore it; written by exports); the document's name, which the tab, Save As and the default export names use, stays the file's name. |
|
||||
| 2026-11-11 | minor | `.ai` and EPS import, and what the canvas, every export and `.ai` save draw: type's strokes in an envelope (its characters' and those on its own appearance) paint along the bent glyph outlines at their own weight, Distort Appearance on or off, as the drawn art of users' `.ai` files strokes them (used locally, never committed: there the drawn art keeps those strokes as stroked paths at the type's own weights, and its ring is as wide wherever the grid squeezes or stretches the type). With Distort Appearance, VectorCraft bent them with the art, as their filled outlines, so an envelope of stroked type in a squeezed or stretched grid didn't draw as its drawn art and kept it. The same holds in an envelope inside an envelope. Paths' strokes, geometry effects, opacity masks, linear gradients and pattern fills bend as before. On 224 of our own files: envelopes of type that open live go from 135 in 37 files to 154 in 45, and no file loses one; no file changes whether it opens from its layers or its PDF part; renders at 40 ppi change in 31 files, by at most 2.3% of their pixels, 19 closer to the file's own page and 12 further, 11 of those where an envelope of type now opens live in place of its drawn art (the page's own art) and one by 4 pixels. Tests `envelope_distort::type_strokes_paint_at_their_own_weight_in_a_squeezed_envelope`, `a_stroke_on_types_appearance_paints_at_its_own_weight_in_a_squeezed_envelope`, `a_paths_stroke_still_bends_in_a_squeezed_envelope`, `tests_native::an_envelope_of_stroked_type_whose_drawn_art_strokes_the_bent_outlines_at_the_weight_opens_live` (#6644) |
|
||||
| 2026-11-11 | minor | `.ai` and EPS import, and what SVG export, PDF export and `.ai` save draw: centred and right-aligned lines of tracked type are aligned without the tracking after their last character, as a `.ai`'s own page aligns them (see type-parity.md, same date). In 224 users' files (used locally, never committed), 63 render differently: 54 closer to the page, 7 further, 1 alike and 1 that couldn't be compared; among them the median share of pixels that differ from the page went from 4.4% to 1.0%. Of the 7: one now opens from its layers instead of its PDF part (they differ from the page by less than the import's 5% limit now; type note 3.8%); one has a centred line whose spacing inside it differs from the page's, which now centres on the page's line while half of it no longer lies on the page's glyphs (type note 1.4% to 2.4%); five moved by less than 0.05 of a percentage point, in type the page draws in other glyphs. The type note shows a smaller share in 6 files; 2 more envelopes of type open live (129 to 131, none fewer in any file). SVG import: `text-anchor` aligns a line's advance, which ends after its last character's letter spacing, so a centred or right-aligned letter-spaced line moves back by half or all of it and stays where a reader draws it; SVG export, which anchors lines so, reads back unchanged (test `import_text::anchored_letter_spaced_lines_stay_where_a_reader_draws_them`). Affinity import: centred or right-aligned tracked artistic text moves by half or all of its last character's tracking, as layout now aligns it (unverified against Affinity, whose files we don't have), and differently aligned paragraphs line up by the widths layout aligns them by (`affinity_features::artistic_text_aligns_its_lines_within_the_longest`). Scores and estimates unchanged. (#6610) |
|
||||
| 2026-11-11 | minor | `.ai` and EPS import: while an envelope's contents are edited, the editing data keeps its mesh (a mesh's, a top object's or a warp's grid) in its dictionary, and in some files that mesh lies away from the drawn art by a move, the contents with the drawn art, as if the mesh hadn't followed the envelope when it moved: the envelope drew its contents elsewhere and kept its drawn art. A mesh or top object saved in Edit Contents (or a warp of type tried on its saved grid) whose mesh draws the contents as large as the drawn art, within half a point over width and height together, but further from it than that, is now tried again, after every other way and with one more of the import's checks, with its mesh moved by as far as the drawn art's box lies from the box of what it draws; it opens live there where it then draws as the drawn art. In users' files, used locally and never committed, only envelopes saved in Edit Contents draw elsewhere than their drawn art, and nothing in their dictionaries says by how much they moved. Tests `ai::tests::an_envelope_in_edit_contents_whose_mesh_stayed_behind_opens_live_on_its_drawn_art`, `tests_native::a_warp_of_type_in_edit_contents_whose_grid_stayed_behind_opens_on_its_drawn_art` (#6570) |
|
||||
|
||||
@@ -858,6 +858,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | An imported document's own title (an SVG's root `<title>`, a PDF's XMP `dc:title` else its Info `Title`) goes into File Info only; the document's name stays the file's name (#3626); no scored gap or estimate reassessed. |
|
||||
| 2026-11-11 | minor | Eyedropper copies effective character dimensions between transformed text objects (#6404); scores and estimates unchanged. |
|
||||
| 2026-11-11 | minor | G5: built-in Energy Beams brush library (Laser Bolt, Plasma Arc, Charge Wave, Spark Trail); Missing trimmed; estimate 5–15 → 4–14 h; MCP Brush libraries list Energy Beams. |
|
||||
| 2026-11-11 | minor | G5: built-in Skill Icons symbol library (Attack, Defend, Heal, Buff, Ult); Missing trimmed; estimate 6–16 → 5–15 h; MCP Symbol libraries list Skill Icons. |
|
||||
|
||||
+118
-16
@@ -165,6 +165,14 @@ the suite) keep the agent to the folders you give it:
|
||||
and `modified` the document keeps of that file. Each row keeps its id, `name` (`fileName`), status and other
|
||||
fields. This applies to every document, also one the person or a client with wider roots opened. The paths a
|
||||
client gives `links.relink` and `links.unembed` stay in their replies, and the document's links are not changed.
|
||||
- `image.info` gives `link: null` for a linked file outside the read roots, as `links.info` leaves it out; its id,
|
||||
name, `linked` and the other fields stay. `links.findFiles` and `text.findFontFiles` replies, including later
|
||||
`{}` and `{stop: true}` calls on a search another client started, leave out the files found outside the read
|
||||
roots (with their `size` and `match`), a sought link's `path` outside them, and `folder` when the folder searched
|
||||
is outside them; `error` then gives the reason without the path (for example `the folder can't be read`). The
|
||||
rows keep their `name`, `ids`, family, style and status, and the counts stay. `text.addFontFiles` gives
|
||||
`folder: null` when VectorCraft's Fonts folder is outside the read roots, and only the file name in `copied` and
|
||||
`kept` `to` and in `skipped` `file` for a path outside them; the fonts are still copied.
|
||||
- `prefs.get`, `prefs.list`, `prefs.set` and `prefs.reset` show a folder preference outside the read roots
|
||||
(`fontsFolder`, `pluginsFolder`, `scratchPrimary`, `scratchSecondary`, `recoveryFolder`, `templatesFolder`) as
|
||||
`null`; an empty folder and the scratch disk choices `Startup` and `None` stay. In `prefs.get {}` and
|
||||
@@ -463,7 +471,14 @@ or a new object (`css.export`, `object.isolate`, `layer.locate`, `layer.releaseT
|
||||
counterparts, `image.info`, `links.info`, `links.goTo`, `links.unembed`, `shaper.release`, `shaper.expand`,
|
||||
`transparency.editOpacityMask`, `transparency.viewOpacityMask`, `symbol.edit`, the `object.blend.spine.*` commands
|
||||
and `path.curvature`) refuse an `id` that is not a non-negative integer (`"abc"`, `"5"`, `true`) with an error and
|
||||
change nothing; an omitted or null `id` keeps the default. A command that leaves out an object selected inside a
|
||||
change nothing; an omitted or null `id` keeps the default. Commands that need an object id (`document.node`, the
|
||||
`path.*` anchor and handle commands, `text.reshapePath`, `text.editRange`, the `object.mesh.*` point commands,
|
||||
`layer.target`, `node.move`, `select.magicWand` and the others like them, and the `source`, `target`, `group` and
|
||||
`path` keys of `appearance.transfer`, `appearance.copyFrom`, `layer.move`, the `livePaint.*` commands and
|
||||
`text.createInPath`) refuse a value that is not a non-negative integer with "`id` must be a whole number", naming the
|
||||
key, and report a key left out or null as missing. The optional `group` of `livePaint.fill` and `parent` of
|
||||
`node.move` are refused the same way when given; left out, they keep their defaults. `livePaint.info` reads `id` when
|
||||
`group` is left out or null, and reports a missing group only when both are left out. A command that leaves out an object selected inside a
|
||||
selected group also leaves it out when `ids` list it together with a group that holds it. Every command that takes
|
||||
`ids` (or `slices`, for the slice commands) refuses a list with an entry that is not a non-negative integer, or a value
|
||||
that is not a list, and changes nothing; it does not act on the other entries. The error for an entry names the key,
|
||||
@@ -3442,6 +3457,16 @@ that shape, and the effects before it are left out. Its Contents row stands for
|
||||
vector exports write those items as paths, and `effect.expandAppearance` turns them into objects in a group with the
|
||||
image.
|
||||
Use Preview Bounds measures an image by its frame.
|
||||
A Warp effect (`warp.arc` … `warp.twist`) on an image, or on a group that holds one, bends the image's pixels with its
|
||||
frame: after the last effect in the list, the image is cut into 10 × 10 cells of two triangles, each a clipped piece of
|
||||
the image under its own affine map. The cell count is VectorCraft's own. Effects after the Warp reshape only the frame,
|
||||
a second Warp bends the same pieces, and a Transform effect moves them. A placed gradient on a group that holds the
|
||||
image follows the group's bounds as the pieces draw them. The pieces keep the
|
||||
image's fill color when it colorizes a grayscale image, and so do the pieces an envelope cuts. Under any geometry
|
||||
effect, an image keeps that color and is drawn with or without smoothing as the image is. SVG writes the image once and
|
||||
draws the pieces with `<use>`; PDF, EPS, `.ai` and PNG write the bent pieces. Removing the warp shows the image as it
|
||||
was. The images stay unbent when their pieces would make the art pass 2 million objects, and an image stays unbent when
|
||||
its clipping frame keeps its shape because the warp would pass the effects' anchor budget.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"appearance.addFill","params":{"ids":[7]}}}
|
||||
@@ -5766,13 +5791,23 @@ They also write the `copyrightNotice` as `dc:rights`, the `copyrightUrl` as `xmp
|
||||
`copyrightStatus` as `xmpRights:Marked` (`True` for copyrighted, `False` for public domain, none for unknown) to the
|
||||
XMP packet.
|
||||
A PDF, `.ai` or `.ait` opened from its pages or its editing copy (`document.open`,
|
||||
`file.newFromTemplate`, `file.revert`; not one restored from the document it carries) opens with the `author`,
|
||||
`description`, `keywords`, `copyrightStatus`, `copyrightNotice`, `copyrightUrl`, `created` and `modified` that its XMP
|
||||
packet (`dc:creator`, `dc:description`, `dc:subject` or `pdf:Keywords`, `xmpRights:Marked`, `dc:rights`,
|
||||
`xmpRights:WebStatement`, `xmp:CreateDate`, `xmp:ModifyDate`) and its document information dictionary (`Author`,
|
||||
`Subject`, `Keywords`, `CreationDate`, `ModDate`) give, the XMP packet's value where both give one. A value that
|
||||
`file.info` would refuse is left out, and the title is set as for any other open (the file name, or a new untitled
|
||||
name for a template).
|
||||
`file.newFromTemplate`, `file.revert`; not one restored from the document it carries) opens with the `title`,
|
||||
`author`, `description`, `keywords`, `copyrightStatus`, `copyrightNotice`, `copyrightUrl`, `created` and `modified`
|
||||
that its XMP packet (`dc:title`, `dc:creator`, `dc:description`, `dc:subject` or `pdf:Keywords`, `xmpRights:Marked`,
|
||||
`dc:rights`, `xmpRights:WebStatement`, `xmp:CreateDate`, `xmp:ModifyDate`) and its document information dictionary
|
||||
(`Title`, `Author`, `Subject`, `Keywords`, `CreationDate`, `ModDate`) give, the XMP packet's value where both give
|
||||
one (for `dc:title` and `dc:description`, the `x-default` language item first). An SVG opens with the `title` of its
|
||||
root `<title>` element (not a shape's or group's). A title is read with its white space made single spaces and
|
||||
without control characters, at most 65,536 characters, as `file.info` takes one. A value that `file.info` would
|
||||
refuse is left out. A file that gives no title, a blank one or the `Untitled-N` name New gives leaves File Info with
|
||||
the document's name as its title, and a template opens with a new untitled name.
|
||||
The title a file gave is File Info's alone: the document's name, which is what its tab shows until it has a file and
|
||||
what Save As and the default export names start from, stays the file's name with its extension (`box.svg`), or the
|
||||
title a native file holds. File Info's `title` is the one the file gave, else the document's name. It is saved with the
|
||||
document (a native file, and the editing data of an SVG, PDF, `.ai` or EPS that carries it), and the files an export
|
||||
writes carry it (PDF `Title` and XMP `dc:title`, SVG `<title>` and Dublin Core, PNG text, EPS and `.ai` `%%Title`),
|
||||
where the document has one. Changing the `title` with `file.info` sets the document's name, as it always did, and drops
|
||||
the title the file gave.
|
||||
`document.inspect` (`inspect_document`) and the `document.open` (`open_file`) reply report the File Info title as
|
||||
`fileInfoTitle`. Their `title` is the document's tab name, which comes from the file name once the document is saved
|
||||
to or opened from a file (with ` [Converted]` or ` [Recovered]` where that applies).
|
||||
@@ -6532,9 +6567,15 @@ else its topmost visible fill's, where a fill or stroke at 0% opacity does not c
|
||||
of its stops, a gradient mesh's the average of its points' colors (points at 0% opacity left out), and a pattern's its first visible solid fill (gray
|
||||
when it has none). The pens draw in turn, each pen's paths in paint order, or with `sortPaths: true` each next path
|
||||
the one that starts nearest where the pen stopped (an open path may be drawn from its other end, and a closed one
|
||||
starts at its point nearest the pen). Curves become straight moves no further than `curveTolerance` points from the
|
||||
curve (default 0.1, from 0.01 to 10). These defaults, the 255-pen limit and the rules for pen numbers and colors above
|
||||
are VectorCraft's own.
|
||||
starts at its point nearest the pen). With `insideFirst: true`, a path is drawn only after every path of the same
|
||||
pen that lies inside it, for example a letter's counter before the letter and a part's holes before the part. A
|
||||
path lies inside a closed path when it does not cross that path's line, every part of it lies inside that path (by
|
||||
the even-odd rule) or on its line, and some part lies inside: a path touching the line around it is inside, a path
|
||||
crossing it or leaving it through a corner is not, and copies of one outline are not inside each other. Without
|
||||
`sortPaths` the paths otherwise keep paint order; with it, the next path is the nearest of those free to draw.
|
||||
Curves become straight moves no further than `curveTolerance` points from the curve (default 0.1, from 0.01 to 10).
|
||||
These defaults, the 255-pen limit and the rules for pen numbers, colors and the order of paths above are
|
||||
VectorCraft's own.
|
||||
|
||||
`hpglVersion: "hpgl"` (the default) writes `IN;`, then for each pen `PU;SP<n>;` and each path `PUx,y;PDx,y,…;`, and
|
||||
ends with `PU;SP0;`. `"hpgl2"` also writes the palette (`NP` and a `PC` color for each pen) and draws each pen's
|
||||
@@ -6543,11 +6584,16 @@ with a coordinate outside −32,768 to 32,767 units (about 82 cm from the origin
|
||||
as it is, with a warning that older devices may not read it; this check is VectorCraft's own. `warnings` names
|
||||
type drawn as outlines, inline graphics in type (left out), placed images, opacity masks, raster effects, stroke
|
||||
looks that are not drawn, art past the plotter's range, HP-GL coordinates past 16-bit numbers, colors past 255 pens,
|
||||
and the limits on points, clipping work, symbols, live art, nesting depth and sorting.
|
||||
and the limits on points, clipping work, symbols, live art, nesting depth and sorting. Sorting and finding which paths lie inside which share one
|
||||
work budget for the plot, 500 million box and point comparisons. When sorting runs out of it, the paths not yet
|
||||
drawn keep paint order, each after the paths inside it with `insideFirst`. When it runs out while the writer is
|
||||
finding which paths lie inside which, the paths of that pen and of the later pens keep paint order. Both cases add
|
||||
a warning.
|
||||
|
||||
```json
|
||||
{"name":"export","arguments":{"path":"/tmp/decal.plt","format":"hpgl"}}
|
||||
{"name":"run_command","arguments":{"command":"document.export","params":{"format":"hpgl","path":"/tmp/sheet.plt","useArtboards":true,"hpglVersion":"hpgl2","pens":["#000000","#ff0000"],"sortPaths":true}}}
|
||||
{"name":"run_command","arguments":{"command":"document.export","params":{"format":"hpgl","path":"/tmp/stencil.plt","sortPaths":true,"insideFirst":true}}}
|
||||
```
|
||||
|
||||
## TIFF, BMP and Targa export
|
||||
@@ -7348,6 +7394,25 @@ Panel sets it back to Push Out Only. One undo step.
|
||||
{"name":"run_command","arguments":{"command":"text.setFormat","params":{"kinsokuType":"pushInFirst"}}}
|
||||
```
|
||||
|
||||
## Korean Line Break Anywhere
|
||||
|
||||
`text.setFormat {koreanLineBreakAnywhere: bool}` (Paragraph panel menu › Korean Line Break Anywhere, shown with the
|
||||
East Asian options) turns it on or off for the paragraphs of the selected type (or `ids`, or the paragraphs a `start` /
|
||||
`end` range touches). Off, a Korean word (the characters between two spaces, when any of them is Hangul, Han and kana
|
||||
included) breaks only at its spaces, after East Asian punctuation in it, and at forced line breaks, so a word that does
|
||||
not fit moves to the next line whole; a word wider than the line still breaks where it overflows. On, a line may break
|
||||
between any two Hangul syllables. Both composers and vertical type follow it, No Break and the Kinsoku Set still apply,
|
||||
and hyphenation never adds a hyphen to Hangul. Text without Hangul keeps its own breaks. New type and imported text have
|
||||
it off; paragraphs saved before the option open with it on, the breaks they were laid out with. It is saved with the
|
||||
paragraph (`korean_line_break_anywhere` in `para`) only when off, paragraph styles carry it (Paragraph Style Options ›
|
||||
East Asian Composition Settings), and Reset Panel turns it off. A value that is not true or false is refused. One undo
|
||||
step. `.ai` editing data does not hold it, and a save reports a Korean paragraph with it on as left out. The
|
||||
help page: https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/set-line-breaks-korean-text.html
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"text.setFormat","params":{"koreanLineBreakAnywhere":true}}}
|
||||
```
|
||||
|
||||
## Hanging punctuation (burasagari)
|
||||
|
||||
`text.setFormat {burasagari: "none"|"standard"|"forced"}` sets the Paragraph panel menu's Burasagari (None / Regular /
|
||||
@@ -7907,6 +7972,26 @@ begins or ends, dragging the centre bracket slides the type along its path and,
|
||||
{"name":"run_command","arguments":{"command":"type.pathOptions","params":{"start":0.25,"end":0.75,"flip":true}}}
|
||||
```
|
||||
|
||||
`type.pathCenter.top {ids?|id?}` and `type.pathCenter.bottom {ids?|id?}` (Type › Type on a Path › Center on Top and
|
||||
Center on Bottom, and the Contextual Task Bar for type on a closed path) slide the selected type on a closed path along
|
||||
it so that the middle of the span between its brackets lands on the path's topmost or bottommost point. This is
|
||||
VectorCraft's own behavior. The path is measured as the object is drawn, after its transform, so a rotated, scaled or
|
||||
skewed circle centers on its drawn top. On a level stretch, such as a rectangle's top edge, the middle of the stretch
|
||||
is used; with several separate topmost points, the middle of the shortest part of the path that holds them all is
|
||||
used. The span keeps its length, and type that runs once round stays once round. The first paragraph's alignment
|
||||
becomes Center unless it is Center or Justify Center, so the type is centered there. The type is not flipped: on the
|
||||
bottom of a circle, whose path runs clockwise, it reads upside down outside the circle until `type.pathOptions {flip:
|
||||
true}` turns it inside. Point type and area type in the selection are left as they are. Type on an open or compound
|
||||
path, type in a thread, and type on a path with no length or no height are refused with an error that names them,
|
||||
and nothing changes. The menu items are enabled, and the task bar shows them, only while every selected type on a
|
||||
path is on one closed path and none of it is in a thread. Several objects move in one undo step. When every object is
|
||||
already centered there, nothing changes and no undo step is recorded. Each replies `{ids, start, end}` with the first
|
||||
object's brackets.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"type.pathCenter.top","params":{}}}
|
||||
```
|
||||
|
||||
## Dynamic Text
|
||||
|
||||
`type.dynamicText {on?: bool, ids?}` (Type › Dynamic Text, and the Contextual Task Bar for type on a path) turns
|
||||
@@ -8056,8 +8141,8 @@ the Paragraph Styles panel, or Paragraph Style Options… in its menu, does the
|
||||
`paraStyle.edit {name, to, attrs}`, which takes the same arguments as `charStyle.edit` and replaces the style's
|
||||
name and attributes in one undo step; the paragraphs that use the style keep their local overrides. OK with no field
|
||||
changed records no undo step, and Preview updates the paragraphs while the dialog is open (Cancel restores them).
|
||||
The `paraStyleOptions` dialog exposes `name`, `section` (`general`, `basic`, `tabs`, `composition`, `hyphenation` or
|
||||
`justification`) and `preview`, and these fields:
|
||||
The `paraStyleOptions` dialog exposes `name`, `section` (`general`, `basic`, `tabs`, `composition`, `hyphenation`,
|
||||
`justification` or `eastAsian`) and `preview`, and these fields:
|
||||
|
||||
- Basic Paragraph Formats: `justify` (`Auto`, `Left`, `Center`, `Right`, `JustifyLeft`, `JustifyCenter`,
|
||||
`JustifyRight` or `JustifyAll`), and `left_indent`, `right_indent`, `first_line_indent`, `space_before` and
|
||||
@@ -8076,6 +8161,8 @@ The `paraStyleOptions` dialog exposes `name`, `section` (`general`, `basic`, `ta
|
||||
- Justification: `word_min`, `word_desired`, `word_max`, `letter_min`, `letter_desired`, `letter_max`, `glyph_min`,
|
||||
`glyph_desired` and `glyph_max` (percent), `auto_leading` (percent) and `single_word` (`fullJustify`,
|
||||
`alignLeft`, `alignCenter` or `alignRight`).
|
||||
- East Asian Composition Settings: the `korean_line_break_anywhere` checkbox (Korean Composition Settings › Korean
|
||||
Line Break Anywhere).
|
||||
|
||||
A field left as it was keeps the attribute as the style has it, and an empty field leaves its attribute unset; an
|
||||
empty Hyphenation or Justification field takes its default while another field of that section is set. Attributes
|
||||
@@ -8090,8 +8177,9 @@ position. A value the style already has is kept. An invalid value leaves the
|
||||
style, its paragraphs and Undo history unchanged, and keeps the dialog open.
|
||||
Reset Panel removes the section's attributes from the definition: Basic Paragraph Formats removes `justify` and the
|
||||
five distances, Tabs removes `tabs`, Composition removes `composer` and `roman_hanging`, Hyphenation removes `hyphenate` and
|
||||
`hyphenation`, and
|
||||
Justification removes `justification`. `ui.paraStyleOptions.resetPanel {}` performs that reset for the section
|
||||
`hyphenation`,
|
||||
Justification removes `justification`, and East Asian Composition Settings removes `korean_line_break_anywhere`.
|
||||
`ui.paraStyleOptions.resetPanel {}` performs that reset for the section
|
||||
selected in Paragraph Style Options or New Paragraph Style; it is disabled on General.
|
||||
|
||||
In the native file a text object's `para` holds the first paragraph's attributes and `paras` (left out when every
|
||||
@@ -8580,6 +8668,20 @@ share of the content's width or height in proportion to the length of its contro
|
||||
its curve parameter, so content spreads evenly along a segment without handles. An envelope opened from an `.ai` file
|
||||
keeps the corners the file saves, and they stay on the same anchors when the envelope is moved, rotated or
|
||||
reflected. These are VectorCraft's own rules, and existing top-object envelopes redraw by them.
|
||||
A warp bends the content's bounds as one Bézier patch. The style sets the patch's control points from the bend and
|
||||
the size of the bounds (Arc's top and bottom edges, for example, are cubic curves close to arcs around one center), then
|
||||
the horizontal distortion scales each column of control points about its middle, and the vertical distortion scales
|
||||
each row. Content with no width or no height takes the middle of the patch along that side. New warp envelopes, and
|
||||
those opened from `.ai` files, carry `version` 1 for this map, and `object.envelope.info` reports it. Envelopes in
|
||||
`.vectorcraft` documents saved before the version existed read as `version` 0, the earlier map, and keep their look,
|
||||
and editing an envelope keeps its version. `version` (0 or 1) on `object.envelope.makeWithWarp`,
|
||||
`object.envelope.resetWithWarp` and `object.envelope.options` sets the map. The Warp effects (`warp.arc` …
|
||||
`warp.twist`) take the same map through the same `version` param: `effect.apply` gives a new Warp `version` 1 unless
|
||||
`params.version` is 0, the built-in graphic styles' Warps have 1, and Effect › Warp edits keep each Warp's own version.
|
||||
These commands refuse any other value with `` `version` must be 0 or 1 ``; `1.0` counts as 1. An `.ai` file holds a
|
||||
warp's settings and not its version, so a `version` 0 warp saved to `.ai` and opened again draws with `version` 1.
|
||||
These patches are VectorCraft's own rules; the styles and the ranges of Bend and the distortions are those of the Warp
|
||||
options (https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html).
|
||||
`object.envelope.info` reads the selected envelope's settings (`type`, the warp's or mesh's values, `fidelity` and
|
||||
the options, `editing`), or with no envelope selected the options new envelopes get. With an envelope selected,
|
||||
`object.envelope.resetWithWarp` and `object.envelope.resetWithMesh` (`rows`, `cols`, `maintainShape`: true keeps
|
||||
|
||||
+151
-157
@@ -1,9 +1,9 @@
|
||||
# Plug-ins: the WebAssembly plug-in API (ABI v1)
|
||||
|
||||
VectorCraft does not host native plug-ins built for other illustration apps: that needs unsafe FFI into
|
||||
arbitrary machine code and cannot work in the browser build. Instead, plug-ins are **WebAssembly modules**
|
||||
run in a sandbox. One `.wasm` file works on macOS, Windows, Linux and the web. The design follows PhotoCraft's
|
||||
plug-in API (same sandbox, same manifest and parameter schema), extended for vector documents.
|
||||
arbitrary machine code and cannot work in the browser build. Instead, plug-ins are **WebAssembly modules** run in a
|
||||
sandbox. One `.wasm` file works on macOS, Windows, Linux and the web. The design follows PhotoCraft's plug-in API (same
|
||||
sandbox, same manifest and parameter schema), extended for vector documents.
|
||||
|
||||
There are four kinds of plug-in:
|
||||
|
||||
@@ -20,8 +20,8 @@ There are four kinds of plug-in:
|
||||
paths and compound paths. File › Open lists their files, File › Open With lists the importers by name, and the host
|
||||
hands them the file's bytes.
|
||||
|
||||
This page is the contract for plug-in authors and the reference for the host (`crates/plugins`, the
|
||||
`plugin.*` commands in `crates/engine/src/cmd/plugin.rs`).
|
||||
This page is the contract for plug-in authors and the reference for the host (`crates/plugins`, the `plugin.*` commands
|
||||
in `crates/engine/src/cmd/plugin.rs`).
|
||||
|
||||
## Using plug-ins
|
||||
|
||||
@@ -44,14 +44,15 @@ An exporter adds a write-only format, `plugin.<id>`, which `document.formats` li
|
||||
its extensions that no built-in format has; `selectedOnly` hands it the selection, as for the built-in formats.
|
||||
|
||||
An importer adds a read-only format, `plugin.<id>`, which `document.formats` lists with its `plugin` id. `openExtensions`
|
||||
holds the extensions File › Open opens with the importer (the lists below describe which are left out). `document.open {"path"}` (or `{"name", "dataBase64"}`) opens a file with it when no built-in format
|
||||
reads the file, by its content or by its extension, and the importer lists the file's extension; when several do, the
|
||||
one whose id sorts first opens it. `document.open {"importer": "plugin.<id>", "params"?: {…}}` names the importer,
|
||||
which must list the file's extension, also one a built-in format reads (an importer that lists `svg` opens an `.svg`
|
||||
only this way). A damaged file of a built-in format reports that format's error, and the file is not handed to an
|
||||
importer. The importer's objects become a new document whose artboard is their bounds (at least 1 pt wide and high,
|
||||
and within ±4,000,000 pt); the document keeps no path, so Save asks for a name. File › Place reads only the built-in formats, and a file only an importer reads opens as a new document when it is dropped on the canvas. These rules are
|
||||
VectorCraft's own.
|
||||
holds the extensions File › Open opens with the importer (the lists below describe which are left out). `document.open
|
||||
{"path"}` (or `{"name", "dataBase64"}`) opens a file with it when no built-in format reads the file, by its content or
|
||||
by its extension, and the importer lists the file's extension; when several do, the one whose id sorts first opens it.
|
||||
`document.open {"importer": "plugin.<id>", "params"?: {…}}` names the importer, which must list the file's extension,
|
||||
also one a built-in format reads (an importer that lists `svg` opens an `.svg` only this way). A damaged file of a
|
||||
built-in format reports that format's error, and the file is not handed to an importer. The importer's objects become a
|
||||
new document whose artboard is their bounds (at least 1 pt wide and high, and within ±4,000,000 pt); the document keeps
|
||||
no path, so Save asks for a name. File › Place reads only the built-in formats, and a file only an importer reads opens
|
||||
as a new document when it is dropped on the canvas. These rules are VectorCraft's own.
|
||||
|
||||
In the app, installed filters appear under **Object › Plug-ins**, next to **Install Plug-in…** and **Reload
|
||||
Plug-ins**; live effects under **Effect › Plug-ins** and in the Appearance panel's effect menu. A plug-in with
|
||||
@@ -60,25 +61,23 @@ of numbers and colors, labelled, explained and sorted into sections as its manif
|
||||
parameters it runs at once. Edit › Keyboard Shortcuts lists each installed filter under Object › Plug-ins, where
|
||||
it can be given a shortcut (`plugin:<id>`); the shortcut stays saved while the plug-in is removed and applies again
|
||||
when it is reinstalled. Parameters too many for the window's height scroll inside the dialog, which keeps its
|
||||
heading and buttons in view. Exporters are formats of **File › Export › Export As**, where Use
|
||||
Artboards is greyed out (the plug-in writes the whole document); one with parameters asks for them in a dialog of the
|
||||
same kind once the file is picked, and its Export button writes the file. Importers' files are listed under File ›
|
||||
Open's "All readable files" and under a filter of their own, named after the importer (other open dialogs, such as New
|
||||
from Template and Load Styles, list only the files the built-in formats read). These lists leave out an importer's
|
||||
extensions that a built-in format reads, and those File › Open installs, imports or loads as something else (`.wasm`,
|
||||
`.kys`, `.jsx`, presets, libraries and swatch or graphic style libraries such as `.ase`). File › Open does not give
|
||||
those files to the importer, and Open With does. **File › Open With** lists the
|
||||
installed importers by name (`file.openWith {"importer", "path"?, "params"?}`), and each opens a file picked among its
|
||||
extensions with that importer (on the web, File › Open's picker opens instead) and adds it to Open Recent. On the
|
||||
desktop the importer runs off the UI thread, and a run that takes a while shows a progress window with Cancel, which
|
||||
stops it. An importer
|
||||
with parameters, given no `params`, asks for them in a dialog of the same kind once the file is picked, and its Open
|
||||
button opens the file; File › Open runs an importer with its parameters' defaults. Open Recent opens a file the way
|
||||
File › Open does: it does not remember the importer or the parameters given to Open With. A built-in format reads the
|
||||
file if one can, and otherwise the first importer by id that lists its extension reads it with its parameters' defaults.
|
||||
**File › Open** of a `.wasm` file installs
|
||||
a plug-in too (on the web this is how plug-ins get in). A heavy filter asks before it is installed, and runs in the
|
||||
background ([below](#heavy-filters)). A plug-in that lists permissions asks for them before it is installed
|
||||
heading and buttons in view. Exporters are formats of **File › Export › Export As**, where Use Artboards is greyed out
|
||||
(the plug-in writes the whole document); one with parameters asks for them in a dialog of the same kind once the file is
|
||||
picked, and its Export button writes the file. Importers' files are listed under File › Open's "All readable files" and
|
||||
under a filter of their own, named after the importer (other open dialogs, such as New from Template and Load Styles,
|
||||
list only the files the built-in formats read). These lists leave out an importer's extensions that a built-in format
|
||||
reads, and those File › Open installs, imports or loads as something else (`.wasm`, `.kys`, `.jsx`, presets, libraries
|
||||
and swatch or graphic style libraries such as `.ase`). File › Open does not give those files to the importer, and Open
|
||||
With does. **File › Open With** lists the installed importers by name (`file.openWith {"importer", "path"?,
|
||||
"params"?}`), and each opens a file picked among its extensions with that importer (on the web, File › Open's picker
|
||||
opens instead) and adds it to Open Recent. On the desktop the importer runs off the UI thread, and a run that takes a
|
||||
while shows a progress window with Cancel, which stops it. An importer with parameters, given no `params`, asks for them
|
||||
in a dialog of the same kind once the file is picked, and its Open button opens the file; File › Open runs an importer
|
||||
with its parameters' defaults. Open Recent opens a file the way File › Open does: it does not remember the importer or
|
||||
the parameters given to Open With. A built-in format reads the file if one can, and otherwise the first importer by id
|
||||
that lists its extension reads it with its parameters' defaults. **File › Open** of a `.wasm` file installs a plug-in
|
||||
too (on the web this is how plug-ins get in). A heavy filter asks before it is installed, and runs in the background
|
||||
([below](#heavy-filters)). A plug-in that lists permissions asks for them before it is installed
|
||||
([below](#permissions)).
|
||||
|
||||
### Commands
|
||||
@@ -117,9 +116,9 @@ the object is drawn as if the effect weren't there, and the record is kept for w
|
||||
|
||||
## The sandbox
|
||||
|
||||
The host is [`wasmi`](https://github.com/wasmi-labs/wasmi), a pure-Rust WebAssembly interpreter
|
||||
(MIT OR Apache-2.0). It builds for `wasm32-unknown-unknown` too, so plug-ins also run in the web build. Limits
|
||||
(`vectorcraft_plugins::Limits`, defaults):
|
||||
The host is [`wasmi`](https://github.com/wasmi-labs/wasmi), a pure-Rust WebAssembly interpreter (MIT OR Apache-2.0). It
|
||||
builds for `wasm32-unknown-unknown` too, so plug-ins also run in the web build. Limits (`vectorcraft_plugins::Limits`,
|
||||
defaults):
|
||||
|
||||
| Limit | Default | On violation |
|
||||
|---|---|---|
|
||||
@@ -155,15 +154,15 @@ measured with the page's `performance.now()`.
|
||||
Some filters do heavy but legitimate work, such as routing a plotter's pen, packing shapes onto a sheet or running a
|
||||
simulation, which a minute and 50 M instructions don't cover. Such a filter declares `"budget": "heavy"` in its
|
||||
manifest (only a filter may: a live effect runs whenever the document is drawn, an exporter has an export's budget and
|
||||
an importer an import's). Each of its runs gets 1 G instructions (20 times a light run's) plus the 4,000 per input byte, and 300 s
|
||||
(`vectorcraft_plugins::{HEAVY_FUEL, HEAVY_WALL_TIME_MS}`). Its output can't add to the budget: the host learns how
|
||||
much there is only once the run has ended.
|
||||
an importer an import's). Each of its runs gets 1 G instructions (20 times a light run's) plus the 4,000 per input byte,
|
||||
and 300 s (`vectorcraft_plugins::{HEAVY_FUEL, HEAVY_WALL_TIME_MS}`). Its output can't add to the budget: the host learns
|
||||
how much there is only once the run has ended.
|
||||
|
||||
- **Installing asks first.** Install Plug-in… and File › Open show what the filter asks for, and install it once the
|
||||
user clicks OK (see [Permissions](#permissions) for who can). `plugin.install` refuses it without `allowHeavy: true`,
|
||||
and refuses `allowHeavy` from a request except in a session started with `vectorcraft-cli --allow-plugin-grants`
|
||||
(`plugin.inspect` shows the budget beforehand). A filter kept in the `Plug-ins` folder was agreed to and is installed again
|
||||
at startup. The Additional Plug-ins Folder skips heavy filters with a note in its report, since loading a folder
|
||||
(`plugin.inspect` shows the budget beforehand). A filter kept in the `Plug-ins` folder was agreed to and is installed
|
||||
again at startup. The Additional Plug-ins Folder skips heavy filters with a note in its report, since loading a folder
|
||||
asks nobody.
|
||||
- **It runs in the background.** Its dialog has no Preview. OK (or the menu item, without parameters) runs it on a
|
||||
thread of its own while editing goes on, shown in the status bar and, after a moment, in a progress window with
|
||||
@@ -203,17 +202,15 @@ capability model proposed for PhotoCraft plug-ins
|
||||
`state`, which only its commands use. A filter may list any of its three, and `commands` with a `commandAllowlist`.
|
||||
Any kind may add `progress`, `settings` and `cache`, which no kind implies. An unknown name, a name listed twice, a
|
||||
name that doesn't apply to the kind, more than 16 names, `progress`, `settings`, `cache` or `commands` with an `api`
|
||||
below 2, or
|
||||
one of the names kept for a later plug-in API
|
||||
(`ui.panel`, `ui.tool`) is refused when the module is installed, and the error names it.
|
||||
below 2, or one of the names kept for a later plug-in API (`ui.panel`, `ui.tool`) is refused when the module is
|
||||
installed, and the error names it.
|
||||
- **Installing asks first.** Install Plug-in… and File › Open show the plug-in's name, id, version, the SHA-256 of
|
||||
the file and each permission in plain words, and install it once the user clicks OK, if the file still has the
|
||||
SHA-256 shown; Cancel installs nothing. The
|
||||
names shown have control and bidirectional characters escaped (`\u{202E}`) and are cut to 64 characters.
|
||||
Only a person can press its OK: `ui.dialog.confirm`, MCP's `answer_dialog` and injected input (`ui.key`, `ui.click`)
|
||||
are refused and the dialog stays open. Enter confirms it only when OK has the keyboard focus. `ui.dialog.set` can't
|
||||
change what it installs. OK stays disabled until the question has been on screen in the focused window for half a
|
||||
second (VectorCraft's own value, from the delay the security guidance at
|
||||
SHA-256 shown; Cancel installs nothing. The names shown have control and bidirectional characters escaped (`\u{202E}`)
|
||||
and are cut to 64 characters. Only a person can press its OK: `ui.dialog.confirm`, MCP's `answer_dialog` and injected
|
||||
input (`ui.key`, `ui.click`) are refused and the dialog stays open. Enter confirms it only when OK has the keyboard
|
||||
focus. `ui.dialog.set` can't change what it installs. OK stays disabled until the question has been on screen in the
|
||||
focused window for half a second (VectorCraft's own value, from the delay the security guidance at
|
||||
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/security/security-considerations-for-browser-ui.md
|
||||
gives for dialog boxes), so a click, Enter or Space, or a press begun before then, installs nothing. The half second
|
||||
starts over for every new question, even one that reads the same, and when the window gets the focus back. Cancel
|
||||
@@ -280,23 +277,22 @@ capability model proposed for PhotoCraft plug-ins
|
||||
does not count for any session in the same process that uses the same `Plug-ins` folder, by any path to it, until
|
||||
`grants.json` can be written for that plug-in, as when the plug-in is granted again. Another process on the same
|
||||
folder, for example `vectorcraft-cli` beside the app, still reads `grants.json` as it is written. A grant for the
|
||||
same id kept in another `Plug-ins` folder counts. In Safe Mode, and for
|
||||
a stored module that the user disabled,
|
||||
same id kept in another `Plug-ins` folder counts. In Safe Mode, and for a stored module that the user disabled,
|
||||
VectorCraft does not read the module, and `grants.json` keeps the grant for its id. The Additional Plug-ins Folder
|
||||
and `plugin.reload` do not change the `Plug-ins` folder: when they leave a module that lists no permissions under an
|
||||
id, the process forgets its own grant for that id, and a grant kept in `grants.json` stays with the stored module it
|
||||
covers. An entry of `grants.json` that this version can't use (a permission it doesn't know, an invalid id) is
|
||||
skipped and stays in the file until its plug-in is granted again, replaced by a module that lists no permissions, or
|
||||
removed. When such an entry can't be removed from the file, `plugin.install` and `plugin.remove` treat it as a grant
|
||||
that can't be removed. A `grants.json` that is not a JSON list is never overwritten, and the file is never written larger than
|
||||
1 MiB, the most that is read. When a `Plug-ins` folder is in use, only the grants kept
|
||||
in it count for `plugin.install`, `plugin.inspect`, `plugin.reload` and the Additional Plug-ins Folder. At startup,
|
||||
a stored plug-in is skipped when no grant covers its bytes and the permissions it lists. The Additional Plug-ins
|
||||
Folder and `plugin.reload` install only plug-ins that such a grant covers, and report the others. A module without a
|
||||
that can't be removed. A `grants.json` that is not a JSON list is never overwritten, and the file is never written
|
||||
larger than 1 MiB, the most that is read. When a `Plug-ins` folder is in use, only the grants kept in it count for
|
||||
`plugin.install`, `plugin.inspect`, `plugin.reload` and the Additional Plug-ins Folder. At startup, a stored plug-in
|
||||
is skipped when no grant covers its bytes and the permissions it lists. The Additional Plug-ins Folder and
|
||||
`plugin.reload` install only plug-ins that such a grant covers, and report the others. A module without a
|
||||
`permissions` list, or with an empty one, needs no grant.
|
||||
- The host enforces the grant on a filter run and a command run: without `doc.read` the filter is given no objects; without `state` its
|
||||
`documentData` is `null` and an output that sets it is refused; without `doc.write` an output that changes the
|
||||
document is refused. A refused output leaves the document as it was.
|
||||
- The host enforces the grant on a filter run and a command run: without `doc.read` the filter is given no objects;
|
||||
without `state` its `documentData` is `null` and an output that sets it is refused; without `doc.write` an output that
|
||||
changes the document is refused. A refused output leaves the document as it was.
|
||||
- `api` is the plug-in API level the manifest is written for: `1` (the default) or `2`, which adds the [host
|
||||
imports](#host-imports-api-level-2), a filter's `inputs` ([reading type, images and
|
||||
symbols](#reading-type-images-and-symbols)) and a live effect's [`paintOutput`](#painted-live-effects). A higher one
|
||||
@@ -307,12 +303,11 @@ capability model proposed for PhotoCraft plug-ins
|
||||
- Parameter declarations are unchanged by permissions (`params`, below).
|
||||
|
||||
Every run gets a **fresh instance**, so no state survives between calls except what a plug-in writes to its
|
||||
[settings and cache](#settings-and-cache). Traps, exhausted budgets, malformed
|
||||
modules, bad manifests, out-of-range pointers, negative return codes and invalid output all become errors, and the
|
||||
document is left untouched: a filter's output is checked completely before one edit applies it. A live effect that
|
||||
fails draws the object's geometry unchanged; `plugin.info` reports why (`lastError`). A run that succeeds clears the
|
||||
error of a failed run. A check that finds every swatch and pattern a painted result names clears only the error of a
|
||||
refused swatch or pattern.
|
||||
[settings and cache](#settings-and-cache). Traps, exhausted budgets, malformed modules, bad manifests, out-of-range
|
||||
pointers, negative return codes and invalid output all become errors, and the document is left untouched: a filter's
|
||||
output is checked completely before one edit applies it. A live effect that fails draws the object's geometry unchanged;
|
||||
`plugin.info` reports why (`lastError`). A run that succeeds clears the error of a failed run. A check that finds every
|
||||
swatch and pattern a painted result names clears only the error of a refused swatch or pattern.
|
||||
|
||||
wasmi is built with its *portable* (loop) dispatcher: the default tail-call dispatcher relies on LLVM
|
||||
sibling-call optimisation, and when that doesn't happen a long-running module overflows the host stack, which aborts
|
||||
@@ -365,12 +360,12 @@ still returns `1`. `vc_progress`, `vc_cancelled` and `vc_log` need `progress`; t
|
||||
filter that lists `progress` runs in the background as a heavy one does, under its light budget, when it runs at once
|
||||
from Object › Plug-ins or its shortcut, or when OK runs it with Preview off; with Preview on, its dialog keeps the
|
||||
previewed run. A filter that also lists a `commandAllowlist` runs where it is called, since its command calls need
|
||||
the document. A plug-in that checks `vc_cancelled` can
|
||||
stop early, and the host discards what it returns after the Cancel or after its time budget ran out; one that
|
||||
ignores it is still stopped by the instruction and time budgets. With a known total, a report is passed on only when
|
||||
it reaches another tenth of a percent; with an unknown total, each change of `done` is passed on. After 2,000 reports
|
||||
in a run, one change in 1,000 is passed on, and so is the first report after the total becomes known or unknown. A
|
||||
live effect's progress is not shown, since it is drawn as part of something else.
|
||||
the document. A plug-in that checks `vc_cancelled` can stop early, and the host discards what it returns after the
|
||||
Cancel or after its time budget ran out; one that ignores it is still stopped by the instruction and time budgets.
|
||||
With a known total, a report is passed on only when it reaches another tenth of a percent; with an unknown total, each
|
||||
change of `done` is passed on. After 2,000 reports in a run, one change in 1,000 is passed on, and so is the first
|
||||
report after the total becomes known or unknown. A live effect's progress is not shown, since it is drawn as part of
|
||||
something else.
|
||||
- Log lines go to the app's log (target `vectorcraft_plugins`) as `<plug-in id>: <text>`, with control and
|
||||
bidirectional characters escaped. The whole text must be UTF-8. A line shows its first 1,024 bytes, as at most 1,024
|
||||
characters with the escapes, and ends with `…` when it is cut. A run writes at most 200 lines and 32 KiB of shown
|
||||
@@ -405,8 +400,7 @@ or modified time differs from the last read or write. A get answered from memory
|
||||
set to the value the key already has writes nothing, and neither does a delete of a key that has no value. A delete
|
||||
of the last setting removes the file and counts no written bytes. Every other successful set or delete writes the
|
||||
file before the call returns. A cache call reads or writes the whole entry in memory in one step, up to 64 MiB, and
|
||||
the run waits for it.
|
||||
A live effect runs while the art draws, so keep the entries it uses small.
|
||||
the run waits for it. A live effect runs while the art draws, so keep the entries it uses small.
|
||||
|
||||
- A key is 1 to 64 characters from `A-Z`, `a-z`, `0-9`, `_` and `-`, and is not a Windows device name (`CON`, `PRN`,
|
||||
`AUX`, `NUL`, `COM0` to `COM9`, `LPT0` to `LPT9`, in any case). Keys differ in case: `tile` and `Tile` are two.
|
||||
@@ -416,28 +410,27 @@ A live effect runs while the art draws, so keep the entries it uses small.
|
||||
also fails with `-4` when the `cache` folder holds more than 8,192 items; clearing the cache removes every file in
|
||||
it.
|
||||
- The desktop app and `vectorcraft-cli` keep them in a folder named from the id under `storage/` in the `Plug-ins`
|
||||
folder (ids that differ only in case get two folders): `settings.json` and a
|
||||
`cache` folder with one file per entry, named by the host. A settings file that is too large or doesn't parse is
|
||||
moved to `settings.unreadable.json`, replacing an earlier one there, and the settings start empty. A link in that
|
||||
folder is refused (`-5`). Writes go to a temporary file that then replaces the old one; a temporary file a failed
|
||||
write leaves in the `cache` folder does not count toward the caps, and clearing the cache removes it.
|
||||
folder (ids that differ only in case get two folders): `settings.json` and a `cache` folder with one file per entry,
|
||||
named by the host. A settings file that is too large or doesn't parse is moved to `settings.unreadable.json`,
|
||||
replacing an earlier one there, and the settings start empty. A link in that folder is refused (`-5`). Writes go to a
|
||||
temporary file that then replaces the old one; a temporary file a failed write leaves in the `cache` folder does not
|
||||
count toward the caps, and clearing the cache removes it.
|
||||
- A plug-in reaches only the storage of its own id. Replacing it with another module of the same id
|
||||
(`plugin.install` with `replace: true`, after the user agreed) keeps the storage; `plugin.remove` deletes it, and a run of the plug-in still going gets `-5` from every storage call after that.
|
||||
`plugin.remove` first renames the folder, adding `.removed` to its name (or `.removing` while a `.removed` folder
|
||||
holds settings an earlier removal kept). When that fails, `plugin.remove` fails and the plug-in stays installed with
|
||||
its storage as it was. Before VectorCraft deletes a renamed folder, it writes an empty file beside it with `.delete`
|
||||
added to the folder's name, and removes that file once the folder is gone. When that file can't be written, the
|
||||
folder is not deleted and is treated like any renamed folder without a `.delete` file (see below). Files that can't be deleted stay in the
|
||||
renamed folder with the `.delete` file beside it, where no plug-in reaches them. A later `plugin.remove` tries again
|
||||
to delete them when a plug-in with that id is installed or a storage folder for the id exists, and that deletion can
|
||||
fail too. For an id that isn't installed and has only such a folder, `plugin.remove` does not delete it and fails as
|
||||
not found. When `plugin.remove` can't remove the grant and can't rename the folder back either, its error names the
|
||||
renamed folder. The next load, install or `plugin.remove` of the plug-in renames a renamed folder that has no
|
||||
`.delete` file back while the plug-in has no folder of its own; when it has one, that folder is used and the renamed
|
||||
folder stays until `plugin.remove` deletes both.
|
||||
`plugin.remove` with the id of a plug-in that isn't installed deletes the storage kept for that id, when there is
|
||||
any.
|
||||
`plugin.clearCache` empties the cache and leaves the settings, and `plugin.list` and
|
||||
(`plugin.install` with `replace: true`, after the user agreed) keeps the storage; `plugin.remove` deletes it, and a
|
||||
run of the plug-in still going gets `-5` from every storage call after that. `plugin.remove` first renames the folder,
|
||||
adding `.removed` to its name (or `.removing` while a `.removed` folder holds settings an earlier removal kept). When
|
||||
that fails, `plugin.remove` fails and the plug-in stays installed with its storage as it was. Before VectorCraft
|
||||
deletes a renamed folder, it writes an empty file beside it with `.delete` added to the folder's name, and removes
|
||||
that file once the folder is gone. When that file can't be written, the folder is not deleted and is treated like any
|
||||
renamed folder without a `.delete` file (see below). Files that can't be deleted stay in the renamed folder with the
|
||||
`.delete` file beside it, where no plug-in reaches them. A later `plugin.remove` tries again to delete them when a
|
||||
plug-in with that id is installed or a storage folder for the id exists, and that deletion can fail too. For an id
|
||||
that isn't installed and has only such a folder, `plugin.remove` does not delete it and fails as not found. When
|
||||
`plugin.remove` can't remove the grant and can't rename the folder back either, its error names the renamed folder.
|
||||
The next load, install or `plugin.remove` of the plug-in renames a renamed folder that has no `.delete` file back
|
||||
while the plug-in has no folder of its own; when it has one, that folder is used and the renamed folder stays until
|
||||
`plugin.remove` deletes both. `plugin.remove` with the id of a plug-in that isn't installed deletes the storage kept
|
||||
for that id, when there is any. `plugin.clearCache` empties the cache and leaves the settings, and `plugin.list` and
|
||||
`plugin.info` give the cache's size as `cacheBytes`.
|
||||
- Without a `Plug-ins` folder (the web build, and embedders that attach none) the storage calls return `-5`.
|
||||
|
||||
@@ -458,8 +451,9 @@ has the id, `-6` when a plug-in may not call the command, `-7` when the command
|
||||
`-8` when the command ran and failed, `-9` when a limit below is passed, and `-11` when nothing can be called (from
|
||||
the module's start function, in a run that isn't a filter's run on a document, or after a call in the run failed).
|
||||
|
||||
- Every code also fails the run, whatever the plug-in does next, and the error names the call and why it failed. The run's calls and its output are one undo step, named after the plug-in or its command. A run that fails,
|
||||
traps, runs out of budget or is canceled leaves every document, its selection and its history as they were.
|
||||
- Every code also fails the run, whatever the plug-in does next, and the error names the call and why it failed. The
|
||||
run's calls and its output are one undo step, named after the plug-in or its command. A run that fails, traps, runs
|
||||
out of budget or is canceled leaves every document, its selection and its history as they were.
|
||||
- Each call runs on the document the run is for, with its params checked as any request's are, and sees what the calls
|
||||
before it changed: a `select.all` followed by `object.move` moves everything. The filter's own input is the one it
|
||||
was given when the run started; an edit still in progress (typing, or a shape being drawn) is kept as its own undo
|
||||
@@ -540,18 +534,17 @@ the module's start function, in a run that isn't a filter's run on a document, o
|
||||
no empty part between dots. A malformed list is refused when the module is installed, and the error names the id.
|
||||
- `params`: in dialog order. Names are `[A-Za-z0-9_]`, not starting with `_`, and not `id`, `ids`, `params` or
|
||||
`preview`. The host validates the user's values before calling: missing values take the default, numbers are
|
||||
clamped to `min..max` (ints rounded), a wrong type or an unknown choice is an error. Numeric defaults
|
||||
are also clamped to their range, including the implicit zero default of an `int` parameter. Besides `number`,
|
||||
`int`, `bool` and `choice` there are `string` (`{"type": "string", "default": "", "maxLength": 256}`: a text field;
|
||||
longer text is cut to `maxLength`, at most 4,096 characters) and `numberList` (`{"type": "numberList", "min": 0,
|
||||
"max": 360, "default": [45, 135], "minItems": 1, "maxItems": 16}`: numbers typed separated by commas or spaces,
|
||||
each clamped to `min..max`; a list of too few or too many is an error, and `maxItems` is at most 256) and `color`
|
||||
(`{"type": "color", "default": "#3a7bd5"}`: a color button that opens a color picker; the default, black when left
|
||||
out, and a value given to `plugin.run` may be a color object as paints hold one or a hex string, and the plug-in
|
||||
always gets a color object, its components clamped, such as `{"model": "rgb", "r": 0.23, "g": 0.48, "b": 0.84}`; a
|
||||
hex string and the picker's choice are display colors, given as the working RGB color that shows as them, so in a
|
||||
working RGB space other than sRGB the numbers differ from the hex). Hosts from before `color` refuse a manifest that
|
||||
uses it. A plug-in declares up to 256
|
||||
clamped to `min..max` (ints rounded), a wrong type or an unknown choice is an error. Numeric defaults are also clamped
|
||||
to their range, including the implicit zero default of an `int` parameter. Besides `number`, `int`, `bool` and
|
||||
`choice` there are `string` (`{"type": "string", "default": "", "maxLength": 256}`: a text field; longer text is cut
|
||||
to `maxLength`, at most 4,096 characters) and `numberList` (`{"type": "numberList", "min": 0, "max": 360, "default":
|
||||
[45, 135], "minItems": 1, "maxItems": 16}`: numbers typed separated by commas or spaces, each clamped to `min..max`; a
|
||||
list of too few or too many is an error, and `maxItems` is at most 256) and `color` (`{"type": "color", "default":
|
||||
"#3a7bd5"}`: a color button that opens a color picker; the default, black when left out, and a value given to
|
||||
`plugin.run` may be a color object as paints hold one or a hex string, and the plug-in always gets a color object, its
|
||||
components clamped, such as `{"model": "rgb", "r": 0.23, "g": 0.48, "b": 0.84}`; a hex string and the picker's choice
|
||||
are display colors, given as the working RGB color that shows as them, so in a working RGB space other than sRGB the
|
||||
numbers differ from the hex). Hosts from before `color` refuse a manifest that uses it. A plug-in declares up to 256
|
||||
parameters, at most 64 of them outside groups.
|
||||
|
||||
Each parameter may also say how dialogs show it. Hosts from before these keys ignore them, so a plug-in that
|
||||
@@ -576,17 +569,18 @@ parameters uses `label`, `unit`, `help` and `visibleWhen`, and lists the paramet
|
||||
order (bottom first): the selected ones and those inside selected groups and layers; hidden, locked, guide (compound
|
||||
guides too) and clipping paths are left out, and so are other kinds of object (type, images, symbols, live blends…),
|
||||
which a filter never touches unless its manifest lists them in `inputs` ([reading type, images and
|
||||
symbols](#reading-type-images-and-symbols)). With nothing selected the list is empty (a generator then adds its own objects). A live effect gets one
|
||||
object: its geometry (`type`, `path`, `fillRule`, `bounds`) and its `fills` and `strokes`, as a filter gets them (hosts
|
||||
from before these give geometry only). The paints are read only: an effect may draw as wide as a stroke or use a
|
||||
fill's colour to decide its shape, but it changes only the geometry, unless it declares `paintOutput` ([painted live
|
||||
effects](#painted-live-effects)).
|
||||
symbols](#reading-type-images-and-symbols)). With nothing selected the list is empty (a generator then adds its own
|
||||
objects). A live effect gets one object: its geometry (`type`, `path`, `fillRule`, `bounds`) and its `fills` and
|
||||
`strokes`, as a filter gets them (hosts from before these give geometry only). The paints are read only: an effect may
|
||||
draw as wide as a stroke or use a fill's colour to decide its shape, but it changes only the geometry, unless it
|
||||
declares `paintOutput` ([painted live effects](#painted-live-effects)).
|
||||
|
||||
An **exporter** gets something else: the native `.vectorcraft` document JSON without indentation, optional preview/PDF
|
||||
renditions or save-preference metadata (the format is documented in [`crates/format/src/lib.rs`](../crates/format/src/lib.rs)). That is every layer, object,
|
||||
appearance and effect record, artboard, swatch and symbol, so an exporter reads whatever the document holds; it checks
|
||||
`version`, since a later host may write a newer one. Images of 8 KiB or more are listed in `images` with their MIME
|
||||
type and `blob` but without their data, which follows the JSON in a saved file; smaller ones keep their base64 `data`.
|
||||
renditions or save-preference metadata (the format is documented in
|
||||
[`crates/format/src/lib.rs`](../crates/format/src/lib.rs)). That is every layer, object, appearance and effect record,
|
||||
artboard, swatch and symbol, so an exporter reads whatever the document holds; it checks `version`, since a later host
|
||||
may write a newer one. Images of 8 KiB or more are listed in `images` with their MIME type and `blob` but without their
|
||||
data, which follows the JSON in a saved file; smaller ones keep their base64 `data`.
|
||||
|
||||
An **importer** gets the bytes of the file being opened, as they are (not JSON), at most 128 MiB of them.
|
||||
|
||||
@@ -615,12 +609,11 @@ An **importer** gets the bytes of the file being opened, as they are (not JSON),
|
||||
(absent: no handle), `kind` `"Smooth"` when the handles stay collinear. Coordinates are points, y down.
|
||||
- `fills`, `strokes`: the object's fills and strokes in paint order (bottom first); `null` is no paint. A stroke has its
|
||||
`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"}, "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.
|
||||
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"}, "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
|
||||
`plugin.<id>` for a live-effect plug-in), `params` and `visible`. Its fills' and strokes' own effects aren't listed.
|
||||
- `bounds`: `[x0, y0, x1, y1]`, the geometric bounds (read only).
|
||||
@@ -631,9 +624,9 @@ A filter whose manifest lists `inputs` also gets the selected objects of those k
|
||||
and under the same rules (hidden and locked ones, objects that clip a clip group, alone or inside a group that clips
|
||||
it, and anything inside a symbol, a placed document or a live object are left out). They are never turned into
|
||||
outlines, rectangles or the symbol's art. Each has `id`, `type`, `name` (when it has one), `bounds` (`null` until the
|
||||
object has been laid out or drawn) and
|
||||
`transform`, `[a, b, c, d, e, f]` mapping its own space (text space, pixel space, the symbol's art) into the document.
|
||||
In this example the character and paragraph styles are cut short; a filter gets every attribute:
|
||||
object has been laid out or drawn) and `transform`, `[a, b, c, d, e, f]` mapping its own space (text space, pixel space,
|
||||
the symbol's art) into the document. In this example the character and paragraph styles are cut short; a filter gets
|
||||
every attribute:
|
||||
|
||||
```json
|
||||
{"objects": [
|
||||
@@ -662,13 +655,13 @@ In this example the character and paragraph styles are cut short; a filter gets
|
||||
`pixelsOmitted: false`. A CMYK image's pixels are its inks through the working CMYK profile, as sRGB. The pixels of
|
||||
all the images in one input take at most 16 MiB in base64, and an image the host starts to decode takes its size
|
||||
from those 16 MiB even when decoding fails. A larger image, one whose pixels don't fit what the images before
|
||||
it in paint order left, and a linked image (`linked: true`) have
|
||||
`pixelsOmitted: true` and no pixels; the host checks an image's size before it decodes the image. An image of any
|
||||
size also has `pixelsOmitted: true` when its data is missing from the document, when its width or height is 0, when
|
||||
its data cannot be decoded (including a non-CMYK image whose decoder enforces the host's 64 MiB memory limit and
|
||||
needs more, and a TIFF the host rejects as too large), or when the dimensions in its data differ from `pixelSize`.
|
||||
A plug-in checks `pixelsOmitted` before it reads `pixelsBase64`, which is present only when `pixelsOmitted` is `false`. The host never
|
||||
reads a linked image's file for a filter and never gives its path.
|
||||
it in paint order left, and a linked image (`linked: true`) have `pixelsOmitted: true` and no pixels; the host checks
|
||||
an image's size before it decodes the image. An image of any size also has `pixelsOmitted: true` when its data is
|
||||
missing from the document, when its width or height is 0, when its data cannot be decoded (including a non-CMYK image
|
||||
whose decoder enforces the host's 64 MiB memory limit and needs more, and a TIFF the host rejects as too large), or
|
||||
when the dimensions in its data differ from `pixelSize`. A plug-in checks `pixelsOmitted` before it reads
|
||||
`pixelsBase64`, which is present only when `pixelsOmitted` is `false`. The host never reads a linked image's file for
|
||||
a filter and never gives its path.
|
||||
- A symbol instance (`"symbolInstance"`): `symbolId` is the name of the symbol it shows, with `missing: true` when the
|
||||
document has no symbol of that name. `overrides` is `{}`.
|
||||
|
||||
@@ -699,16 +692,16 @@ toward the 64 MiB output cap.
|
||||
```
|
||||
|
||||
`mode` is `"filter"`, `"effect"`, `"export"`, `"import"` or `"command"` (a [command](#commands) run, whose
|
||||
`command` is the command's id); `bounds` the selection's geometric bounds (a live effect: the reference
|
||||
box of the effect, the object's bounds or those of the previous effect's result); `artboard` (filters) the artboard
|
||||
under the selection's centre, else the first; `documentData` (filters) what the plug-in last kept with the document
|
||||
(`null` until it keeps something; see Output). A live effect's `item` says where it sits: `"object"` (one of the
|
||||
object's effects), or `{"fill": i}` / `{"stroke": i}` (in the own effects of the object's `i`th fill or stroke,
|
||||
counted from the bottom as in `fills` and `strokes`). An export's `_context` is `{"mode": "export", "extension": "plt",
|
||||
"name": "Poster"}` instead: the extension of the file being written (one the exporter lists) and its name without the
|
||||
extension (the document's name when the export is not given a path), which some formats keep in their header. An
|
||||
import's `_context` is `{"mode": "import", "extension": "plt", "name": "Poster"}`: the extension of the file being
|
||||
opened and its name without the extension.
|
||||
`command` is the command's id); `bounds` the selection's geometric bounds (a live effect: the reference box of the
|
||||
effect, the object's bounds or those of the previous effect's result); `artboard` (filters) the artboard under the
|
||||
selection's centre, else the first; `documentData` (filters) what the plug-in last kept with the document (`null` until
|
||||
it keeps something; see Output). A live effect's `item` says where it sits: `"object"` (one of the object's effects), or
|
||||
`{"fill": i}` / `{"stroke": i}` (in the own effects of the object's `i`th fill or stroke, counted from the bottom as in
|
||||
`fills` and `strokes`). An export's `_context` is `{"mode": "export", "extension": "plt", "name": "Poster"}` instead:
|
||||
the extension of the file being written (one the exporter lists) and its name without the extension (the document's name
|
||||
when the export is not given a path), which some formats keep in their header. An import's `_context` is `{"mode":
|
||||
"import", "extension": "plt", "name": "Poster"}`: the extension of the file being opened and its name without the
|
||||
extension.
|
||||
|
||||
### Output
|
||||
|
||||
@@ -785,17 +778,17 @@ is a piece drawn by itself, in the order returned, with paints of its own:
|
||||
path or compound path is hit-tested on its source path and appearance. On an object with no geometry effect of its
|
||||
own, Pathfinder effects and compound shapes use the object's path for an effect on a fill or stroke, as they do for
|
||||
every other effect on a fill or stroke; on an object that has one, they also use the pieces of a painted effect on a
|
||||
fill or stroke. A 3D effect later in the stack uses the
|
||||
object's input path when the pieces name a swatch or pattern. Each piece counts against the budget of the
|
||||
object's geometry effects as well as its anchors, so all the painted effects in a stack make at most 100,000 pieces
|
||||
together; a run whose pieces would pass the budget draws its input as it was.
|
||||
fill or stroke. A 3D effect later in the stack uses the object's input path when the pieces name a swatch or pattern.
|
||||
Each piece counts against the budget of the object's geometry effects as well as its anchors, so all the painted
|
||||
effects in a stack make at most 100,000 pieces together; a run whose pieces would pass the budget draws its input as
|
||||
it was.
|
||||
- The result is checked whole before any of it is drawn: the limits of a filter's output (100,000 objects, 4,000,000
|
||||
anchors, 256 fills and 256 strokes per piece, 256 stops per gradient), finite numbers in range with nothing clamped
|
||||
(color components, tints, offsets, opacities, midpoints 0.13 to 0.87, stops in order of `offset`, stroke
|
||||
widths 0 to 1,000 pt, miter limits 1 to 500), and every swatch a solid paint, gradient or stop links to and every pattern named in
|
||||
the document. One fault refuses the whole result: that step of the stack draws its input as it was, the effect
|
||||
record stays, and `plugin.info`'s `lastError` names the plug-in and the field, for example
|
||||
`Test Tone: plug-in output: objects[1].strokes[0].width: …`.
|
||||
widths 0 to 1,000 pt, miter limits 1 to 500), and every swatch a solid paint, gradient or stop links to and every
|
||||
pattern named in the document. One fault refuses the whole result: that step of the stack draws its input as it was,
|
||||
the effect record stays, and `plugin.info`'s `lastError` names the plug-in and the field, for example `Test Tone:
|
||||
plug-in output: objects[1].strokes[0].width: …`.
|
||||
- The pieces are never written into the document while it is drawn, measured, previewed, saved or exported. PDF and SVG
|
||||
export draw them as the canvas does, from a copy of the document. Object › Expand Appearance writes each piece as an
|
||||
ordinary path or compound path with its fills and strokes, as one undo step. Object › Expand of gradient and pattern
|
||||
@@ -823,12 +816,13 @@ crates/plugins/example/build.sh # cargo build --release --target wasm32-unk
|
||||
|
||||
The script also refreshes `crates/plugins/tests/fixtures/desaturate.wasm`, the test fixture built from that source.
|
||||
The crate is not a workspace member. Any language that targets core WebAssembly works (C, Zig, AssemblyScript,
|
||||
WAT…), as long as the module imports nothing but the host imports: in Rust, `std` on `wasm32-unknown-unknown` is fine as long as it never
|
||||
touches WASI. `vectorcraft_plugins::wat::module` is the skeleton of a plug-in in WebAssembly text, which the tests
|
||||
build their plug-ins from; `wat::echo_exporter` is an exporter whose file is its input or its parameters (with a
|
||||
base64 encoder written out in WebAssembly text), which the tests read back what the host hands a plug-in with, and
|
||||
`wat::when_given` a plug-in whose output depends on whether its input or parameters hold a given text, which shows in a
|
||||
live effect's geometry. `wat::reporting` and `wat::cooperating` build level 2 plug-ins that import the host functions.
|
||||
WAT…), as long as the module imports nothing but the host imports: in Rust, `std` on `wasm32-unknown-unknown` is fine as
|
||||
long as it never touches WASI. `vectorcraft_plugins::wat::module` is the skeleton of a plug-in in WebAssembly text,
|
||||
which the tests build their plug-ins from; `wat::echo_exporter` is an exporter whose file is its input or its parameters
|
||||
(with a base64 encoder written out in WebAssembly text), which the tests read back what the host hands a plug-in with,
|
||||
and `wat::when_given` a plug-in whose output depends on whether its input or parameters hold a given text, which shows
|
||||
in a live effect's geometry. `wat::reporting` and `wat::cooperating` build level 2 plug-ins that import the host
|
||||
functions.
|
||||
|
||||
## Performance
|
||||
|
||||
|
||||
@@ -591,6 +591,8 @@ since 2026-10-01.
|
||||
- **Transform effect options (#885):** Effect › Distort & Transform › Transform… has visible Transform Objects, Transform Patterns and Scale Strokes & Effects checkboxes (`distort.transform` params `objects`, `patterns`, `strokes`; files without them draw as before). With Transform Objects off the geometry stays and makes no copies; Scale Strokes & Effects scales stroke weights, dashes and the fills' and strokes' effect distances by the mean scale, and Transform Patterns moves pattern tiles, each copy `k` by the transformation `k` times. Such objects draw, export and expand through the same per-copy art, so the canvas and Expand Appearance agree (engine `tests_transformfx`, effects `tests_transformfx`, the dialog test). A Transform effect in a fill's or stroke's own effects applies the same options to that fill or stroke: its weight and dashes, the distances of its later effects and its pattern follow the transformation, and with copies each copy is an object of its own; the canvas, export, Expand Appearance and Use Preview Bounds use the same art (effects `tests_transformfx`). Not yet: object effects after a Transform effect keep their distances. Public reference: [Scale objects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/scale-objects.html), [How to apply effects](https://helpx.adobe.com/illustrator/using/effects.html), [Distort and transform effects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html). Scores and hours unchanged.
|
||||
- **Warp Options (#1760):** Effect > Warp and existing Appearance effect links share the fifteen existing styles, original schematic previews, orientation choices and percent Bend/Distortion controls. Focused tests passed for real numeric entry and slider input, Preview/Cancel, immediate Enter/OK, mixed-style edits, target/slot/visibility preservation and Undo/Redo. Each applied style stays unchanged until an explicit Style choice. Original style-menu and live-preview captures were reviewed; their numeric values were supplied programmatically. Public references: [Reshaping with effects](https://helpx.adobe.com/illustrator/using/reshaping-objects-effects.html) and [Using effects](https://helpx.adobe.com/illustrator/using/effects.html). Warp geometry, optional Properties integration and remembered defaults are unchanged; no broader parity claim.
|
||||
- **Placed gradients follow the Warp effects:** under each of the fifteen Warp styles with a bend or a distortion, a gradient placed with the Gradient tool moves and scales from the object's bounds before the warp to its bounds after it, as under Free Distort; a stroke's gradient uses those bounds inflated by the stroke. This holds for an object's own Warp and for a fill's or stroke's own, on the canvas and in Object › Expand of the fill. In a group with a Warp effect, each path's and compound path's gradients follow its own bounds, a nested group's own fills and strokes follow the nested group's bounds, and the group's own fills and strokes follow the group's bounds. In the art Pathfinder and compound shapes take, a symbol instance's Warp moves the gradients of its symbol's art by the art's bounds, and a compound shape's Warp moves its gradients by the bounds of its area. A gradient fitted to the object still fits the warped outline, and a warp with no bend and no distortion leaves the gradient exactly in place. Existing documents with a Warp effect on art with a placed gradient draw differently: before, the gradient stayed where it was while the outline changed. How gradients follow the warp is VectorCraft's own rule; what the Warp effects do to the object is described in [Reshaping with effects](https://helpx.adobe.com/illustrator/using/reshaping-objects-effects.html) (effects `tests::warp_bends_when_the_warp_moves_the_outline`, `bake::tests::baked_warps_move_placed_gradients_from_the_bounds_to_the_warped_bounds`, `bake::tests::a_warp_with_no_bend_leaves_a_placed_gradient_exactly_in_place`, `bake::tests::a_fills_and_a_strokes_own_warp_move_its_placed_gradient_with_the_bounds`, `group::tests::a_groups_warp_moves_the_gradients_placed_on_its_paths_by_each_paths_bounds`, `group::tests::a_groups_warp_moves_its_own_a_nested_groups_and_a_compound_paths_gradients_by_their_bounds`, `group::tests::shape_art_moves_the_gradients_of_a_symbol_instances_art_and_a_compound_shape_with_a_warp`; render `objectfx::a_paths_gradient_moves_and_scales_with_its_warped_bounds`; engine `objexpand::a_placed_gradient_moved_by_a_warp_expands_as_the_canvas_draws_it`). Scores and hours unchanged.
|
||||
- **Warp as a Bézier patch per style:** each of the fifteen Warp styles bends the object's bounds as one Bézier patch whose control points the style sets from the bend and the size of the bounds; the horizontal distortion then scales each column of control points about its middle and the vertical distortion scales each row. The Warp effects and warp envelopes share the map, and content with no width or no height takes the middle of the patch along that side. New warps, the built-in graphic styles' Warps and warps opened from `.ai` files carry `version` 1 for it. Warps in `.vectorcraft` documents saved before it read as `version` 0 and keep their earlier look, and edits keep a warp's version; a `version` 0 warp saved to `.ai` opens again with `version` 1, since the file holds only its settings. The patches are VectorCraft's own rules; the styles and ranges are those of the Warp options in [Distort and transform effects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html) (doc `live::tests::warp_nets_bend_each_style_as_its_own_patch`, `warp_net_arc_edges_are_concentric_circles`, `warp_net_distortion_scales_the_columns_then_the_rows`, `warp_net_takes_any_numbers`, `warp_net_bends_a_huge_box_without_overflowing`, `warp_envelopes_bend_flat_content_as_the_middle_of_the_patch`, `warp_envelopes_write_their_map_version_only_when_it_has_one`, `style_libs::tests::builtin_warp_styles_take_the_nets_map`; effects `tests::a_warp_draws_with_the_map_its_version_names`, `tests::a_warp_bends_a_flat_box_as_the_middle_of_its_patch`, `tests::new_warps_take_the_nets_version`; engine `effectedit::warps_keep_the_map_their_version_names`, `envelope::new_warp_envelopes_take_the_net_and_saved_ones_keep_their_map`; eps `ai_editing::tests::every_effect_a_graphic_style_holds_is_read_back`, `import::ai::tests::a_warp_envelope_opens_live`, `import::ai::tests::warp_envelopes_read_every_style`, `import::ai::style::tests::a_stack_of_fills_with_effects_of_their_own_is_read_as_itself`; ui-egui `dialogs::tests_warp::warp_options_edit_keeps_each_targets_map_version`). Scores and hours unchanged.
|
||||
- **Warp effects bend images:** a Warp effect on an image, or on a group that holds one, bends the image's pixels with its frame, as the Warp effect is documented to distort bitmap images in [Reshaping with effects](https://helpx.adobe.com/illustrator/using/reshaping-objects-effects.html): after the last effect, the image is cut into 10 × 10 cells of two triangles, each a clipped piece under its own affine map. Effects after the Warp reshape only the frame, a second Warp bends the same pieces, and warps saved without a version bend the pixels with their earlier map, as they bend the frame. A placed gradient on a group that holds the image follows the group's bounds as the pieces draw them. SVG writes the image once for the pieces; PDF, EPS, `.ai` and PNG write the bent pieces. The images stay unbent when their pieces would make the art pass 2 million objects, and so does an image whose frame keeps its shape past the anchor budget. A grayscale image's fill color now colorizes the pieces of a warp or an envelope, and an image under any geometry effect keeps that color and its smoothing setting. Existing documents with a Warp effect on an image, or on a group that holds one, draw differently: before, the image showed unbent, cut by the bent frame. So do existing documents with a colorized image under an envelope or a geometry effect, or an image drawn without smoothing under a geometry effect: before, those lost the color or the setting. The cell count and the limits are VectorCraft's own (effects `tests::warped_images_stay_within_the_object_budget`, `tests::warped_images_keep_document_keys_and_hidden_images_apart`, `tests::an_image_whose_frame_passes_the_budget_stays_whole`, `tests::effects_after_a_warp_reshape_an_images_frame_and_not_its_pieces`, `tests::an_images_pixels_take_the_map_their_warps_version_names`, `tests::an_image_under_a_geometry_effect_keeps_its_color_and_smoothing`, `tests::a_nested_groups_gradient_follows_its_warped_image`; engine `warp_images::an_images_pixels_bend_with_effect_warp`, `a_warped_image_keeps_its_bend_in_svg`, `a_warped_image_keeps_its_bend_in_pdf`, `a_warped_image_keeps_its_bend_in_ai`, `a_warped_image_keeps_its_bend_in_eps`, `a_warped_image_keeps_its_bend_in_png`, `svg_writes_an_image_clipped_twice_in_a_users_group_twice`, `expand_appearance_of_a_warped_image_draws_as_the_canvas_did`, `a_colorized_image_keeps_its_color_when_warped`, `a_warp_on_a_group_bends_the_image_in_it`, `envelope_distort::a_colorized_image_keeps_its_color_in_an_envelope`). Scores and hours unchanged.
|
||||
- **Distort Appearance with Transform effect options:** with Envelope Options › Distort Appearance on, a path whose Transform effect has Scale Strokes & Effects or Transform Patterns on, or copies it with a gradient, is bent as the same per-copy art it draws and expands as outside the envelope: each copy's own stroke weight, dashes, pattern placement and gradient (effects `bake::tests`). Scores and hours unchanged.
|
||||
- **Fitted gradients turn with the Transform effect:** a gradient fitted to the object (not placed with the Gradient tool) turns, reflects and stretches with the object when its Transform effect rotates, reflects or scales it unevenly, as Object › Transform already does: the gradient is fixed to its fit on the geometry the effect takes, then follows the effect and the effects after it as a placed gradient does. Under moves and even scales it draws as a refit does, and a Transform effect that scales the object to nothing across leaves it fitted, with Scale Strokes & Effects or Transform Patterns on too, and on each copy. This holds on the canvas and in Object › Expand of the fill, for an object's own effects, a fill's or stroke's own, and a group's, where the gradients fitted to its paths, to its own fill, to the groups inside it and to its images' and symbol instances' own fills each fix to their own object's fit. Existing documents with such a Transform effect on fitted-gradient art draw differently: before, the gradient refit to the turned outline. The Gradient tool still shows a fitted gradient's vector at its unturned fit. How gradients follow the effect is VectorCraft's own rule; what the effect does to the object is described in [Distort and transform effects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html) (effects `bake::tests::a_fitted_gradient_turns_reflects_and_stretches_with_a_transform_effect`, `bake::tests::a_transform_effect_that_scales_to_nothing_leaves_a_fitted_gradient_fitted`, `group::tests::a_groups_transform_turns_the_gradients_fitted_to_its_members_and_its_own_fill`; render `objectfx::a_fitted_gradient_turns_and_reflects_with_its_transform_on_the_canvas`; engine `objexpand::a_fitted_gradient_turned_by_a_transform_expands_as_the_canvas_draws_it`). Scores and hours unchanged.
|
||||
- **Placed gradients follow Transform and Free Distort effects:** a gradient placed with the Gradient tool moves, turns and scales with the object when the object's Transform effect moves its outline, and each copy of the Transform effect takes it as many times as it takes the transform. Under Free Distort it moves and scales from the path's bounds before the effect to its bounds after it. This holds on the canvas, in export, in Expand Appearance, in Pathfinder and compound shapes, and in Object › Expand of the fill, for an object's own effects, a group's (the own fills of the group, of the groups inside it, of a symbol's art, and of the images and symbol instances it moves, included) and a fill's or stroke's own. A gradient on a path inside a group with a Transform effect now moves with the path (#2214 moved only the gradients of type), and so does a gradient on an image's or symbol instance's own fill. When a path keeps its shape because the effect would pass the geometry budget, the group's own gradients stay in place; a hidden path in a layer, which the layer's bounds leave out, does not hold them. A freeform gradient whose points were edited moves its points in the same way. How gradients follow each effect is VectorCraft's own rule; what each effect does to the object is described in [Distort and transform effects](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html) (effects `bake::tests::a_baked_transform_moves_a_placed_gradient_with_the_path`, `bake::tests::a_strokes_own_transform_and_free_distort_move_its_placed_gradient`, `bake::tests::a_fills_gradient_follows_the_effects_around_its_transform_with_options`, `bake::tests::baked_free_distort_moves_placed_gradients_from_the_bounds_to_the_distorted_bounds`, `group::tests::a_groups_transform_and_free_distort_move_the_gradients_placed_on_its_paths`, `group::tests::a_groups_transform_moves_the_gradient_of_its_own_fill`, `group::tests::a_nested_groups_own_gradient_follows_the_outer_groups_transform_and_free_distort`, `group::tests::a_groups_transform_moves_the_gradients_placed_on_an_images_own_fills`, `group::tests::shape_art_moves_gradients_with_a_transform_effect`, `group::tests::shape_art_moves_the_gradient_of_a_symbol_instances_art_with_its_transform`, `tests::art_past_the_budget_keeps_its_own_gradients_in_place`, `tests::a_hidden_path_past_the_budget_leaves_a_layers_gradients_free_to_move`, `tests::edited_freeform_points_follow_a_transform_effect_and_free_distort`; render `objectfx::a_paths_gradient_moves_with_its_transform_and_free_distort`; engine `objexpand::a_placed_gradient_moved_by_a_transform_or_free_distort_expands_as_the_canvas_draws_it`). Scores and hours unchanged.
|
||||
@@ -600,7 +602,7 @@ since 2026-10-01.
|
||||
- **Copy and Cut of anchors picked with a lent Direct Selection (#6123):** with the Pen or the Curvature tool, Cmd/Ctrl held lends Direct Selection for the gesture; the anchors and segments it picks are what Edit › Copy and Cut take once the key is let go, as with Direct Selection chosen, while the last anchor the Pen leaves selected as it draws still makes Copy take the whole path (#2122). A Cmd gesture that leaves the selection as it was (a Shift marquee over nothing) picks nothing. Regression test: `tests::draw2::pen_cmd_selected_anchors_are_what_copy_and_cut_take`.
|
||||
- **Cut of picked anchors (#6361):** Edit › Cut removes direct-selected anchors from their path and from a compound path's member, with the segments attached to them, as Clear does. Picked segments still lose only those segments. When a path also has a picked segment, Cut removes and copies only its picked segments and ignores its picked anchors, on every contour, as Clear does; an anchor left without a surviving segment is removed with them. Regression tests: `cut_*_anchors_*` and `cut_of_an_anchor_with_a_segment_on_another_contour_leaves_the_source_as_clear_does` in `crates/engine/tests/it/copy_direct_selection.rs`.
|
||||
- **Escape in Selection tool drags:** Escape during a Selection tool move or Alt-drag copy, a bounding-box scale or rotation, a marquee, or a corner widget, pie widget or bracket drag cancels it: the art returns to its state at the press, no copy is left, no undo step is added, the release does nothing, and the tool stays chosen. This is VectorCraft's own behavior. Regression tests: `escape_cancels_*` in `select/tests.rs` and `pie.rs`, and `escape_cancels_a_selection_tool_move_copy_scale_or_rotation` in `tests/selectescape.rs`.
|
||||
- **Clicks pass through raster effects:** clicks with the Selection and Direct Selection tools, and the Selection tool's pointer and marquee, leave out the area that a drop shadow, a glow, a blur or another raster effect paints beyond an object. A click on a shadow selects the object under it, or nothing over an empty canvas, and a press on the shadow of a selected object does not move it ([the help page](https://helpx.adobe.com/illustrator/using/selecting-objects.html)). No setting changes this. This covers paths, groups, type, symbol instances and images. Regression tests: `crates/engine/src/tests/select_effects.rs`.
|
||||
- **Selection passes through raster effects:** clicks with the Selection, Direct Selection and Group Selection tools (Group Selection's repeated click included), the Selection tool's pointer and marquee, Direct Selection's marquee, pointer and anchor highlight, and Group Selection's marquee (E for enclosed objects included) leave out the area that a drop shadow, a glow, a blur or another raster effect paints beyond an object. A click on a shadow selects the object under it, or nothing over an empty canvas, a repeated Group Selection click there adds the groups of the object under it, and a press on the shadow of a selected object does not move it ([the help page](https://helpx.adobe.com/illustrator/using/selecting-objects.html)). Over a shadow, Direct Selection highlights only the anchors of the object under it, and Group Selection keeps its own pointer and highlights no anchor, as it does everywhere (VectorCraft's own rule). No setting changes this. This covers paths, groups, type, symbol instances and images. Regression tests: `crates/engine/src/tests/select_effects.rs`.
|
||||
- **Global Edit: Shift-click leaves a match out:** while Global Edit is on, a Shift-click with the Selection tool on a highlighted match, or on an object inside a matched group, leaves that match out (`select.globalEditExclude`): it is no longer highlighted and later edits leave it as it is; the object the edit started from stays, and Global Edit ends when fewer than two matches are left ([Update assets quickly with Global Edit](https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html)). Regression tests: `a_shift_click_*`, `an_object_left_out_*`, `a_group_left_out_*` and `leaving_out_all_but_one_*` in `crates/engine/src/tests/globaledit.rs`.
|
||||
- **Global Edit Options in the Control bar:** while Select › Start Global Edit can run, the Control bar shows a Global Edit Options dropdown that opens the same options as the Properties panel's (Match: Appearance, Size; On Artboards; Include Objects On Canvas; Result(s)); both set the same options. The dropdown is off while Global Edit is on; it sits after Recolor Artwork, ahead of the X/Y/W/H fields ([Edit similar objects globally](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)). Regression test: `the_control_bar_shows_the_global_edit_options` in `crates/ui-egui/src/chrome/tests.rs`.
|
||||
- **Global Edit's enabled check on large paths:** the check that decides whether Select › Start Global Edit can run on the selected path, compound path or symbol instance (which the Control bar makes every frame) measures the box of its anchors and handles instead of building and normalizing a copy of every anchor, with the same answer. On a 200,000-anchor path the check takes 0.7 ms instead of 2.2 to 3.0 ms. Regression tests: `support_*` in `crates/engine/src/cmd/globaledit.rs`.
|
||||
@@ -764,6 +766,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Saves, Export for Screens and Package under `--automation-read-root`: a save without a path leaves the document's own file out of its reply, and a save without a path known leaves out the suggested folder, when they are outside the read roots; Export for Screens into an export preset's folder outside them lists its files relative to that folder and names that folder by its name in an error, and the export settings and saved presets leave out a folder outside them when read or exported; Package's report and warnings, and Document Info's linked images, name a file outside them by its file name. A path the client gives stays in the reply. `crates/engine/src/tests/save.rs`, `crates/engine/src/cmd/fileio/tests_screens.rs`, `crates/engine/src/tests/package.rs`, `crates/ui-egui/src/tests/automation.rs`.
|
||||
- `ui.inspect` under `--automation-read-root`: the documents still opening, the recent files, library panels' files and folder preferences (saved, and in an open Preferences dialog) outside the read roots are left out of the reply, a library loaded from outside them shows by its file name in `ui.inspect` and `ui.docking`, and the app's state is not changed. `crates/ui-egui/src/tests/automation.rs` (`ui_inspect_leaves_out_paths_outside_the_read_root`).
|
||||
- Link replies under `--automation-read-root`: `links.list`, `links.check`, `links.info` and `document.open`'s link rows leave out the path, found path and folder of a linked file outside the read roots, and the stored size and date of it in `links.info`; each row keeps its id, file name and status, and the document's links are not changed. `crates/engine/src/tests/linkspanel.rs` (`link_paths_outside_the_read_roots_are_left_out`), `crates/engine/src/tests/placed_document.rs` (`link_info_leaves_out_a_placed_documents_path_outside_the_read_roots`).
|
||||
- Linked-file and font-file searches under `--automation-read-root`: `links.findFiles` and `text.findFontFiles` replies, also later polls and stops of a search another client started, leave out the files found, a link's saved path and the folder searched outside the caller's read roots, and a failed search gives its reason without the path; `image.info` gives `link: null` for a linked file outside them; `text.addFontFiles` gives `folder: null` and the copies' file names when the Fonts folder is outside them. `crates/engine/src/tests/linkfiles.rs`, `crates/engine/src/tests/fontfiles.rs`, `crates/engine/tests/font_folder.rs`.
|
||||
- Swatch, brush, symbol and graphic style library lists under `--automation-read-root`: library files and the user library folder outside the read roots are left out, a library loaded from outside them is listed and found by its file name, and the app's own list is not changed. `crates/engine/src/tests/library_roots.rs`.
|
||||
- Preferences commands under `--automation-read-root`: `prefs.get`, `prefs.list`, `prefs.set` and `prefs.reset` show folder preferences outside the read roots as `null` and leave out an export preset's folder outside them, and the preferences are not changed. `crates/engine/src/tests/prefs_roots.rs`.
|
||||
- Scripts: `alert`, `confirm` and `prompt` open dialogs during a run, each with a Stop button that ends the run and puts every document back. In the app the run waits at the call for the person's answer; a run over MCP or the command line takes its answers in order from `script.run`'s `answers`, and the report lists each dialog with its answer. `crates/engine/src/tests/script_dialogs.rs`, `crates/ui-egui/src/tests/script_dialogs.rs`.
|
||||
@@ -1193,6 +1196,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
Object › Flatten Transparency, the Flattener Preview, and EPS, PDF and print output that flatten transparency leave art without transparency as it is when it overlaps only other art without transparency, unless it overlaps a member of a see-through object's group and lies above that member and below the group's topmost object. Such art joins the group, and the group takes the place of its topmost object, so the stacking order stays the same. In files without transparency with Clip Complex Regions on, art under the group within one image pixel of a member joins it too. The 1 pt antialiasing margin goes around a group's art once, however deeply its groups nest. On a grid of 3,000 rectangles with a 0.5 pt stroke, 60 of them with a drop shadow, the Flattener Preview counts 180 affected objects where it counted 3,000, and Flatten Transparency with the Medium preset makes 240 flat regions and 2 images where it made one image of the whole grid. These rules are VectorCraft's own (`tests::flatten::opaque_art_that_touches_only_opaque_art_stays`, `tests::flatten::opaque_art_the_group_would_move_under_joins_it`).
|
||||
New Symbol and Redefine Symbol on a frame of threaded text take it out of the thread with the text it shows, which the symbol's art holds, and the other frames keep the text they hold. When the last frame goes into the symbol, the text it has no room for goes after the text of the last frame that stays. When every frame of a thread goes into the symbol, the copy of the last frame keeps all its text.
|
||||
With Optical Margin Alignment on, or Roman Hanging Punctuation on in the first paragraph, the glyphs at the ends of left-to-right type on a path hang outside its brackets, measured along the path. The type hangs at the start bracket when its line is placed there, hangs at the end bracket when the line is placed there, and hangs at both when it is centered. Glyphs that hang past the end bracket do not make the type overflow, and a hang stops at an open path's end. SVG export writes such type as outlines.
|
||||
Type › Type on a Path › Center on Top and Center on Bottom (`type.pathCenter.top`, `type.pathCenter.bottom`, also on the Contextual Task Bar for type on a closed path) slide type on a closed path so the middle of its span lands on the path's topmost or bottommost point as drawn, keep the span's length, and set the first paragraph to Center unless it is Center or Justify Center. This is VectorCraft's own behavior (`crates/geom/src/arc.rs`, `crates/engine/src/tests/pathtype.rs`).
|
||||
Type on a Path Options › Align to Path (option names as in [Create type on a path](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/create-type-on-a-path.html)) moves each character by its own font, size and vertical scale: Ascender runs the top of the font's em box along the path (the ideographic em box that its `BASE` table, its typographic or `hhea` box or its ideographs give, else an em box centered on the cap height), Descender runs the bottom of the font's bounding box along it, and Center runs a line at half the height of the top of the font's H above the baseline along it. In vertical type, Ascender and Descender move the characters half an em, and Center leaves them on the path. These distances are VectorCraft's own values (`tests::on_path_align_runs_each_glyph_by_its_own_metrics`, `tests::on_path_align_finds_the_em_box_of_each_kind_of_face`, `tests::on_path_align_in_vertical_type_moves_half_an_em`).
|
||||
Type › Dynamic Text (`type.dynamicText`, also on the Contextual Task Bar for type on a path) sizes type on a path to fill the span between its start and end brackets and fits it again after each edit to the text or a bracket ([Dynamic Text overview](https://helpx.adobe.com/photoshop/desktop/text-typography/work-with-dynamic-text/dynamic-text-overview.html), [start and end points](https://helpx.adobe.com/photoshop/desktop/text-typography/work-with-dynamic-text/reposition-start-and-end-points.html)). VectorCraft's own rules: reshaping or scaling the path fits the text again; one factor scales every size, leading and baseline shift; the first character's size moves in 0.1 pt steps within 0.1 to 1,296 pt; turning it off, threading the type or Release Type on a Path keeps the drawn sizes; SVG, EPS, legacy `.ai` and the editing data write the drawn sizes (`crates/text/src/tests/fit.rs`, `crates/engine/src/tests/dynamic_text.rs`).
|
||||
Dynamic Text on area type is Block (Type › Dynamic Text, the Contextual Task Bar, Area Type Options › Block, the Properties panel's Dynamic Text checkbox with Word Gap and Line Gap; [Dynamic Text overview](https://helpx.adobe.com/photoshop/desktop/text-typography/work-with-dynamic-text/dynamic-text-overview.html)): each line as typed is set to the frame's width by its font size, again after every edit to the text or the frame. VectorCraft's own rules: lines are not broken again, the first character's size moves in 0.1 pt steps within 0.1 to 1,296 pt, Word Gap (0 to 1,000 %, default 100) is the Desired word spacing and Line Gap (-1,296 to 1,296 pt, default 0) is added to each size for the leading, the sizes are written into the characters, threaded frames cannot use it, and Convert To Point Type turns it off, keeping the sizes (`crates/engine/src/cmd/typecmd/block_tests.rs`).
|
||||
@@ -1202,7 +1206,8 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
Type › Optical Margin Alignment (`type.opticalMarginAlignment`) hangs the punctuation and letter edges at the ends of a type object's lines outside its margins: at the left of left-aligned lines, at the right of right-aligned lines, and at both ends of centered and justified lines, in point and area type of left-to-right paragraphs. Quotation marks, hyphens, full stops and commas hang wholly; asterisks, tildes, ellipses, en and em dashes, colons and semicolons hang by half; and A, V, W, Y, v, w and y hang by a tenth of their width. It is off by default and saved with the type object. Justification's Glyph Scaling leaves the glyphs that hang at their width, so they hang exactly their share, and Standard burasagari counts what hangs at a line's start as room before hanging a comma; `crates/text/src/tests/hang.rs` checks whole paragraphs with both composers, ragged, centred and justified, with Line-end Punctuation Half Width and bidirectional lines.
|
||||
Optical Margin Alignment and Roman Hanging Punctuation hang right-to-left paragraphs of point and area type, and type on a path whose first paragraph runs right to left, too. Auto aligns a right-to-left line at its right, or at the end bracket of a path, so it hangs there. A right-to-left line is read from right to left: where it hangs at its left margin, `."` ending it hangs both glyphs.
|
||||
The Paragraph panel menu's Kinsoku Shori Type (`text.setFormat {kinsokuType}`, with the East Asian options) picks Push In First, Push Out First or Push Out Only for each paragraph: a character the Kinsoku Set keeps off a line's start or end is set on the line, with the line's gaps next to CJK characters closed by at most a tenth of an em each to fit, or moved to the next line with the character before it. Push Out First pushes in only when pushing out would leave the line more than an em short. Push Out Only is the default, the setting is saved only when it is not, and paragraph styles carry it.
|
||||
The Paragraph panel menu's Reset Panel (`text.setStyle`, `text.setFormat` and `text.tabs.clear`) gives the selected type's paragraphs, or the paragraphs the Type tool's selection touches, Auto alignment, no indents or paragraph spacing, Hyphenate off with the default Hyphenation and Justification options, Auto direction, Bottom-to-Bottom Leading, the Line-end Punctuation Half Width Mojikumi Set, Regular burasagari, the Hard kinsoku set, Push Out Only, the Every-line Composer, Roman Hanging Punctuation off, no kashidas and no tab stops, as one undo step.
|
||||
The Paragraph panel menu's Korean Line Break Anywhere (`text.setFormat {koreanLineBreakAnywhere}`, with the East Asian options, and the East Asian Composition Settings section of Paragraph Style Options) sets for each paragraph whether a line may break between any two Hangul syllables. When it is off, a word that contains Hangul breaks only at its spaces and after punctuation, or where the word is wider than the line, and takes no hyphen. New type has it off; paragraphs saved before the option read as on, so their lines do not move.
|
||||
The Paragraph panel menu's Reset Panel (`text.setStyle`, `text.setFormat` and `text.tabs.clear`) gives the selected type's paragraphs, or the paragraphs the Type tool's selection touches, Auto alignment, no indents or paragraph spacing, Hyphenate off with the default Hyphenation and Justification options, Auto direction, Bottom-to-Bottom Leading, the Line-end Punctuation Half Width Mojikumi Set, Regular burasagari, the Hard kinsoku set, Push Out Only, the Every-line Composer, Roman Hanging Punctuation and Korean Line Break Anywhere off, no kashidas and no tab stops, as one undo step.
|
||||
The Paragraph panel menu's Hyphenation… (`ui.hyphenation`, `text.setFormat {wordsLongerThan, afterFirst, beforeLast, hyphenLimit, hyphenationZone, hyphenateCapitalized}`) sets each paragraph's hyphenation limits: the shortest word that breaks, the fewest letters on each side of the hyphen, the most hyphenated lines in a row, a zone at the right edge where the Single-line Composer breaks before a word instead of hyphenating it, and whether capitalized words break. The defaults are words longer than 5 letters, 3 letters on each side, no limit or zone, and capitalized words hyphenated; paragraph styles and the native file keep the options.
|
||||
PDF export with type as text writes the hyphen that ends a line broken inside a word as a hyphen character where the line draws it, and the file opens with the hyphen in the line's text; kashidas are drawn as paths.
|
||||
The Paragraph panel's Insert Kashidas (with the Indic options; `text.setFormat {kashida}`) lengthens each Arabic word of a justified horizontal area type line with a kashida, a tatweel between its last pair of joined letters, before the word spaces widen: Short, Medium and Long kashidas are at most a quarter, half and a whole em long, Stylistic ones are whole tatweels up to an em, and None (the default) leaves the line as before; paragraph styles and the native file keep the setting.
|
||||
@@ -1520,6 +1525,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- HEIC and HEIF images open and place from `.heic` and `.heif`, as the [supported file formats](https://helpx.adobe.com/illustrator/kb/supported-file-formats-illustrator.html) list High Efficiency Image under both Open and Place (and [Fix HEIF file import errors on Windows](https://helpx.adobe.com/illustrator/desktop/add-and-import-files/import-other-file-types/fix-heif-file-import-errors-on-windows.html) covers both ways of bringing HEIF files in): HEVC-coded single pictures and grids of tiles, 8 to 10 bits, with an alpha image, decoded by heic-rs in the engine's default `heif` feature. The container's rotation, mirror and crop are applied. Without an `nclx` box, the HEVC stream's video signal type gives the samples' matrix and range. An RGB ICC profile, or Display P3 with the sRGB curve named by an `nclx` box or, without a color box, by the stream, is converted to sRGB; the EXIF resolution tags size the image, or 72 ppi. Image sequences, other codings and grids of cropped tiles are refused with a message, and so is every HEIF file in a build without the feature. The decoder checks image size before allocating (`crates/engine/src/cmd/fileio/tests_heifread.rs`).
|
||||
- HP-GL/2 plot files (`.plt`, `.hpgl`, `.hgl`, `.hpg`, or a name no other format reads when the file starts with plotter instructions) open and place as paths on one layer, the artboard round the art: each line drawn with the pen down is a stroked path in its pen's color and width, line types become dash patterns, circles, arcs, wedges and Bézier curves are curves, filled and edged rectangles, wedges and polygons (polygon mode) are filled or stroked paths, and encoded polylines, scaling points, user units and rotation are read. Labels, symbol mode's symbols, raster images and hatching aren't drawn and are listed in the warnings (`crates/cad/src/hpgl/tests.rs`, `crates/engine/src/cmd/fileio/tests_hpgl.rs`).
|
||||
- Export As › HP-GL (`document.export {format: "hpgl"}`) writes a `.plt` plot of an artboard, each artboard or the art for pen plotters and cutters: each visible path once along its line in plotter units from the artboard's bottom-left corner or center, a pen per color or a pen table, curves flattened to a tolerance, type as outlines, clipping masks and the artboard's edges applied, and HP-GL or HP-GL/2 (pen colors and encoded polylines); plots read back by the HP-GL reader give the same lines (`crates/cad/src/hpgl/write/tests.rs`, `crates/engine/src/cmd/fileio/tests_hpgl.rs`).
|
||||
- HP-GL export's `insideFirst` cuts each pen's paths inside before outside: a path is drawn only after the pen's paths that lie inside it (a letter's counter, a part's holes, rings within rings), touching outlines counted as inside and crossing ones not, in paint order or nearest first with `sortPaths`, within the plot's work budget; these order rules are VectorCraft's own (`crates/cad/src/hpgl/write/nest.rs`, `crates/cad/src/hpgl/write/tests.rs`, `crates/engine/src/cmd/fileio/tests_hpgl.rs`).
|
||||
- EPS and DXF in and out, EMF/WMF in and out; PNG (with PNG-8), JPEG, WebP, GIF, TIFF, BMP, Targa and layered PSD export; grayscale TIFFs with an alpha channel or other extra channels place, relink and open, the alpha as the image's transparency (#1466 in part); PSD and PSB files open and place, linked or embedded, as their merged image in every colour mode and depth (#918). EPS import runs a PostScript Level 3 interpreter written from the PostScript Language Reference: resource categories and `resourceforall`, local and global VM (`currentglobal`/`setglobal`/`gcheck`), `clipsave`/`cliprestore`, subarrays and substrings that share storage, the standard dictionaries in `systemdict`, `dictstack`, `internaldict`, executable filters and strings, `load` by any key, `//name` immediate names, `token`, `execform`, user paths, `cshow`, `rootfont`, `cachestatus`, colour rendering and halftone queries, file `status` and data read with `flushfile`, and `pdfmark` marks dropped (Discord feedback, #466, #469, #474). Masked images (`ImageType 3` in its three interleavings) and reusable streams are read, and an image's several data sources are read in turn, as the reference defines (procedures that read one file get their own rows: such images came in banded, #498). `restore` puts dictionaries back as they were at the `save`, so programs that count their saves in a dictionary read to their end (part of #505). Tiling patterns come in as pattern swatches, uncoloured ones too; Type 3 fonts' glyphs as their outlines, spaced by `ashow`/`widthshow`/`xshow`/`kshow`; mesh and function-based shadings (types 1, 4–7) as gradient meshes; sampled functions (type 0) and Flate/LZW predictors are read. It is tested with programs written from the specification in the ways common generators use these features; Inkscape/cairo EPS files open as vectors. When a file's PostScript still can't be read, it opens as its TIFF preview, its Windows metafile preview (as vectors) or its EPSI preview, and the warning (in the status bar, `document.open`/`app.open`/MCP `open_file` replies and `vectorcraft-cli info`) names the error, the operator that raised it and the procedures it ran in. Legacy PostScript `.ai` files (Illustrator 3–8, written with the prolog that defines their operators) keep their groups and nested groups (`u` … `U`), between and inside clipping groups; EPS and EMF/WMF import nest clips (and groups) at most 128 deep, so a hostile file can't exhaust the stack. DXF TEXT justified Fit or Aligned spans its two points (stretched, or scaled as a whole). PDF export writes type as real, searchable text in embedded subset fonts by default, as Illustrator's PDF presets do (Convert Text to Outlines is an option; printer's marks stay shapes), so text-heavy PDFs stay small (#733). DXF export writes each fill as its outlines followed by its hatch, so laser and cutter software, which skips hatches, gets filled art and type too (#546).
|
||||
- TIFFs that store each channel as its own plane (PlanarConfiguration 2) place, relink and open as the same image stored pixel by pixel does, 8 and 16 bits: grayscale with an alpha channel or other extra channels, and CMYK with or without them. A planar CMYK TIFF keeps its ink amounts and is stored as a CMYK TIFF with each pixel's inks together, at the file's resolution. A TIFF whose header claims more strips or tiles than can be counted is refused as too large to draw (#1466 in part; `crates/engine/src/cmd/fileio/tests.rs`).
|
||||
- 1-bit TIFFs compressed with CCITT Group 3 (ITU-T T.4) place, relink and open: Compression 2 (modified Huffman) and Compression 3 with one-dimensional rows or, with T4Options bit 0, two-dimensional ones, with or without fill bits, in strips or tiles, with either FillOrder and either polarity. Rows in T.4's uncompressed mode are not read. A file whose pixels would take more than the decoding allowance as RGBA is refused as too large to draw (#1466 in part; `crates/engine/src/cmd/fileio/tests.rs`).
|
||||
@@ -1536,7 +1542,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- File › Place with Bounding Box (`file.place {crop: "bounding"}`) measures the art that shows, with its strokes and effects, as an export does: hidden objects, guides and template layers do not count, a group whose only part on the page or artboard is hidden is not placed, and symbol instances are measured by their art, for `at`, `rect`, the place cursor and `file.place.info` (`crates/engine/src/tests/place.rs`).
|
||||
- Separations Preview shows placed and embedded raster images on the visible plates, as it shows vector art (#4153): an RGB image's pixels are separated into process inks with the proof setup's intent, a CMYK image shows its own inks, a hidden plate's ink leaves the image, and with one plate visible the image shows that plate's ink as a grey. Raster images carry no spot ink, so a spot plate shown alone shows them as paper; a colorized image shows its color's plates. Emoji and other bitmap glyphs follow the same rule. The plate-filtered pixels and their reduced-size copies are kept apart from the image's own pixels, for each set of visible plates, proof setup and color settings, and with the preview off nothing changes. Evidence: `tests::colorize::separations_preview_shows_an_rgb_images_process_inks`, `separations_preview_shows_a_cmyk_images_own_inks`, `separations_preview_keeps_an_images_pixels_per_plate_set`. Print preview fidelity remains the open gap, so scores and hours are unchanged.
|
||||
- Desktop print transport uses `craft-print` for CUPS and modern Windows. Sheet composition, copies, separations and colour management remain in VectorCraft. The Print dialog's Media list also carries the Japanese papers — JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 128 × 182 mm beside the ISO B4/B5 (250 × 353 and 176 × 250 mm, kept so settings and presets saved before them keep their paper), and はがき 100 × 148 mm, 往復はがき 148 × 200 mm, 封筒 長形3号 120 × 235 mm, 長形4号 90 × 225 mm, 角形2号 240 × 332 mm, 角形3号 216 × 277 mm and 四六判 127 × 188 mm; the JSON id or the dialog label names a paper, any case and spacing (`B5 (JIS)` or `b5jis`) (`crates/pdf/src/print/settings.rs`, `crates/pdf/src/tests/print.rs`). The print-preview and physical-device fidelity gaps remain, so print scores and hours are unchanged.
|
||||
- A PDF, PDF-compatible `.ai` or `.ait` opened from its pages or its editing copy opens with the File Info that its XMP packet and its document information dictionary give: the author, description, keywords, copyright status, notice and URL, and the created and modified dates, the XMP packet's value where both give one; the title is not changed (`crates/pdf/src/tests/file_info.rs`, `crates/engine/src/tests/fileinfo.rs`).
|
||||
- A PDF, PDF-compatible `.ai` or `.ait` opened from its pages or its editing copy opens with the File Info that its XMP packet and its document information dictionary give: the title (`dc:title`, its `x-default` item first, else Info `Title`), the author, description, keywords, copyright status, notice and URL, and the created and modified dates, the XMP packet's value where both give one (`crates/pdf/src/tests/file_info.rs`, `crates/engine/src/tests/fileinfo.rs`). An SVG opens with the title of its root `<title>`. The title is File Info's alone (`DocMetadata::title`): the document's name stays the file's name with its extension, a file that gives no title leaves File Info with that name, and exports (PDF, SVG, PNG, EPS, `.ai`) and native saves carry it; changing the title in File Info sets the document's name as before (#3626).
|
||||
- PDF export and PDF-compatible `.ai` and `.ait` saves write the document's File Info created date as the file's creation date, or the time of writing when the document has none, and the time of writing as its modification date; the page information mark still prints the time of writing (`crates/engine/src/tests/fileinfo.rs`, `crates/pdf/src/tests.rs`, `crates/pdf/src/tests/marks.rs`).
|
||||
- PDF export and PDF-compatible `.ai` and `.ait` saves write File Info's copyright notice, URL and status to the XMP packet (`dc:rights`, `xmpRights:WebStatement`, `xmpRights:Marked`), so a PDF or `.ai` opened from its pages keeps them (`crates/pdf/src/tests/file_info.rs`, `crates/engine/src/tests/fileinfo.rs`).
|
||||
- Opening a PDF or PDF-compatible `.ai` whose XMP packet nests elements more than 256 deep no longer overflows the stack: File Info leaves the packet out and takes the document information dictionary's values (`crates/pdf/src/tests/file_info.rs`).
|
||||
@@ -1997,7 +2003,7 @@ A VectorCraft addition (it replaces a script some users ran), so no area score m
|
||||
|
||||
## Image Tools add-on: Remove Background (2026-11-11)
|
||||
|
||||
A VectorCraft addition, so no area score moves. Object › Plug-ins › Image Tools › Remove Background… (`addon.image.removeBackground`) makes the background of the selected images transparent in their own pixels, in the app with no service or model. The background is the most common edge color (nothing is cleared when the edges hold at least as many clear pixels as pixels of that color), the average of the corners that are not clear, or picked colors. Contiguous (on by default) clears it only where it reaches in from an edge; off, it is cleared everywhere. Tolerance and Softness (0 to 100, 10 by default, VectorCraft's own values) set how near a color is cleared and the band over which pixels fade out, measured as RGB distance or, with Match set to Each Channel, as the largest difference on one channel. The run is one undo step, and none is recorded when no pixel changes. The image stays an image: a PNG with alpha at its own bit depth, gray or color as it was, with its color profile and resolution, and a linked image is embedded. CMYK images, linked images that show their preview, images that would need more than the decode allowance and results with no pixel left are refused. The dialog's Preview works as Release Type on a Path's does; its preview steps decode an image once, and the decoded pixels are released when the dialog closes. Tests: `cmd::addons::image::tests` in `vectorcraft-engine` (edge, corner and picked colors, contiguous on and off, a second run, tolerance and softness by distance and by channel, 16-bit and gray images, the color profile and resolution kept, an image read at another size, linked images, refusals and bad params, and decoded pixels reused across preview steps and released at OK and Cancel), `addons::image::tests::remove_background_previews_cancels_and_runs_on_ok` in `vectorcraft-ui-egui` and `command_sweep`. Commands: `docs/mcp.md` › Image Tools add-on.
|
||||
A VectorCraft addition, so no area score moves. Object › Plug-ins › Image Tools › Remove Background… (`addon.image.removeBackground`) makes the background of the selected images transparent in their own pixels, in the app with no service or model. The background is the most common edge color (nothing is cleared when the edges hold at least as many clear pixels as pixels of that color), the average of the corners that are not clear, or picked colors. Contiguous (on by default) clears it only where it reaches in from an edge; off, it is cleared everywhere. Tolerance and Softness (0 to 100, 10 by default, VectorCraft's own values) set how near a color is cleared and the band over which pixels fade out, measured as RGB distance or, with Match set to Each Channel, as the largest difference on one channel. The run is one undo step, and none is recorded when no pixel changes. The image stays an image: a PNG with alpha at its own bit depth, gray or color as it was, with its color profile and resolution, and a linked image is embedded. CMYK images, linked images that show their preview, images that would need more than the decode allowance and results with no pixel left are refused. The dialog's Preview works as Release Type on a Path's does; its preview steps decode an image once, and the decoded pixels are released when the dialog closes. Tests: `cmd::addons::image::tests` in `vectorcraft-engine` (edge, corner and picked colors, contiguous on and off, a second run, tolerance and softness by distance and by channel, 16-bit and gray images, the color profile and resolution kept, an image read at another size, linked images, refusals and bad params, and decoded pixels reused across preview steps and released at OK and Cancel), `addons::image::tests::remove_background_previews_cancels_and_runs_on_ok` in `vectorcraft-ui-egui` and `command_sweep`. Commands: `docs/mcp.md` › Image Tools add-on. Since 2026-11-12 a selected image also offers Remove Background in the Contextual Task Bar, Properties › Quick Actions and the Control bar, where the reference app offers it (<https://community.adobe.com/announcements-651/ai-powered-image-editing-features-in-illustrator-1624061>); a click runs it at once with the defaults, as one undo step (tests `canvas::tests::the_task_bar_removes_a_selected_images_background` and `addons::image::tests::properties_and_the_control_bar_remove_the_background_at_once`).
|
||||
|
||||
## Color Effects add-on: Find Nearest Swatch (2026-11-01)
|
||||
|
||||
@@ -2067,6 +2073,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | File Info on open: an imported document's own title (an SVG's root `<title>`, a PDF's, `.ai`'s or `.ait`'s XMP `dc:title` else its Info `Title`) goes into File Info only; the document's name (tab, Save As, default export names) stays the file's name (#3626); scores and estimates unchanged. |
|
||||
| 2026-11-12 | minor | Type: kerning at the Type tool's caret at the start of a line (the text's, a paragraph's, or after a forced line break) sets the space before the character after it (`kerningBefore`), which moves it and the rest of its line; it did nothing at the text's start and kerned the invisible line break at a paragraph's (#5111). Scores and estimates unchanged. |
|
||||
| 2026-11-12 | minor | Live Paint Bucket fills faces crossed in one drag (#6373). Scores and estimates unchanged. |
|
||||
| 2026-11-11 | minor | Rulers: the zero point dragged out of the rulers' corner snaps as a drawn point does (Smart Guides, Snap to Point and the ruler guides, pixels, the grid); evidence updated; scores unchanged. |
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-12 · **Change:** minor (Ruler origin: the corner drag snaps as a drawn point does; previously minor: Live Paint Bucket fills faces crossed in one drag (#6373); previously: #6404: transform-aware Eyedropper character copying; scores and estimates unchanged; previously: Outline view: images and gradient meshes are picked on what Outline draws, so a click inside them reaches the path behind, #6437; previously minor: a middle-click on a document tab closes it, #6506; previously minor: Blob Brush: a round tip's preview is the outline of the area it adds, round at both ends, as the stroke it commits (#4343); scores and estimates unchanged; previously:; ruler double-click adds a guide at the pointer, #5239; G5: Inventory Icons built-in symbol library (four built-ins); previously minor: files that record no units open in Preferences › Units › General, #5820; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; numeric fields step on wheel hover without taking keyboard focus, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-12 · **Change:** minor (Remove Background in the Contextual Task Bar, Properties › Quick Actions and the Control bar; previously minor: Ruler origin: the corner drag snaps as a drawn point does; previously minor: Live Paint Bucket fills faces crossed in one drag (#6373); previously: #6404: transform-aware Eyedropper character copying; scores and estimates unchanged; previously: Outline view: images and gradient meshes are picked on what Outline draws, so a click inside them reaches the path behind, #6437; previously minor: a middle-click on a document tab closes it, #6506; previously minor: Blob Brush: a round tip's preview is the outline of the area it adds, round at both ends, as the stroke it commits (#4343); scores and estimates unchanged; previously:; ruler double-click adds a guide at the pointer, #5239; G5: Inventory Icons built-in symbol library (four built-ins); previously minor: files that record no units open in Preferences › Units › General, #5820; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; numeric fields step on wheel hover without taking keyboard focus, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
>
|
||||
> Vector Halftone preview update (2026-11-10): temporary dot selection marks are hidden, and the bounding box stays available (#6369).
|
||||
>
|
||||
@@ -520,6 +520,7 @@ before release (#6373). [Paint faces and edges](https://helpx.adobe.com/illustra
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-12 | minor | Remove Background for a selected image in the Contextual Task Bar, Properties › Quick Actions and the Control bar, which runs at once with its defaults. Scores and estimates unchanged. |
|
||||
| 2026-11-12 | minor | Live Paint Bucket fills faces crossed in one drag (#6373). Scores and estimates unchanged. |
|
||||
| 2026-11-11 | minor | Ruler origin: the corner drag snaps as a drawn point does, onto the art's and the artboards' anchors, centres, edges and paths, and with Snap to Point along the ruler guides; scores and estimates unchanged |
|
||||
| 2026-11-11 | minor | Eyedropper copies effective character dimensions between transformed text objects (#6404); scores and estimates unchanged. |
|
||||
|
||||
Reference in New Issue
Block a user