mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 15:21:58 +00:00
Merge main into feat/native-save-precision
EPS saves, new on main, name `precision` in their warnings as SVG, PDF and .ai saves do.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Architecture
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (shared PDF reader and writer; app output policy and editable-import adapter retained; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; desktop printing uses craft-print; Windows 7 fallback retained) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (shared PDF reader and writer; app output policy and editable-import adapter retained; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; desktop printing uses craft-print) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
How VectorCraft is built today. The design rationale lives in the local, gitignored `plan/architecture.md`;
|
||||
this file describes what is committed. Day-to-day rules (clean-room, never crash, quality gates) are in
|
||||
@@ -55,9 +55,7 @@ lists every format read or written; importers are fuzzed (`crates/engine/tests/i
|
||||
Desktop printer discovery and PDF submission use `craft-print`, pinned to an immutable
|
||||
craft-libs revision. The engine still owns sheet composition, copies, separations and colour
|
||||
management. Submission runs on the existing background worker; its receipt or refusal
|
||||
reaches the status bar, and Quit waits for pending submissions. Windows 7 retains WMI discovery
|
||||
and the installed viewer's Print/PrintTo verb;
|
||||
its target excludes the shared backend because Windows.Data.Pdf is unavailable there.
|
||||
reaches the status bar, and Quit waits for pending submissions.
|
||||
|
||||
PDF writing uses the optional `craft-pdf` writer at an immutable craft-libs revision. Shared
|
||||
configuration, metadata, font preparation and guarded serialization leave drawing, colour
|
||||
|
||||
@@ -114,14 +114,14 @@ the two can still win that race.
|
||||
| `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 |
|
||||
| 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`), 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?, background?, antiAlias?, interlaced?, quality?}` | `ppi` sets the pixel size (72 = one pixel per point) and is stored in the file; `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` |
|
||||
| `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` |
|
||||
| `app.export` JPEG options | `{path?, format: jpg, quality?: 0–100, colorModel?: rgb \| cmyk \| gray, method?: baseline \| optimized \| progressive, scans?: 3–5, embedIcc?, imageMap?: none \| client \| server}` | `cmyk` writes ink amounts in the working CMYK space (CMYK colours keep their inks; RGB ones are separated with the colour settings); `embedIcc` (default true) embeds sRGB, the working CMYK profile or a grey profile, generated by the CMS; `imageMap` also writes `<stem>.html` (client-side `<map>`) or `<stem>.map` (NCSA) beside the image for the objects with a URL and an Image Map shape (`attributes.set`), returned in `linked`. The `jpgOptions` dialog has these fields too (`quality` is shown 0–10; a CMYK document defaults to `cmyk`) |
|
||||
| `app.export` PNG-8 and GIF | `{path?, format: png8 \| gif, colors?: 2–256, reduction?: perceptual \| selective \| adaptive \| web \| blackWhite \| gray, dither?: none \| diffusion \| pattern \| noise, ditherAmount?: 0–100, transparency?, matte?, interlaced?, …raster options}` | a palette of at most `colors` entries (art with that many colours or fewer keeps them exactly); pixels under half opacity become one transparent entry unless `transparency` is false, partly transparent edges are blended over `matte` (`none` keeps their colour). `png8` writes an indexed `.png` (1–8 bits per pixel); Export for Screens rows take both. The `png8Options` / `gifOptions` dialogs have these fields |
|
||||
| `app.export` Text and WebP | `{path?, format: txt, encoding?: utf8 \| utf16, lineEndings?: lf \| crlf, selectionOnly?}` / `{format: webp, lossless?, quality?}` | Text writes the stories in stacking order (back to front; a thread once; hidden objects and template layers left out), each line ended by `lineEndings`; `utf16` is little-endian with a byte order mark. Export As → Text opens `txtOptions` (fields `path`, `encoding`, `lineEndings`, `selectionOnly`; OK writes the file). WebP is always lossless: `lossless: false` adds a warning |
|
||||
| `engine.execute` `document.exportForOffice` | `{}` / `{path?, ppi?: 72 \| 150 \| 300, transparent?, artboard?}` | File → Save for Office Documents…: without params opens `saveForOffice` (fields `ppi`, `transparent`, `artboard`; OK picks the file); with params writes the artboard as a PNG (`path`, else a picked file) → `{path}`, and the status bar shows the first of the export's `warnings` as Save As shows a save's notes. Headless it returns `{dataBase64, width, height, warnings}` |
|
||||
| Native file dialogs | any command with no `path` that needs one (`file.open`, `file.saveAs`, `file.place`, exports…) | on macOS (a sheet on the window) and Linux the dialog doesn't hold the window: the call returns `"cancelled"` at once, `ui.inspect` reports `fileDialog`, and when the person at the computer picks a file the command runs again on the document it was asked for. While it is open another dialog is refused: that call returns `"cancelled"` too, and the status bar (`ui.inspect` → `ui.status`) says `"A file dialog is open: choose there first"`; on macOS `app.quit`, `file.close` and `file.closeAll` return that message and the menu bar does nothing (#592, #867). On Windows the dialog is shown in line: the call returns once it closes. Pass `path` to skip the dialog |
|
||||
| Background Save / Export | `app.save`, `app.export {path}`, `file.save`, `file.saveAs`, `file.saveCopy`, `file.exportForScreens {folder, …}` | with the `backgroundSave` / `backgroundExport` preferences on (the default) the desktop app writes the file from a snapshot on a worker thread: the call returns `{path, background: true, status}` at once (`file.exportForScreens`: `{background: true, status}`, and the files are written into the folder from a snapshot taken at the call), `ui.inspect` lists the jobs still running in `background` (labels such as `"Saving a.vectorcraft"`), and the document counts as saved (as of the snapshot) once its file is written. Quit and Close wait for them. A job that fails reports the error in the status bar only (`ui.inspect` → `ui.status`), a path outside `--automation-write-root` included. With the preferences off (`prefs.set`), saves return once written. `app.save {path?, wait: true}` and `app.export {path, wait: true}` wait for the jobs already running, write the file before they reply whatever the preferences are set to, and return its result or the error. When the save asks first (the question before replacing a file that opened with losses, or on macOS and Linux the file dialog for an untitled document without `path`), the reply is `{pending}` or `"cancelled"` as before, and the save that runs after the answer uses the preferences. A `wait` that is not `true`, `false` or `null` is refused with "`wait` must be true or false", and nothing is written |
|
||||
| `engine.execute` `file.exportForScreens` | `{}` / `{…document.exportForScreens params}` | without params opens `exportForScreens` on the settings the document last exported with (`document.exportSettings`). While the Artboard tool is in use, the artboards it has selected (its `selected` option, the rows the Artboards panel highlights) are checked instead, and `select` is `range`, or `all` when every artboard is selected. Fields: `tab` (`artboards` \| `assets` \| `layers` \| `selection`; OK reads Export Layer or Export Selection on those), `layers` ([layer id…] checked), `selectionMode` (`one` \| `each`), `name` (the file name pattern of `document.exportForScreens`), `counterStart`, `folders` (`none` \| `rows` \| `format` \| `item` \| `itemFormat`), `exportPreset` (the preset applied last), `__savePreset` (the name typed to save one), `select` (`all` \| `range` \| `full`), `range` ("1-3, 5"), `boards` (a bool per artboard; checking them sets `select` and `range`), `includeBleed`, `folder` (desktop; default the Desktop, else home), `openLocation`, `subfolders`, `prefix`, `preset` (`""` \| `mobile` \| `density`), `formats` (rows `{format, scale: "2x" \| "100w" \| "100h" \| "72ppi", suffix, quality?, folder?}`), `settings` (`{png \| png8 \| jpg \| webp \| gif \| svg \| pdf: {…options}}`, the gear's Format Settings; `__settings` names the format shown). `ui.dialog.confirm` exports (a bad range or no folder keeps it open); on the web OK reads "Download" and the files download (several as one `.zip`). With params it runs `document.exportForScreens`: no `folder` returns the files (or with `zip: true` one store-only zip) as `dataBase64`; with `openLocation` the first file is shown in the file manager. When `warnings` is not empty, an export into a folder or a download shows the first in the status bar as Save As shows a save's notes |
|
||||
| `engine.execute` `file.exportForScreens` | `{}` / `{…document.exportForScreens params}` | without params opens `exportForScreens` on the settings the document last exported with (`document.exportSettings`). While the Artboard tool is in use, the artboards it has selected (its `selected` option, the rows the Artboards panel highlights) are checked instead, and `select` is `range`, or `all` when every artboard is selected. Fields: `tab` (`artboards` \| `assets` \| `layers` \| `selection`; OK reads Export Layer or Export Selection on those), `layers` ([layer id…] checked), `selectionMode` (`one` \| `each`), `name` (the file name pattern of `document.exportForScreens`), `counterStart`, `folders` (`none` \| `rows` \| `format` \| `item` \| `itemFormat`), `exportPreset` (the preset applied last), `__savePreset` (the name typed to save one), `select` (`all` \| `range` \| `full`), `range` ("1-3, 5"), `boards` (a bool per artboard; checking them sets `select` and `range`), `includeBleed`, `folder` (desktop; default the Desktop, else home), `openLocation`, `subfolders`, `prefix`, `preset` (`""` \| `mobile` \| `density`), `formats` (rows `{format, scale: "2x" \| "100w" \| "100h" \| "72ppi", suffix, quality?, folder?}`), `settings` (`{png \| png8 \| jpg \| webp \| gif \| svg \| pdf: {…options}}`, the gear's Format Settings; `__settings` names the format shown). `ui.dialog.confirm` exports (a bad range or no folder keeps it open); on the web OK reads "Download" and the files download (several as one `.zip`). With params it runs `document.exportForScreens`: no `folder` returns the files (or with `zip: true` one zip) as `dataBase64`; with `openLocation` the first file is shown in the file manager. When `warnings` is not empty, an export into a folder or a download shows the first in the status bar as Save As shows a save's notes |
|
||||
| Export for Screens' Assets tab, Asset Export panel | `ui.dialog.set` `tab: "assets"`, `assets: [asset id…]`; `window.panel {panel: "assetExport"}` | on the Assets tab of `exportForScreens` the `assets` field lists the checked asset ids (all of them when it opens); `ui.dialog.confirm` runs `document.exportForScreens {assets}` (nothing checked keeps it open) and OK reads "Export Asset". The Asset Export panel acts through `assets.*` and shares the format rows with the dialog (`assets.settings.set`) |
|
||||
| `engine.execute` `file.exportSelection` | `{multiple?}` | File › Export Selection…: collects the selected objects as one asset (`assets.add`; one per object with `multiple: true`) and opens `exportForScreens` on its Assets tab with just them checked → `{assets}`. `document.exportSelection` still exports the selection in one go, without the dialog |
|
||||
| CSS Properties panel | `window.panel {panel: "cssProperties"}`; `engine.execute` `css.copy` / `css.exportFile` | the panel shows `css.selection` of the selection (after Generate CSS, while nothing is selected, `css.generate`); its menu holds the options the commands take (`units`, `position`, `dimensions`, `unnamed`, `rasterize`). `css.copy {scope?, …options}` copies the CSS to the clipboard → `{css, rules}`; `css.exportFile {path?, scope?, …options}` writes `css.export` to `path` (else a picked file; the web downloads it) with the pictures of rasterized art next to it → `{path, rules, images, warnings}`, and the status bar shows the first of `warnings` as Export As shows an export's notes |
|
||||
@@ -799,12 +799,12 @@ events take `pressure` (0..1, default 1): it is the Liquify intensity while Use
|
||||
|
||||
Freehand Tool Options: double-clicking the Pencil, Paintbrush, Smooth, Blob Brush or Eraser tool
|
||||
(`tool.options {tool: "pencil"}`) opens a `freehandOptions` dialog. Its fields are `tool` and the options the tool
|
||||
keeps: `fidelity` (pt; Pencil, Paintbrush, Smooth), `fill` (Pencil, Paintbrush), `keepSelected` (Pencil, Paintbrush,
|
||||
Blob Brush; on by default for the Pencil and Paintbrush and off for the Blob Brush, and while it is off the path a
|
||||
stroke draws, continues or merges into is not selected afterwards), `mergeOnlySelection` (Blob Brush; off by default,
|
||||
and while it is on a stroke merges only with selected blobs), `closeWithin` and `editWithin` (screen pixels, 0 turns
|
||||
it off; Pencil, Paintbrush), `size` (pt; Blob Brush, Eraser), and `angle` (°, −180 to 180; 0) and `roundness`
|
||||
(%, 1 to 100; 100) of the Eraser's tip (Eraser). `ui.dialog.confirm` runs
|
||||
keeps: `fidelity` (pt; Pencil, Paintbrush, Smooth, Blob Brush), `fill` (Pencil, Paintbrush), `keepSelected` (Pencil,
|
||||
Paintbrush, Blob Brush; on by default for the Pencil and Paintbrush and off for the Blob Brush, and while it is off
|
||||
the path a stroke draws, continues or merges into is not selected afterwards), `mergeOnlySelection` (Blob Brush;
|
||||
off by default, and while it is on a stroke merges only with selected blobs), `closeWithin` and `editWithin`
|
||||
(screen pixels, 0 turns it off; Pencil, Paintbrush), `size` (pt; Blob Brush, Eraser), and `angle`
|
||||
(°, −180 to 180; 0) and `roundness` (%, 1 to 100; 100) of the Eraser's tip (Eraser). `ui.dialog.confirm` runs
|
||||
`tool.setOption {tool, values}`.
|
||||
|
||||
`ui.pointer` events also take `holdMs` (0..60000): the pointer then holds still that long, button down, before the
|
||||
|
||||
+5
-4
@@ -238,10 +238,10 @@ international Spanish; every `es-*` locale such as `es-ES`, `es-MX`, `es-AR` or
|
||||
complete; `fr-FR`, `fr-BE`, `fr-CA`, `fr-CH` and every other `fr-*` locale resolve to it), Italian (`it`,
|
||||
complete; `it-IT`, `it-CH` and every other `it-*` locale resolve to it), Russian (`ru`, complete; every `ru-*`
|
||||
locale such as `ru-RU`, `ru-BY` or `ru-KZ` resolves to it), Ukrainian (`uk`, complete; `uk-UA`,
|
||||
`uk_UA.UTF-8` and every other `uk-*` locale resolve to it; integer plurals use one/few/many), Hungarian (`hu`, complete;
|
||||
`hu-HU` and every other `hu-*` locale resolve to it; a count takes the singular, so plurals have one form), Polish (`pl`, complete; `pl-PL` and every other `pl-*`
|
||||
`uk_UA.UTF-8` and every other `uk-*` locale resolves to it; integer plurals use one/few/many), Hungarian (`hu`, complete;
|
||||
`hu-HU` and every other `hu-*` locale resolves to it; a count takes the singular, so plurals have one form), Polish (`pl`, complete; `pl-PL` and every other `pl-*`
|
||||
locale resolve to it; plurals use one/few/many, with 21, 31 … taking many), Swedish (`sv`, complete; `sv-SE`, `sv-FI` and every
|
||||
other `sv-*` locale resolve to it), Georgian (`ka`, complete; `ka-GE` and `ka_GE.UTF-8` resolve to it; one count-neutral plural form), Czech (`cs`, every menu label) and Brazilian Portuguese
|
||||
other `sv-*` locale resolve to it), Georgian (`ka`, complete; `ka-GE` and `ka_GE.UTF-8` resolve to it; one count-neutral plural form), Catalan (`ca`, complete; `ca-ES`, `ca-AD`, `ca-FR`, `ca-IT` and every other `ca-*` locale resolve to it; one/other plurals), Czech (`cs`, every menu label) and Brazilian Portuguese
|
||||
(`pt-br`, complete; `pt-BR`, `pt_BR.UTF-8` and other tags that resolve to `pt-br` through `candidates`). Untranslated text falls back to English until its rows are added.
|
||||
|
||||
- `tl!("…")` translates a literal into the language the UI is drawn in; `i18n::t(s)` is the same for a
|
||||
@@ -277,7 +277,7 @@ other `sv-*` locale resolve to it), Georgian (`ka`, complete; `ka-GE` and `ka_GE
|
||||
control channel, MCP and tests read them) and are translated only where the status bar draws them
|
||||
(`i18n::msg`). `@msg` catalog rows hold a whole message or a template such as
|
||||
`Couldn't open {name}: {e}`; `{_1}`, `{_2}` … stand for the format string's `{}`, and the values in the
|
||||
placeholders are translated in turn (the reason after `: {e}` is often a message too). Czech, German, Greek, Spanish, French, Georgian, Hungarian, Italian, Japanese, Korean, Polish, Brazilian Portuguese, Russian, Simplified Chinese, Traditional Chinese, Swedish, Ukrainian, Vietnamese, Finnish and Turkish
|
||||
placeholders are translated in turn (the reason after `: {e}` is often a message too). Czech, German, Greek, Spanish, French, Georgian, Hungarian, Italian, Japanese, Korean, Polish, Brazilian Portuguese, Russian, Simplified Chinese, Traditional Chinese, Swedish, Ukrainian, Vietnamese, Finnish, Turkish and Catalan
|
||||
translate status and error messages (`COMPLETE_MESSAGES` in `i18n::tests`); the untranslated report lists the
|
||||
message literals the scan finds that one of them has no row for. The scan reads `Err("…")`, `Other(…)`,
|
||||
`#[error(…)]` and `status(…)` messages, messages given to an error enum's variant (`PsError::Limit(…)`, the reason
|
||||
@@ -492,6 +492,7 @@ Scores and hour estimates are unchanged.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-28 | minor | Localisation: Catalan (`ca`) catalog and registry entry (every `ca-*` locale); complete under the catalog tests. |
|
||||
| 2026-10-25 | minor | Linux Wayland drag-and-drop note names eframe 0.37 (still on winit 0.30); behavior unchanged. |
|
||||
| 2026-10-23 | minor | Tests: `cargo xtask test` (nextest, then doctests), one integration-test binary per crate and the files that keep their own, the dev profile's debug info. |
|
||||
| 2026-10-21 | minor | Fonts › Leaving out the bundled fonts: vectorcraft-ui-egui's `interface-fonts` feature, and `theme::install_fonts` for an embedder that draws the panels without `VectorcraftApp::logic`. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Effects parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** minor (Pathfinder › Trap: the Pathfinder panel's Trap… and Effect › Pathfinder › Trap… add an overprinting strip of the lighter colour where two solid colours meet; recounted with Hard Mix (#3061) and Soft Mix already in the catalogue: Pathfinder 13/13, Illustrator effects 49/50; previously: Transform effect: Transform Objects, Transform Patterns and Scale Strokes & Effects, #885; G2: the Effect Gallery shipped (#2883): Effect › Effect Gallery… browses the Artistic, Brush Strokes, Distort, Sketch, Stylize and Texture filters, edits each filter's settings, builds an ordered stack (add, reorder, hide, remove) with a live canvas preview, and OK applies it as separate appearance effects in one undo step; remaining: filter thumbnails and a preview area inside the dialog; G2 estimate 4–6 → 1–2 h; Raster effects 82% → 85% (power-user view 49% → 52%), estimated, a modest move because the named missing dialog landed without thumbnails or an in-dialog preview; previously:; stacked geometry effects make at most 2 million anchors of a path and about 8 million lines to draw; Roughen, Zig Zag, Free Distort, Twist and the warps build within it, #3578; scores unchanged; G2: Effect › Sketch › Conté Crayon (Foreground Level, Background Level, Texture, Scaling, Relief, Light Direction, Invert; a black crayon in the dark areas and a white one in the light areas on textured mid-grey paper; ranges and defaults unverified VectorCraft choices); measured Photoshop-style filters 57/57, the Effect Gallery remains (4–6 h); 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; G2: Effect › Sketch › Note Paper; Halftone Pattern and Note Paper defaults documented as unverified VectorCraft choices; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 46/57, 11 remaining (8–14 h); G2: Sketch › Chrome; Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap, #2972, #2967, #2976, #3034: 37/57 Photoshop effects; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Zig Zag's Smooth points make a continuous wave, #2343; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 46/57, 11 remaining (8–14 h); Round Corners also rounds corners where a curve meets a line or another curve, with Live Corners' cut; straight corners unchanged; score unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; wide shadows and glows at high zoom, #1706; stacked Transform effects, #1705; 3D on nonzero compound paths, #1724; pattern tiles redraw, #1723; scores unchanged; Roughen's Smooth turn-backs, #1643; Scribble per region, #1648; score unchanged; Warp Arc bends in the object's proportions, #1578; Zig Zag ridges evenly spaced, #1558; effect parameters checked, #1592; score unchanged; raster effects after a pattern, radial or freeform gradient fill export at their place and size, #1536; score unchanged; 3D and Materials on outlined type and other shapes of many small curves, #1481; Twist 0° to 360°, #1524; twisted walls keep their faces, #1525; score unchanged; 3D and Materials: Inflate takes Twist, Taper and Cap; score unchanged; 3D and Materials: Perspective as the documented 0°–160° angle; 3D and Materials: the documented light settings) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-28 · **Change:** minor (Drop Shadow Darkness uses fixed RGB subtraction and CMYK black addition; scores unchanged; Pathfinder › Trap: the Pathfinder panel's Trap… and Effect › Pathfinder › Trap… add an overprinting strip of the lighter colour where two solid colours meet; recounted with Hard Mix (#3061) and Soft Mix already in the catalogue: Pathfinder 13/13, Illustrator effects 49/50; previously: Transform effect: Transform Objects, Transform Patterns and Scale Strokes & Effects, #885; G2: the Effect Gallery shipped (#2883): Effect › Effect Gallery… browses the Artistic, Brush Strokes, Distort, Sketch, Stylize and Texture filters, edits each filter's settings, builds an ordered stack (add, reorder, hide, remove) with a live canvas preview, and OK applies it as separate appearance effects in one undo step; remaining: filter thumbnails and a preview area inside the dialog; G2 estimate 4–6 → 1–2 h; Raster effects 82% → 85% (power-user view 49% → 52%), estimated, a modest move because the named missing dialog landed without thumbnails or an in-dialog preview; previously:; stacked geometry effects make at most 2 million anchors of a path and about 8 million lines to draw; Roughen, Zig Zag, Free Distort, Twist and the warps build within it, #3578; scores unchanged; G2: Effect › Sketch › Conté Crayon (Foreground Level, Background Level, Texture, Scaling, Relief, Light Direction, Invert; a black crayon in the dark areas and a white one in the light areas on textured mid-grey paper; ranges and defaults unverified VectorCraft choices); measured Photoshop-style filters 57/57, the Effect Gallery remains (4–6 h); 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; G2: Effect › Sketch › Note Paper; Halftone Pattern and Note Paper defaults documented as unverified VectorCraft choices; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 46/57, 11 remaining (8–14 h); G2: Sketch › Chrome; Sketch › Chalk & Charcoal, Graphic Pen and Reticulation and Artistic › Plastic Wrap, #2972, #2967, #2976, #3034: 37/57 Photoshop effects; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Zig Zag's Smooth points make a continuous wave, #2343; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 46/57, 11 remaining (8–14 h); Round Corners also rounds corners where a curve meets a line or another curve, with Live Corners' cut; straight corners unchanged; score unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; wide shadows and glows at high zoom, #1706; stacked Transform effects, #1705; 3D on nonzero compound paths, #1724; pattern tiles redraw, #1723; scores unchanged; Roughen's Smooth turn-backs, #1643; Scribble per region, #1648; score unchanged; Warp Arc bends in the object's proportions, #1578; Zig Zag ridges evenly spaced, #1558; effect parameters checked, #1592; score unchanged; raster effects after a pattern, radial or freeform gradient fill export at their place and size, #1536; score unchanged; 3D and Materials on outlined type and other shapes of many small curves, #1481; Twist 0° to 360°, #1524; twisted walls keep their faces, #1525; score unchanged; 3D and Materials: Inflate takes Twist, Taper and Cap; score unchanged; 3D and Materials: Perspective as the documented 0°–160° angle; 3D and Materials: the documented light settings) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
Every entry of Illustrator's Effect menu, by its public feature name, against VectorCraft's effects catalogue
|
||||
(`effect_catalog()` in `crates/effects/src/lib.rs`) and the Effect menu built from it
|
||||
@@ -226,3 +226,4 @@ angular cut caps, exact visibility, 3D Classic, Turntable, 3D export) is 25–45
|
||||
| 2026-10-11 | minor | Brush Strokes landed (8 filters): 28/57 Photoshop effects |
|
||||
| 2026-10-10 | minor | Initial Revolve landed (#846) |
|
||||
| 2026-10-10 | major | First checklist, counted from the effects catalogue (20/57 Photoshop effects, 44/50 Illustrator effects) |
|
||||
| 2026-10-28 | minor | Drop Shadow Darkness subtracts the normalized amount from RGB source channels or adds it to CMYK/Gray black before profile conversion and blur; near-100% becomes gray black, with shadow opacity separate. SVG keeps live filters for uniform sRGB sources and renders shadows for other RGB profiles, source models and gradients. Gradient-stop adjustment is an application rule; spot/custom colors and composite-source parity remain open. Scores and estimates unchanged. |
|
||||
|
||||
@@ -167,11 +167,6 @@ Root workspace (`Cargo.toml`):
|
||||
`6d518fc7…`, `winit` to `storytold/winit` rev `a517b384…`, and `hayro-*`/`krilla` to craft-libs rev
|
||||
`a0916808…`. The hayro/krilla patches are unrelated to egui and must remain unless a separate comparison proves
|
||||
replacement.
|
||||
- `vendor/windows-link` is the only vendored package. `vendor/windows-link/VECTORCRAFT-PATCH.md` records the
|
||||
provenance (Microsoft windows-link 0.2.1, crates.io, MIT OR Apache-2.0), the sole change (`CoTaskMemFree` from
|
||||
`ole32.dll` on the Windows 7 target), and the removal condition. It is applied only by
|
||||
`packaging/windows/windows7.ps1` through `--config "patch.crates-io.windows-link.path='vendor/windows-link'"`.
|
||||
**Keep it.** Do not add a root path patch for it, and re-run the Windows 7 packaging check after the egui bump.
|
||||
- Nested workspaces: `crates/affinity/fuzz` (libfuzzer only; no egui) and `crates/plugins/example` (wasm plug-in
|
||||
example; no egui). `crates/plugins/example/Cargo.lock` is separate and must not acquire egui.
|
||||
- `crates/affinity/fuzz` has no lockfile in the checkout. Do not create one merely for this migration.
|
||||
@@ -181,7 +176,7 @@ Manifests that participate in the egui graph (the table records the plan-time de
|
||||
| Manifest | Plan-time UI declaration | Required change |
|
||||
|---|---|---|
|
||||
| `crates/ui-egui/Cargo.toml` | `egui` workspace + `bytemuck`; `egui_extras`; dev `egui_kittest` `wgpu`,`snapshot` | `egui` must also enable `default_fonts` for library tests/embedders and for downstream kittest tests, which do not inherit kittest's own dev-dependency fonts; `egui_extras`/`egui_kittest` to 0.37; no feature rename assumed |
|
||||
| `apps/vectorcraft/Cargo.toml` | `eframe` features `default_fonts`, `wayland`, `x11`; default features `wgpu`, `accessibility`; `windows7 = ["eframe/glow"]`; direct `winit 0.30` | add **`eframe/clipboard`**; keep `links` off (existing native `open_url` service); keep `system_fonts` off; retain winit feature set |
|
||||
| `apps/vectorcraft/Cargo.toml` | `eframe` features `default_fonts`, `wayland`, `x11`; default features `wgpu`, `accessibility`; direct `winit 0.30` | add **`eframe/clipboard`**; keep `links` off (existing native `open_url` service); keep `system_fonts` off; retain winit feature set |
|
||||
| `apps/vectorcraft-web/Cargo.toml` | wasm `eframe` features `default_fonts`, `wgpu`, `accesskit` | keep; add `clipboard` only if the chosen eframe 0.37 web path needs the feature (the web clipboard code is not gated by it, but test it); web `open_url` is handled by the web runner |
|
||||
| `craft-ui` / `craft-window` pins | 0.36-era craft-libs revisions | final L2 craft-libs revision on `3832c47…` with workspace `egui = "0.37"` and the CL-2F capture helper |
|
||||
| `Cargo.lock` | one coherent 0.36 graph | one coherent 0.37 graph; no second `egui`, `eframe`, `egui-winit`, `egui-wgpu`, `epaint`/`emath`/`ecolor`, or `winit`; external `kittest`/`accesskit*` are separate audited registry identities |
|
||||
@@ -196,7 +191,7 @@ never against a candidate, an intermediate fork stage or a provisional craft-lib
|
||||
|
||||
| Target | Path | Current role |
|
||||
|---|---|---|
|
||||
| `vectorcraft` binary | `apps/vectorcraft/src/main.rs` | Native eframe/wgpu app, control server, macOS menu, Linux stylus/XWayland, Windows stylus, printing, Windows 7 glow fallback |
|
||||
| `vectorcraft` binary | `apps/vectorcraft/src/main.rs` | Native eframe/wgpu app, control server, macOS menu, Linux stylus/XWayland, Windows stylus, printing |
|
||||
| `vectorcraft-web` binary | `apps/vectorcraft-web/src/main.rs`, `web.rs` | wasm eframe web runner, WebGPU/WebGL2, DOM paste/drop, `window.vectorcraft` control API |
|
||||
| `vectorcraft-cli` binary | `apps/vectorcraft-cli/src/main.rs`, `perf.rs` | Headless run/convert/bench/perf and MCP stdio |
|
||||
| `vectorcraft-ui-egui` library | `crates/ui-egui/src/lib.rs` | All frontend state, panels, dialogs, canvas, control methods, i18n, clipboard, fonts, workers |
|
||||
@@ -424,8 +419,7 @@ Required design (shared weak completion slot, per the suite capture contract):
|
||||
|
||||
Required design:
|
||||
|
||||
- Add `eframe/clipboard` to the native eframe features (unconditionally, so `--no-default-features --features
|
||||
windows7` keeps it). Keep the app's `SystemClipboard` as the multi-format owner for Copy/Cut (SVG/PDF/PNG/text)
|
||||
- Add `eframe/clipboard` to the native eframe features (unconditionally). Keep the app's `SystemClipboard` as the multi-format owner for Copy/Cut (SVG/PDF/PNG/text)
|
||||
and for image/PDF/EPS/SVG paste.
|
||||
- Consume `Event::PasteImage` in `shortcuts::handle` rather than letting it reach a `TextEdit` or the Type tool:
|
||||
- with a `system_clipboard`, treat it as the textless paste signal and let `adopt_system_clipboard` read the
|
||||
@@ -639,7 +633,6 @@ Source-grounded from the 0.37 changelog and code, with tests rather than assumpt
|
||||
- default style tweaks (roomier buttons, larger corners); app sets most tokens but screenshots will move.
|
||||
- text tessellation (#8731) and DX12 FXC shader compile (#8736) are upstream reports, not reproduced here. Measure
|
||||
our own fixtures before changing anything; preserve Repeat/MirroredRepeat semantics and color emoji.
|
||||
- `eframe` now falls back to a compatibility-profile OpenGL context; Windows 7/glow path must be re-tested.
|
||||
- #8748 (hidden-parent deferred children) concerns child/deferred viewports. P7c owns the hidden-parent/child fix
|
||||
and its regression. VectorCraft has no child viewport, so it verifies only the root window's
|
||||
occluded/minimized logic-only path and that any `Area`/`Window` owned by the root does not assume a parent
|
||||
@@ -674,8 +667,7 @@ One coherent 0.37 family:
|
||||
```
|
||||
eframe 0.37 ─┬─ egui 0.37 ── epaint/emath/ecolor/epaint_default_fonts 0.37
|
||||
├─ egui-winit 0.37 ── winit 0.30.13 (approved storytold fork)
|
||||
├─ egui-wgpu 0.37 ── wgpu 30.0.x
|
||||
└─ egui_glow 0.37 (windows7)
|
||||
└─ egui-wgpu 0.37 ── wgpu 30.0.x
|
||||
egui_extras 0.37
|
||||
egui_kittest 0.37 ── kittest 0.5.0 (registry)
|
||||
craft-ui (final craft-libs rev) ── egui 0.37
|
||||
@@ -683,7 +675,7 @@ craft-window (final craft-libs rev) ── egui 0.37
|
||||
```
|
||||
|
||||
- **Required-used fork-source IDs** (each must appear once from the D1b revision): `egui`, `eframe`, `egui-winit`,
|
||||
`egui-wgpu`, `egui_glow` (`windows7` target only), `egui_extras`, `epaint`, `emath`, `ecolor`,
|
||||
`egui-wgpu`, `egui_extras`, `epaint`, `emath`, `ecolor`,
|
||||
`epaint_default_fonts`, `egui_kittest`.
|
||||
- **Optional or forbidden-if-present IDs:** `egui_demo_lib`, `egui_inspection`, `egui_system_font_provider` (only
|
||||
with the disabled `system_fonts` feature) and `egui_demo_app` (workspace-only binary). Absence is a pass; if one
|
||||
@@ -704,8 +696,7 @@ craft-window (final craft-libs rev) ── egui 0.37
|
||||
dependencies from `craft-ui`/`craft-window` resolve to the same source. A dependency's `[patch]` section is
|
||||
ignored by the consumer.
|
||||
- Do not use a branch or a machine-local path in publication. The winit fork's own patch is not inherited.
|
||||
- Keep `vendor/windows-link` and its separate Windows 7 packaging check, and keep the existing hayro/krilla root
|
||||
patches and PDF fixes. These are unrelated to the egui graph and must not be removed, folded into the fork pin, or
|
||||
- Keep the existing hayro/krilla root patches and PDF fixes. These are unrelated to the egui graph and must not be removed, folded into the fork pin, or
|
||||
blanket-deleted with PdfCraft's vendors. Do not add `vendor` copies of egui/winit for VectorCraft.
|
||||
- Add `cargo xtask ui-deps` (modelled on `xtask/src/pdf_deps.rs`) that runs `cargo metadata --locked` and checks a
|
||||
canonical ledger of every package ID and source: required-used fork IDs, optional IDs with an `absent` or
|
||||
@@ -1105,8 +1096,6 @@ Add accessibility tests in the existing `role_and_state` style and via kittest:
|
||||
pass. The existing unsafe `mac_window::center_buttons` may be replaceable by 0.37's
|
||||
`Frame::set_traffic_lights_position`, but do not adopt that without macOS evidence; keep the existing code and
|
||||
test it on real hardware later.
|
||||
- **Windows 7:** compile/package only under `packaging/windows/windows7.ps1 -Test`; no support claim without a real
|
||||
Windows 7 run. The `vendor/windows-link` patch remains.
|
||||
- **Windows ARM64 and FreeBSD:** keep the existing `.github/workflows/windows-arm64.yml` and `freebsd.yml` checks:
|
||||
compile/package and the smoke they actually run. Do not treat a cross/QEMU build as GUI, IME, clipboard or GPU
|
||||
evidence; record those rows as unavailable unless a real machine is used.
|
||||
@@ -1342,22 +1331,19 @@ These are not “future work”; they block calling the migration done:
|
||||
and isolate it from performance comparisons (§6.2). Recheck if main advanced further.
|
||||
9. **macOS evidence.** No macOS host/display is available here. Keyboard layouts, IME (including press-and-hold),
|
||||
Metal/GL, native pickers, termination and traffic-light positioning remain an explicit release gate.
|
||||
10. **Windows 7.** 0.37 + glow + clipboard + the separate `vendor/windows-link` packaging patch must
|
||||
compile/package and be smoke-tested; no support claim from a modern runner alone. Keep the hayro/krilla PDF
|
||||
patches independent.
|
||||
11. **Performance.** Paired control at `4a7cba0…` and candidate with the same engine delta, plus a stock-0.37.0
|
||||
10. **Performance.** Paired control at `4a7cba0…` and candidate with the same engine delta, plus a stock-0.37.0
|
||||
arm in the first report and every investigated regression, using the shared sample floors and the 5%
|
||||
median / 10% p95 / 5% RSS gates. Upstream #8731, #8736 and #8512 are reports, not results; do not claim a
|
||||
regression or a fix without ours.
|
||||
12. **Dependency identities.** Enumerate every fork-family package at one storytold source; audit external
|
||||
11. **Dependency identities.** Enumerate every fork-family package at one storytold source; audit external
|
||||
`kittest`/`accesskit*` as compatible registry identities, not egui-git crates. Skrifa/harfrust/glifo duplicates
|
||||
are expected; measure binary size and confirm no public type crossing. A new `egui`/winit duplicate is a hard
|
||||
failure.
|
||||
13. **Role semantics.** Review the `SelectableLabel → Button`, `Checkbox → CheckBox`, `CollapsingHeader →
|
||||
12. **Role semantics.** Review the `SelectableLabel → Button`, `Checkbox → CheckBox`, `CollapsingHeader →
|
||||
DisclosureTriangle` and `Other → Unknown` mappings against actual screen-reader semantics, not just compile.
|
||||
14. **Wayland/X11/GPU backend.** The host has an i915 render device but no inherited display session; a managed
|
||||
13. **Wayland/X11/GPU backend.** The host has an i915 render device but no inherited display session; a managed
|
||||
Linux job must qualify the actual compositor/backend/DPI. Availability is not a qualified result.
|
||||
15. **Web.** A wasm `check` is not a browser workflow; headless Chrome WebGPU/WebGL2, paste/drop, save/download and
|
||||
14. **Web.** A wasm `check` is not a browser workflow; headless Chrome WebGPU/WebGL2, paste/drop, save/download and
|
||||
screenshots are required.
|
||||
|
||||
**Done means:** one coherent 0.37 graph; `cargo xtask ci` and `ui-deps` pass from a clean checkout; lifecycle
|
||||
@@ -1391,7 +1377,7 @@ Every finding in `plan-reviews/vectorcraft-v2.md` is closed below by source-grou
|
||||
|
||||
| # | Finding | Disposition |
|
||||
|---|---|---|
|
||||
| 1 | No stock-0.37.0 control arm | **Accepted and fixed.** Stock 0.37.0 (`841a328b`) is a measured arm in the first baseline report and every investigated regression, with the same environment and a pinned minimal adapter where fork-only code cannot compile; retained even when app acceptance pairs 0.36 with the candidate. §5.3, §6.2, §7.1 V1, §8.11. |
|
||||
| 1 | No stock-0.37.0 control arm | **Accepted and fixed.** Stock 0.37.0 (`841a328b`) is a measured arm in the first baseline report and every investigated regression, with the same environment and a pinned minimal adapter where fork-only code cannot compile; retained even when app acceptance pairs 0.36 with the candidate. §5.3, §6.2, §7.1 V1, §8.10. |
|
||||
| 2 | Capture admission and geometry under-specified; 64 MiB too small | **Accepted and fixed.** Adopted the shared 4 requests / 128 MiB per image / 256 MiB aggregate defaults (stricter only if the documented intended surface fits), checked bytes reserved before issue, no eviction; records logical/screen bounds, viewport, DPI/zoom and surface epoch at issuance and rejects/retries any change before save, including change-back, then verifies scale/aspect. §3.5, §4.4, §5.2, §8.5. |
|
||||
| 3 | W27 old-epoch invalidation missing | **Accepted and fixed.** Old-epoch `done` is rejected and invalidates that epoch, never edits the new owner; current-epoch mismatch applies incoming once and defers outgoing. §3.9, §4.6, §5.6, §5.7. |
|
||||
| 4 | P7c ownership of #8763/#8748 absent | **Accepted and fixed.** P7c owns both fixes and their pointer/keyboard and hidden-parent regressions; D1b includes P7c, V2 depends on it, and an app workaround needs equivalent tests plus a removal condition. §3.11, §7.1, §8.1. |
|
||||
|
||||
File diff suppressed because one or more lines are too long
+20
-1
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** patch (missing translations never fail; `cargo xtask ci` prints the untranslated report)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Vietnamese text: stacked diacritics tested and attach in every form, #4491; previously: Catalan (`ca`) catalog added, complete under the tests)
|
||||
|
||||
Per-language status of VectorCraft's interface. How the catalogs work and how to add a language:
|
||||
[`development.md` › Localisation](development.md#localisation). Part of
|
||||
@@ -37,7 +37,7 @@ Chinese by `imachus`). Only Georgian is recorded as reviewed by a native speaker
|
||||
| Japanese | `ja` | 4,004 / 4,020 (99.6%) | all | CJK UI font as above; Japanese IME; vertical type partial; Japanese crop marks and composition settings | no | full | — (review) |
|
||||
| German | `de` | 3,952 / 4,020 (98%; menus, literals and messages complete when counted) | all (602 of 604 message rows; landed 2026-10-10) | Latin | contributor-written, not reviewed | full | — (review) |
|
||||
| Korean | `ko` | 4,870 rows (counted 2026-10-21): menus, `tl!` literals and messages complete when counted; one plural form | all | Hangul from an installed system font on the desktop (Apple SD Gothic Neo, Malgun Gothic, Noto Sans CJK KR…); the web build has no Korean UI font yet (craft-fonts); IME path exists | no (AI-drafted) | full | — (review; web font) |
|
||||
| Vietnamese | `vi` | 4,886 rows (counted 2026-10-21): menus, `tl!` literals and messages complete when counted; one plural form; 4,046 plain + 18 `@plural` + 28 contextual + 794 `@msg` | all | Latin with Vietnamese diacritics (precomposed forms; glyph tests pass) | contributor is a Vietnamese speaker; AI-assisted draft, further native review welcome | full | — (review) |
|
||||
| Vietnamese | `vi` | 4,886 rows (counted 2026-10-21): menus, `tl!` literals and messages complete when counted; one plural form; 4,046 plain + 18 `@plural` + 28 contextual + 794 `@msg` | all | Latin with Vietnamese diacritics (stacked marks attach in precomposed, decomposed and keyboard forms, #4491; glyph tests pass) | contributor is a Vietnamese speaker; AI-assisted draft, further native review welcome | full | — (review) |
|
||||
|
||||
**12-language score: ~82%** (equal weights: 100 + 85 + 100 + 0 + 0 + 100 + 100 + 100 + 100 + 98 + 100 + 100, over 12).
|
||||
**To `full` for all twelve: ~50–85 h**, most of it Arabic (right-to-left interface) and Hindi (interface
|
||||
@@ -62,8 +62,11 @@ plus review. Native-speaker review is human work and isn't in the hours.
|
||||
| Bulgarian | `bg` | 5,245 rows, 763 of them `@msg` (counted 2026-10-21): menus and `tl!` literals complete when counted; its messages are partial | full; AI-drafted, native review pending |
|
||||
| Greek | `el` | 5,251 rows, 795 of them `@msg` (counted 2026-10-21): menus, `tl!` literals and messages complete when counted | full; native review welcome |
|
||||
| Georgian | `ka` | 5,200 rows (counted 2026-10-21): menus, panels, dialogs, plurals (one count-neutral form) and messages, tested complete; reviewed by a native speaker | full |
|
||||
| Turkish | `tr` | 5,558 rows, 880 of them `@msg` (counted 2026-10-28); 6 menu strings and the default swatch names still fall back to English | full |
|
||||
| Indonesian | `id` | 5,531 rows, 788 of them `@msg` (counted 2026-10-28): menus and `tl!` literals complete when counted; its messages are partial; one plural form | full |
|
||||
| Catalan | `ca` | 5,626 rows, 881 of them `@msg` (counted 2026-10-28): menus, `tl!` literals and messages complete when counted; one/other plurals; every `ca-*` locale resolves here | full; AI-drafted, native review welcome |
|
||||
|
||||
Twenty-three non-English catalogs in all. Illustrator, as its public documentation lists, ships around two dozen
|
||||
Twenty-five non-English catalogs in all. Illustrator, as its public documentation lists, ships around two dozen
|
||||
interface languages (including Polish, Dutch, Turkish, the Nordic languages, Hungarian, and Middle East
|
||||
editions with Arabic and Hebrew); it doesn't ship Hindi, Indonesian or Vietnamese.
|
||||
|
||||
@@ -92,6 +95,8 @@ font for the web build.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-29 | minor | Vietnamese text: letters with two marks (Ể, Ứ, Ự) attach in precomposed, decomposed and keyboard forms, and a font lacking the marks falls back for the whole letter (#4491); scores unchanged |
|
||||
| 2026-10-28 | minor | Catalan (`ca`): complete catalog under the tests (menus, literals, messages; one/other plurals); every `ca-*` locale resolves here; AI-drafted, native review welcome. Added the Turkish and Indonesian rows the table was missing. Scores and estimates unchanged |
|
||||
| 2026-10-23 | patch | A missing translation never fails a test or a build; one untranslated report, printed by `cargo xtask ci` and `cargo xtask i18n-report`, lists the missing strings per language; scores unchanged |
|
||||
| 2026-10-16 | minor | Bulgarian (`bg`, #1909) and Greek (`el`, #2076) catalogs added, complete under the tests; Swedish (#2139) and Finnish (#1994) rows for strings added since; eighteen non-English catalogs |
|
||||
| 2026-10-16 | minor | Finnish (`fi`) catalog added, complete under the tests (menus, literals, messages; one/other plurals); AI-assisted, native proofreading pending |
|
||||
|
||||
+174
-38
@@ -416,7 +416,7 @@ objects' fills or strokes differ (`fillMixed` / `strokeMixed`, drawn as a "?" pr
|
||||
| `draw_path` | `{points \| d, closed?, fill?, stroke?, strokeWidth?, dash?, cap?, join?, opacity?, name?, effects?}` | `points` is `[[x,y],…]` or `[{x,y,in?,out?,smooth?},…]`. `d` is SVG path data. The options are below. |
|
||||
| `draw_shape` | `{shape, …geometry, fill?, stroke?, strokeWidth?, dash?, cap?, join?, opacity?, name?, effects?}` | `rectangle`/`ellipse`: `x,y,width,height` (plus `radius` for corners). `polygon`: `cx,cy,radius,sides`. `star`: `cx,cy,radius1,radius2,points`. `line`: `x1,y1,x2,y2`. |
|
||||
| `set_paint` | `{fill?, stroke?, strokeWidth?, ids?}` | Applies to the selection (or `ids`) and becomes the default for new art. Returns `{applied, objects}`: `objects` is the number of objects painted, as the `objects` of the `paint.setFill`, `paint.setStroke` and `stroke.set` replies (the largest of them): a selected group counts the objects inside it. When it is 0, a `note` says that only the paint for new art changed. |
|
||||
| `press_key` | `{key, mods?}` | Remote: a real key event. Headless: runs the command or tool bound to that shortcut, or sends the key to the busy tool (digits too: `5` while dragging with the Perspective Selection tool; `e` while dragging a Selection or Group Selection marquee). |
|
||||
| `press_key` | `{key, mods?}` | Remote: a real key event. Headless: runs the command or tool bound to that shortcut, or sends the key to the busy tool (digits too: `5` while dragging with the Perspective Selection tool; `e` while dragging a Selection or Group Selection marquee; `c` and `f` while dragging out an arc). |
|
||||
| `type_text` | `{text}` | Remote only. |
|
||||
| `answer_dialog` | `{set?: [{field, value?}], action?}` | Remote only. Sets fields of the app's open dialog in the order given with one `ui.dialog.set {fields}` (a missing `value` is `null`). The app stores all of them or none. When the app refuses the `ui.dialog.set`, for example because no dialog is open, the call is an error and `action` is not sent. Then `action` `confirm` (OK, `ui.dialog.confirm`) or `cancel` (`ui.dialog.cancel`). Give `set`, `action` or both; `set` holds at most 256 fields. `inspect_ui` shows the open dialog and its fields as `ui.dialog`. Returns `{dialog, result}`: the dialog after the fields were set (or `null`) and the reply to `confirm` or `cancel` (or `null`). After `invoke_menu` `app.quit`, `file.close` or `file.closeAll` replies `{"pending": "saveChanges"}`, `{action: "confirm"}` saves, `{set: [{field: "discard", value: true}], action: "confirm"}` closes without saving and `{action: "cancel"}` cancels the close or quit. |
|
||||
| `invoke_menu` | `{command, params?}` | Invokes a menu item by command id. Includes UI commands such as `view.*` and `window.*` in remote mode. |
|
||||
@@ -431,7 +431,7 @@ objects' fills or strokes differ (`fillMixed` / `strokeMixed`, drawn as a "?" pr
|
||||
| `transform` | `{ids?, dx?, dy?, rotate?, scale?, scaleX?, scaleY?, reflect?, shear?, origin?, copy?}` | Runs move, rotate, scale, reflect, shear in that order. With `copy`, the first step duplicates. |
|
||||
| `create_graph` | `{type?, x, y, width, height, csv? \| series?, categories?, rows?}` | The nine Illustrator graph types: column, stacked column, bar, stacked bar, line, area, scatter, pie and radar. An empty CSV cell or a `null` in `rows` is a blank value; a number in straight quotes is a label. Edit later with `graph.setData` / `graph.setType` via `run_command`. `graph.column {design, columnType: vertical\|uniform\|repeating\|sliding, each, fractions: chop\|scale}` draws the columns or bars of the series picked with Group Selection (or `seriesIndexes`, or all of them) with a graph design saved by `graph.design`; with no `design` it returns the series' current design and options. Type in the design that holds a `%` followed by two digits shows each column's value, with at least the first digit's places before the decimal point and the second digit's places after it. |
|
||||
| `text_wrap` | `{ids?, offset?, invert?, release?}` | Area type below the objects (same layer) flows around them. |
|
||||
| `undo` / `redo` | `{}` | Many calls that change nothing record no undo step, for example `object.align` with nothing to move, `object.move` by 0 without `copy` and `object.showAll` with nothing hidden. `undo` (`edit.undo`) after such a call undoes the step before it. `inspect_document` lists the undo steps as `history`. `run_command` with `edit.clearHistory` removes every undo and redo step of the active document and returns `{cleared}`, the number of steps removed; the document stays as it is, and the command records no undo step. |
|
||||
| `undo` / `redo` | `{}` | Many calls that change nothing record no undo step, for example `object.align` with nothing to move, `object.move` by 0 without `copy` and `object.showAll` with nothing hidden. `undo` (`edit.undo`) after such a call undoes the step before it. `inspect_document` lists the undo steps as `history`. `run_command` with `edit.clearHistory` removes every undo and redo step of the active document and returns `{cleared}`, the number of steps removed; the document stays as it is, and the command records no undo step. While the Type tool edits type, `undo` and `redo` put its caret, with no characters selected, at the end of the text the step changed: after the text it puts back or retypes, or where the text it takes out was. A step that changes no characters, such as a formatting change, leaves the caret and the selection where they are. |
|
||||
|
||||
For example, create editable Japanese area type with the engine's vertical writing mode:
|
||||
|
||||
@@ -470,8 +470,19 @@ mode. To press the key during a drag, end one
|
||||
{"name":"pointer_gesture","arguments":{"tool":"selection","events":[{"kind":"drag","x":230,"y":170},{"kind":"up","x":230,"y":170}]}}
|
||||
```
|
||||
|
||||
`press_key {key: "Escape"}` between two `pointer_gesture` calls of a Selection tool drag (a move or an Alt-drag copy, a
|
||||
bounding-box scale or rotation, a marquee, a corner widget, pie widget or bracket drag) cancels it: the art goes back,
|
||||
no copy is left, no undo step is added, and the `up` event that follows does nothing.
|
||||
|
||||
With nothing being dragged, `e` chooses the tool bound to E.
|
||||
|
||||
While an arc is being dragged out with the Arc tool, `press_key {key: "c"}` switches it between an open and a closed
|
||||
path, `{key: "f"}` switches its base between the X and Y axes in the same box, and `{key: "up"}` and `{key: "down"}`
|
||||
raise and lower its slope by 10, within −100…100. Each press switches once. The arc drawn on release uses these
|
||||
values, and they stay as the Arc tool's `closed`, `baseAlong` and `slope` options for the next arc. With nothing being
|
||||
dragged, `c` chooses the tool bound to C, and `f` runs the shortcut bound to F in remote mode (headless has no
|
||||
binding for it).
|
||||
|
||||
Appearance stacks: an object can carry several fills and strokes (`appearance.addFill`, `appearance.addStroke`), indexed
|
||||
in paint order (0 is painted first, the bottom row of the Appearance panel). Cmd+/ runs `appearance.addFill` and
|
||||
Cmd+Alt+/ runs `appearance.addStroke` (`press_key {key: "/", mods: {cmd: true, alt: true}}`). `paint.setFill`, `paint.setStroke`,
|
||||
@@ -493,7 +504,8 @@ Errors (unknown tool, bad arguments, a disabled or failing command) come back as
|
||||
|
||||
Swatches go through `run_command`: `swatch.list` lists every swatch with its colour group, kind and colour;
|
||||
`swatch.edit {name, newName?, color?, mode?, global?, spot?}` edits one (fills, strokes and text linked to a
|
||||
global or spot swatch follow its colour and name, as one undo step); `swatch.delete {names, unlink?}` deletes
|
||||
global or spot swatch follow its colour and name, as one undo step, in the art, opacity masks, symbols, graph
|
||||
designs, pattern tiles, graphic styles and the art of brushes); `swatch.delete {names, unlink?}` deletes
|
||||
swatches and colour groups in one undo step (art using a deleted global swatch keeps its colour, unlinked).
|
||||
`swatch.new {color?, mode?, spot?, global?, group?}` saves a colour (into a colour group with `group`), gradient
|
||||
or pattern; `swatch.newGroup {fromArtwork: true, toGlobal?, includeTints?}` makes a colour group of the selected
|
||||
@@ -578,12 +590,16 @@ come back without a path), `file.newFromTemplate {path}`, `file.revert` and `fil
|
||||
with the values a save would use). Without a path the save commands return `{dataBase64, name, folder?, warnings}`. A document saved as SVG or PDF
|
||||
remembers its options, so the next save reuses them. A PDF opened with `pages`, `cropTo` or `password` is only part
|
||||
of the file, so Save asks for a new name instead of writing it back.
|
||||
`file.documentColorMode {mode, convert?, intent?, grays?}` switches the document colour mode, converting the colours of
|
||||
the art, symbols, pattern tiles and swatches through the colour settings (`object.convertDocumentColorMode` is an
|
||||
alias kept for older scripts). Gray colours stay Gray and print on the black plate only. RGB greys are colours like any
|
||||
`file.documentColorMode {mode, convert?, intent?, grays?}` switches the document color mode, converting the colors of
|
||||
the art (opacity masks included), symbols, graph designs, pattern tiles (with the tile that pattern editing's Cancel
|
||||
puts back), graphic styles, brushes (their art and a Hue Shift key color) and swatches through the color settings, in
|
||||
one undo step (`object.convertDocumentColorMode` is an alias kept for older scripts). Gray colors stay Gray and print
|
||||
on the black plate only. RGB grays are colors like any
|
||||
other: to CMYK they separate through the profile into four-colour greys and a rich black, as Illustrator converts
|
||||
them. `grays: "black"` puts RGB greys with R = G = B on the black plate instead, K = their grey value (the inks Edit Colors ›
|
||||
Convert to Grayscale before the switch would give); other colours convert as usual.
|
||||
Convert to Grayscale before the switch would give); other colors convert as usual. The reply's `changed` counts the
|
||||
colors that changed in the art, symbols, graph designs and pattern tiles; it does not count the colors of graphic
|
||||
styles and brushes.
|
||||
|
||||
SVG import keeps what the canvas edits live. `<pattern>` becomes a pattern swatch. A `<symbol>` with `<use>` becomes a
|
||||
symbol (named after its `data-name`, else its id) with an instance per `<use>`; a `<use>` that shows it differently
|
||||
@@ -643,9 +659,13 @@ SVG writes one file per artboard of a `range`); `useArtboards: false` exports th
|
||||
its effects paint, the effects of groups, type and images included. `artboard.fitToArt` measures the art the same
|
||||
way.
|
||||
|
||||
Raster exports take more options in `options`: `ppi` (72, 150, 300…; stored in the file), `background`
|
||||
(`transparent`, `white`, `black` or `"#rrggbb"`), `antiAlias` (`none`, `art`, `type`: text snapped to pixels) and,
|
||||
for PNG, `interlaced`.
|
||||
Raster exports take more options in `options`: `ppi` (72, 150, 300…; stored in the file, except in GIF, WebP and
|
||||
Targa), `background` (`transparent`, `white`, `black` or `"#rrggbb"`), `antiAlias` (`none`, `art`, `type`: text
|
||||
snapped to pixels) and, for PNG, `interlaced`. `width` or `height` sets the size of each file in pixels: each artboard
|
||||
(with `useArtboards`, each file), `rect` or art bounds gets the scale that makes it that wide or high, within 0.01–64
|
||||
pixels per point, and the file stores the resolution that results, as with `ppi`. `width` wins over `height`, and
|
||||
either one wins over `ppi` and `scale`. A value that is not a positive number is refused. Export for Screens rows keep
|
||||
their own size (`scale: "512w"`).
|
||||
|
||||
Long-tail commands:
|
||||
|
||||
@@ -1088,7 +1108,7 @@ The transform, Align, Object Properties and Live Shape Properties commands refus
|
||||
that is not true or false with `` `strokes` must be true or false ``, naming the key, and leave the document and its
|
||||
undo history as they were: the options `copy`, `strokes`, `corners`, `patterns`, `objects` and `typeAreas` in
|
||||
`object.transform`, `object.move`, `object.nudge`, `object.rotate`, `object.scale`, `object.reflect`, `object.shear`,
|
||||
`object.transformAgain` and `object.setBounds`; `anchors` in `object.transform`; `big` in `object.nudge`;
|
||||
`object.transformAgain` and `object.setBounds`; `anchors` and `releaseOutside` in `object.transform`; `big` in `object.nudge`;
|
||||
`absolute` in `object.rotate`; `proportional` in `object.setBounds`; `patterns` in `object.align`, `object.distribute`
|
||||
and `object.distributeSpacing`; `visible`, `locked`, `isolate` and `knockoutShape` in `object.setProps`; and
|
||||
`invertPie`, `makeSidesEqual` and `relativeRadii` in `object.setLiveShape`. A null is read as a key left out.
|
||||
@@ -1133,7 +1153,8 @@ and `radial` in `shape.polarGrid`; `size` and the `width`, `height` and `fitMinP
|
||||
`end` in `type.step`; `by` in `type.size.increase` and `type.size.decrease`; `size` in `type.ruby`; `width`, `height`
|
||||
and `fitMinPercent` in `text.areaOptions`; `x` and `y` in `symbol.place`; `radius`, `density` and `scale` in
|
||||
`symbol.spray`; `radius` and `intensity` in `symbol.adjust`; `to` in `swatch.move`; `weight` and `opacity` in
|
||||
`appearance.setItem`; `angle` in `paint.editGradient` and in the `gradient` of a command that takes a paint; `t` in
|
||||
`appearance.setItem`; `r`, `g`, `b`, `c`, `m`, `y`, `k`, `gray` and `tint` in `edit.colors.adjustBalance`; `steps`
|
||||
and `amount` in `color.harmony`; `angle` in `paint.editGradient` and in the `gradient` of a command that takes a paint; `t` in
|
||||
`paint.freeform.splitLine`; `tolerance` and `gap` in `shapeBuilder.merge`; `gap` in `shapeBuilder.regions`; and
|
||||
`width` and `tolerance` in `livePaint.strokeEdge`. A null is read as a key left out. A gradient stop that names a
|
||||
swatch refuses a `tint` that is not a number with `` stop 0: `tint` must be a number ``.
|
||||
@@ -1250,6 +1271,12 @@ then composite against the art below it). SVG has no non-isolated groups, so the
|
||||
mask is edited; called again it shows the artwork, still editing. It is view state of the open document: the canvas
|
||||
(and `ui.screenshot`) shows it, exports never do, and it ends when mask editing ends.
|
||||
|
||||
Isolation mode and mask editing exclude each other. In isolation mode `transparency.editOpacityMask` and
|
||||
`transparency.viewOpacityMask` are off, and `transparency.makeOpacityMask` refuses one target, an object or a layer,
|
||||
which it would take into mask editing; a call returns an error that ends "exit isolation mode first". While a mask is edited,
|
||||
`object.isolate` and `perspective.editText` are off, and a call returns an error that ends "stop editing the opacity
|
||||
mask first". Pattern editing mode does not count as isolation mode here.
|
||||
|
||||
`view.transparencyGrid {on?}` (View → Show Transparency Grid; default: toggle) is view state of each open document,
|
||||
like the mask view: the grid shows behind the artboards of the documents that turn it on, on the canvas and in
|
||||
`ui.screenshot`, never in exports. It returns `{on}`, isn't undoable and doesn't mark the document changed.
|
||||
@@ -1333,11 +1360,27 @@ that were already hidden or locked. The key object is cleared when it is one of
|
||||
them again with the key object. When the call changes nothing, it records no undo step and still deselects them.
|
||||
Other `object.setProps` calls leave the selection unchanged.
|
||||
|
||||
`object.unlockAll` and `object.showAll` unlock or show every locked or hidden object in the document and select
|
||||
them → `{count}`. With `scope: "group"` they take only the objects inside the groups that hold the selected objects:
|
||||
for each selected object, the group nearest above it, or the object itself when it is a group. The objects of a
|
||||
group inside another such group are counted once. With no selected object in a group, the call returns the error
|
||||
"nothing selected is in a group" and changes nothing. `scope: "document"` is the default, and any other value is
|
||||
refused. Object › Unlock All and Object › Show All clicked in the in-window menu bar with Shift+Alt (Shift+Option)
|
||||
held run with `scope: "group"`.
|
||||
|
||||
`layer.move`, `node.move`, `layer.merge`, `layer.flatten`, `layer.collectInNew`, `dataset.select`, `dataset.next`
|
||||
and `dataset.prev` also deselect every selected object that is hidden or locked after the call, such as objects
|
||||
moved into a hidden or locked layer or group, or objects that a data set hides. The key object is cleared when it
|
||||
is one of them, and `edit.undo` selects them again with the key object.
|
||||
|
||||
`object.hide.unselected` (Object › Hide › Unselected Artwork, Cmd+Alt+Shift+3) hides, in one undo step, every
|
||||
object that `select.all` takes and that is neither selected nor holds a selected object, and returns `{count}`. In a
|
||||
group that holds a selected object it hides the group's other members one by one; a clip group, a compound path or
|
||||
another object that holds a selected object stays as it is with its members. While Lock Guides is on it leaves the
|
||||
guide paths as they are. The selection stays as it is, and `object.showAll` shows the objects again. With nothing to
|
||||
hide it records no undo step. In isolation mode the menu item is disabled, and the command returns the error "exit
|
||||
isolation mode first" and changes nothing.
|
||||
|
||||
These Select menu commands select only the objects that `select.all` selects and the objects inside them: every
|
||||
`select.same.*` and `select.object.*` command except `select.object.slices`, `select.nextAbove`, `select.nextBelow`
|
||||
and `select.recall`. They leave out hidden and locked objects with the objects inside them, the objects on hidden,
|
||||
@@ -1350,6 +1393,19 @@ everything in it, and `select.all`, `select.allOnArtboard` and `select.inverse`
|
||||
among the objects beside it in its group or layer, passing over hidden and locked objects and sublayers. When there
|
||||
is no such object, the selection stays as it is.
|
||||
|
||||
## Shaper Groups
|
||||
|
||||
`shaper.releaseSource {source, group?}` takes the original shape `source` out of its Shaper Group: the shape becomes a
|
||||
path just above the group with its own fill and stroke, and is selected. The group's result is computed again from the
|
||||
original shapes left, and a group left with none is removed. When `group` is given, it must be the Shaper Group that
|
||||
holds `source`. A source that is not an original shape of an editable Shaper Group is an error, and the document and its
|
||||
undo history stay as they were. The command returns `{id}`, the shape's id, and records one undo step, Remove from Shaper
|
||||
Group. Construction mode ends when the group was in it.
|
||||
|
||||
`object.transform` with `releaseOutside: true` does the same for each moved original shape whose bounds end wholly
|
||||
outside the bounds the group's original shapes had before the move, in the move's undo step. A shape whose bounds still
|
||||
overlap them only moves. The Selection tool sends it at the end of a move in Construction mode.
|
||||
|
||||
## Global Edit
|
||||
|
||||
Select › Start Global Edit is `select.globalEdit {appearance?, size?, artboards?, includeCanvas?}`. With one path,
|
||||
@@ -1476,7 +1532,11 @@ already walks Image Trace. Short agent entry points:
|
||||
`brush.new` refuses a longer one, counting the number it adds to a taken name, and `brush.options` refuses a
|
||||
longer `newName`. `brush.duplicate` does not check the length. A bristle brush that already has a longer name
|
||||
keeps it, and `brush.options` without a new name, or with a `newName` that only trims the white space around the
|
||||
name, does not refuse. Other brush types take a name of any length.
|
||||
name, does not refuse. Other brush types take a name of any length. A pattern brush's `outer_source` and `inner_source` set
|
||||
what that kind of corner draws when the brush has no `outer_corner` or `inner_corner` art for it: `none` draws no
|
||||
corner tile, and `autoCentered`, `autoBetween`, `autoSliced` and `autoOverlap` make one from the side tile. Without
|
||||
a source, the corner draws the other kind of corner's art. `brush.options {params: {outer_corner: null}}` removes
|
||||
the art.
|
||||
- Variables: `variable.list`, `variable.define {name, kind: text|visibility|image|graph}`, `variable.bind {variable, ids?}`,
|
||||
`variable.makeTextDynamic`, `variable.makeVisibilityDynamic`, `variable.makeImageDynamic`, `variable.makeGraphDynamic`,
|
||||
`variable.unbind`, `variable.rename`, `variable.delete`, `variable.highlight` (image values are file paths, graph
|
||||
@@ -1540,7 +1600,14 @@ already walks Image Trace. Short agent entry points:
|
||||
highlight color, `#rrggbb` or a layer color's name; `#ff2a2a`) and `width` (the highlight's width, 1 to 20 screen
|
||||
pixels; 4). With `selectStrokes` off, Alt-click selects the face under the pointer as a click does; with
|
||||
`selectFills` off, a click selects the edge nearest the pointer as Alt-click does; with both off, a click selects
|
||||
no face or edge.
|
||||
no face or edge. A drag of 3 screen pixels or more draws a marquee, and the release runs `select.set {ids}` with
|
||||
the faces and edges it touches (a face or edge partly inside counts, and so does a face that holds the whole
|
||||
marquee), or `select.none` when it touches none. With Shift held at the press, the release runs
|
||||
`select.toggle {ids}` with the faces and edges it touches, each of which leaves the selection or joins it, and runs
|
||||
nothing when it touches none. `selectFills` off leaves the faces out, and `selectStrokes` off leaves the edges out. A click and a marquee run their command on the
|
||||
release, and Escape between the press and the release removes the marquee so that the release runs nothing. The
|
||||
tool does not highlight or select the faces and edges of a Live Paint group that is locked, on a locked layer or on
|
||||
a locked artboard, or in isolation mode outside the isolated art.
|
||||
`livePaint.merge` keeps the bottom Live Paint group's id, name, lock, visibility, transparency, appearance, opacity
|
||||
mask and graphic style link. With a Live Paint group among the objects, `livePaint.make` adds the others to it as
|
||||
`livePaint.merge` does, and so does `livePaint.fill {ids}` when `point` is outside that group's faces.
|
||||
@@ -1720,7 +1787,9 @@ the scope and what differs from the public documentation.
|
||||
fills, strokes, text, gradient stops, gradient-mesh points, embedded images (a recoloured copy of the image; linked
|
||||
images are left alone) and the tiles of pattern fills and strokes (a recoloured copy saved as a new pattern swatch,
|
||||
e.g. "Dots 2"; the original pattern is untouched). `includeImages: false` / `includePatterns: false` leave images or
|
||||
patterns out. The Blend commands also grade gradient meshes, which keep their shading.
|
||||
patterns out. The Blend commands also grade gradient meshes, which keep their shading. `edit.colors.saturate` needs
|
||||
`intensity`, a number from -100 to 100 (a number outside the range counts as its nearest end). Without one, or with a
|
||||
value that is not a number, it answers `` missing number `intensity` `` and changes nothing.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"recolor.colors","params":{}}}
|
||||
@@ -2111,7 +2180,8 @@ and `layerRows`, the rows highlighted in the panel.
|
||||
set|add|toggle}` is Shift-click (a range) and Ctrl/Cmd-click (toggle). Selecting art makes its layer current.
|
||||
Without `ids`, the panel commands below act on the highlighted rows, else on the current layer.
|
||||
- **Selection column**: `layer.selectAll {id, add?}` selects a row's visible, unlocked art (a layer's sublayers'
|
||||
too); `add` (Shift) adds it, or removes it when it's all selected.
|
||||
too); `add` (Shift) adds it, or removes it when it's all selected. Alt-click on a layer's or sublayer's name runs
|
||||
`layer.selectAll {id}` and then `layer.setCurrent {id}`.
|
||||
- **Eye and lock**: `layer.setProps {ids?, visible?, locked?, name?}` for any rows, plus Layer Options for layers:
|
||||
`template` (also locks the layer and dims its images to 50% unless given), `printable`, `preview` (off: the layer
|
||||
draws and is clicked in outline, Ctrl/Cmd-click its eye), `dimImages` (0–100, or false) and `color` (an index 0–26,
|
||||
@@ -2122,10 +2192,12 @@ and `layerRows`, the rows highlighted in the panel.
|
||||
inside it); a row never goes into itself or a row inside it, and a locked layer or group takes nothing. Dragging the
|
||||
selected-art square is `layer.move` with the selected objects. `node.move {id, parent?, index}` is the low-level
|
||||
form with the same nesting rules; unlike `layer.move`, it also moves a row into a locked layer or group.
|
||||
- **Buttons**: `layer.new {name?, top?, …options}` (above the current layer at its level; `top` with Ctrl/Cmd),
|
||||
`layer.newSublayer {parent?, name?, …options}` (on top of the parent's contents), `layer.delete {ids?}` (rows with
|
||||
what they hold; the last layer stays), `layer.locate {id?}` (highlights the selected object's row; the panel opens
|
||||
the rows around it) and `layer.clippingMask.toggle`.
|
||||
- **Buttons**: `layer.new {name?, top?, below?, …options}` (above the current layer at its level; `top` with
|
||||
Ctrl/Cmd; `below`, directly below the current layer at its level, with Ctrl/Cmd+Alt),
|
||||
`layer.newSublayer {parent?, bottom?, name?, …options}` (on top of the parent's contents; `bottom`, under them but
|
||||
above a clipping layer's clipping path, with Ctrl/Cmd+Alt), `layer.delete {ids?}` (rows with what they hold; the
|
||||
last layer stays), `layer.locate {id?}` (highlights the selected object's row; the panel opens the rows around it)
|
||||
and `layer.clippingMask.toggle`.
|
||||
- **Panel menu**: `layer.duplicate {ids?}`, `layer.merge {ids?}` (into the layer highlighted last, keeping the
|
||||
stacking order), `layer.flatten {id?}` (every other visible layer's art into one layer; hidden layers are deleted,
|
||||
templates stay), `layer.collectInNew {ids?}`, `layer.releaseToLayers {id?, build?}` and
|
||||
@@ -2148,6 +2220,11 @@ and `layerRows`, the rows highlighted in the panel.
|
||||
opens no rows for it and scrolls as with the option off. A change made with the panel's own rows, buttons or menu is
|
||||
not located, and a change made during a drag is located when the drag ends.
|
||||
A document opens with only its top-level layers open; the open rows are saved in the native file and reopen that way.
|
||||
- **Lock and Hide in isolation mode**: in isolation mode, Object › Lock's and Object › Hide's items (`object.lock`,
|
||||
`object.lock.above`, `object.lock.otherLayers`, `object.hide`, `object.hide.above`, `object.hide.otherLayers`),
|
||||
`object.unlockAll` and `object.showAll` are off, and a call returns the error "command `object.hide` is not
|
||||
available right now: exit isolation mode first", with the command's own id. Pattern editing mode and opacity mask
|
||||
editing do not count as isolation mode for these commands.
|
||||
- **Search and filter** (control channel and the app's MCP): the Search All field keeps the rows whose names hold the
|
||||
text typed, and `ui.layersFilter {objects?, layers?}` sets the choices of the Filter button beside it. `objects` (By
|
||||
Object) takes `paths` (paths and compound paths), `shapes` (live rectangles, ellipses, polygons and lines), `text`,
|
||||
@@ -2158,6 +2235,12 @@ and `layerRows`, the rows highlighted in the panel.
|
||||
stays. A key left out keeps its list. The filter lasts the session and does not change the document, its open rows
|
||||
or the selection. `ui.layersFilter` answers `{objects, layers}`.
|
||||
|
||||
These Layers panel commands refuse a string, a number or another value that is not true or false with
|
||||
`` `copy` must be true or false ``, naming the key, and leave the document, the selection, the highlighted rows and
|
||||
the undo history as they were: `top` in `layer.new` and `layer.newSublayer`, `add` in `layer.selectAll` and
|
||||
`layer.target`, `copy` in `layer.move`, `on` in `layer.template` and `layer.pasteRemembersLayers`, and `build` in
|
||||
`layer.releaseToLayers`. A null is read as a key left out.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"layer.newSublayer","params":{"name":"Shadows"}}}
|
||||
{"name":"run_command","arguments":{"command":"layer.move","params":{"ids":[12,15],"target":7,"place":"inside"}}}
|
||||
@@ -2425,7 +2508,8 @@ so `path.moveAnchors {dx, dy}` (or a Direct Selection drag) pulls the path apart
|
||||
Selected End Points) joins the ends again; `path.join {ids: [a, b], ends: ["last", "first"]}` joins the ends asked for
|
||||
(the Pen's join, #776: drawing on from one open path, a click on another's end makes them one path). `path.convertAnchor {id, subpath?, anchor, to, x?, y?}` and
|
||||
`path.split {id, subpath?, anchor}` do the same to one anchor (the Anchor Point and Scissors tools); the Pen with Alt
|
||||
held over a selected path's handle, anchor or segment works as the Anchor Point tool. `path.reshapeSegment {id,
|
||||
held over a selected path's handle, anchor or segment works as the Anchor Point tool. `press_key` Escape between two
|
||||
`pointer_gesture` calls of an Anchor Point drag cancels it: the path goes back and no undo step is added. `path.reshapeSegment {id,
|
||||
subpath?, segment, t, dx, dy, perpendicular?}` (a segment dragged with Direct Selection or the Anchor Point tool) moves the segment's
|
||||
point at `t` by `dx`, `dy`: a smooth anchor at either end stays smooth, its other handle turning with the moved one.
|
||||
With `perpendicular: true` (Shift held during the drag) both of the segment's handles stand at right angles to the
|
||||
@@ -2446,8 +2530,8 @@ These commands refuse a string or another value that is not a number with `` `dy
|
||||
naming the key; the document does not change and no undo step is recorded: `t`, `dx` and `dy` in
|
||||
`path.reshapeSegment` and `object.blend.spine.reshapeSegment`; `x`, `y` and `t` in `path.curvatureEdit`; `x` and `y`
|
||||
in `path.convertAnchor`; `t` in `path.insertAnchor`; `fidelity` and the `within` of `redraw` in `path.freehand` (and in
|
||||
`brush.freehand`, which runs it); `size`, `angle` and `roundness` in `path.eraseRegion`; `size` in `path.blob`;
|
||||
`width` in `path.eraseSegments`;
|
||||
`brush.freehand`, which runs it); `size`, `angle` and `roundness` in `path.eraseRegion`;
|
||||
`size` and `fidelity` in `path.blob`; `width` in `path.eraseSegments`;
|
||||
`radius` and `fidelity` in `path.smoothRegion`; `tolerance` in `path.joinScrub`; `dx` and `dy` in `path.moveAnchors`;
|
||||
and `dx`, `dy`, `tol` and `radius` in `path.reshape`. A null is read as a key left out. `path.convertAnchor` refuses
|
||||
an `x` without a `y`, or a `y` without an `x`, with "x and y must be given together". `path.freehand`,
|
||||
@@ -2511,6 +2595,23 @@ dialog `simplifySlider` with field `level`) at the Auto-Simplify level for the s
|
||||
opens the Simplify dialog at the slider's tolerance. When OK confirms the dialog with `retain` ticked, the menu item opens
|
||||
the dialog on those settings next time.
|
||||
|
||||
## Smooth
|
||||
|
||||
`object.path.smooth` smooths the selected paths (or the objects in `ids`), compound path members included, as one undo
|
||||
step. `level` is the Smooth slider's, a whole number from 0 (minimum smoothing: the path stays within 0.1% of its longer
|
||||
side) to 100 (maximum smoothing); `auto: true` (Auto-Smooth) uses level 50, the default. Each path is refitted with
|
||||
curves within 0.1% to 2.5% of its longer side, keeping a corner only where it turns back on itself, so jags and kinks
|
||||
become curves; a refit never has more anchor points than the path had, so a shape whose corners the tolerance can't
|
||||
round, such as a rectangle, stays as it is. Closed paths stay closed and open paths keep their end points. The reply has the anchor counts `before` and `after` and the `level` used:
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"object.path.smooth","params":{"level":80}}}
|
||||
```
|
||||
|
||||
In the desktop app, Object › Path › Smooth opens the slider (`ui.menuDialog {command: "object.path.smooth"}`, dialog
|
||||
`smoothSlider` with field `level`) at the Auto-Smooth level and previews the result until `ui.dialog.confirm`;
|
||||
`ui.smoothAuto` moves the open slider back to the Auto-Smooth level.
|
||||
|
||||
## Editing a path with the Curvature tool
|
||||
|
||||
The Curvature tool edits any selected path, whatever drew it, keeping its shape except where edited (#798). Each edit
|
||||
@@ -2599,8 +2700,10 @@ changes size, and type keeps its character strokes' weight. With Scale Corners o
|
||||
size. The journal entry of a scaling command records the `strokes` and `corners` it used, so actions replay alike.
|
||||
`object.transformAgain` applies the last transform's matrix with the `copy`, `strokes`, `corners`, `patterns` and
|
||||
`typeAreas` values that transform used, and a true or false value given in its own params takes precedence. After a
|
||||
drag of art without a pattern, `patterns` follows the preference `transformPatternTiles`. Its journal entry records
|
||||
the `strokes`, `corners` and `patterns` it used, as a transform's does.
|
||||
drag of art without a pattern, `patterns` follows the preference `transformPatternTiles`. After a Direct Selection or
|
||||
Group Selection drag that moved direct-selected anchors (`path.moveAnchors`), it moves the selected anchors and the
|
||||
objects selected whole by the same distance. Its journal entry records the `strokes`, `corners` and `patterns` it
|
||||
used, as a transform's does.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"object.scale","params":{"sx":200,"strokes":true}}}
|
||||
@@ -2708,6 +2811,12 @@ the Align To choice, and while there is a key object or a key guide they send th
|
||||
`spacing`. Edit › Keyboard Shortcuts lists them under Align, with no default shortcut, and
|
||||
`shortcuts.set {id, shortcut}` gives one a shortcut.
|
||||
|
||||
Edit › Keyboard Shortcuts and `shortcuts.list` leave out a command with a menu path and no default shortcut when its
|
||||
params doc has a required field (one with no `?` and no `=` default), no item of the menu bar runs it without params,
|
||||
and it opens no dialog when run without them, such as `document.setUnits {units}` (File › Document Setup › Units).
|
||||
They list such a command while the user has changed its shortcut (`overridden: true` in `shortcuts.list`), and
|
||||
`shortcuts.set` can change or clear it.
|
||||
|
||||
The desktop app adds its active artboard, the one the Artboards panel highlights, to the `object.align`,
|
||||
`object.distribute`, `object.distributeSpacing` and `select.allOnArtboard` calls it runs without `artboard`, as it does
|
||||
for `edit.pasteInPlace`, `edit.pasteInFront` and `edit.pasteInBack`, and to each `command.batch`. A batch passes its
|
||||
@@ -2816,7 +2925,8 @@ The Selection & Anchor Display and General preferences apply to `pointer_gesture
|
||||
Transform Patterns) and of the same param on the commands that move or duplicate art with an artboard (`artboard.move`
|
||||
and the Artboard tool, `artboard.duplicate`, `artboard.rearrange`, `artboard.setProps`): pattern fills and strokes
|
||||
move with the art. `object.nudge` (the arrow keys) and `path.moveAnchors` (a Direct Selection drag of selected
|
||||
anchors) take the same param, with the same default, for the objects they move whole.
|
||||
anchors, and a Direct Selection or Group Selection drag from an object while anchors are selected) take the same
|
||||
param, with the same default, for the objects they move whole.
|
||||
- `selectSameTintPercent` (off by default): `select.same.fillColor`, `strokeColor` and `fillAndStroke` take every tint
|
||||
of a global or spot swatch; on, only the same tint.
|
||||
|
||||
@@ -2991,14 +3101,17 @@ for a uniform stroke). `stroke.widthPoint.remove {id, index | indices}` deletes
|
||||
points}` replaces them all, as dragging several Shift-selected points with the Width tool does in one undo step.
|
||||
With the Width tool, `press_key` Delete or Backspace removes the selected width points (`handledBy: "tool"`); with none
|
||||
selected the key clears the selected objects as usual (`edit.clear`). The Puppet Warp tool's selected pins go the
|
||||
same way.
|
||||
same way. With the Width tool, `press_key` Escape deselects the selected width points (`handledBy: "tool"`) and
|
||||
leaves the document and its undo history as they were.
|
||||
A compound path's stroke is the compound's: width point commands given one of its members edit the compound's
|
||||
profile, which runs along every subpath (the Width tool shows each subpath's points).
|
||||
|
||||
`object.puppetWarp {ids?, pins, moved, angles?, expand?}` warps art as rigidly as possible so that each pin (a point
|
||||
on the art) lands on its `moved` point. `angles` (degrees, or null for a free pin; as long as `pins`) holds the turn
|
||||
the art takes around a pin, as Alt-dragging near a selected pin with the Puppet Warp tool does: one pin with
|
||||
`angles: [90]` turns the art a quarter turn around it.
|
||||
`angles: [90]` turns the art a quarter turn around it. `press_key` Escape between two `pointer_gesture` calls of a
|
||||
pin drag or a turn cancels it: the art, its pins and the selected pins go back to how the press found them, and no
|
||||
undo step is added.
|
||||
|
||||
The Puppet Warp tool's pins live in the document while the same art stays selected (never saved; Undo and Redo take
|
||||
them back with the art). `object.puppetWarp.pins {ids?}` reads them: `pins` (where each sits on the shape the pins
|
||||
@@ -3062,6 +3175,9 @@ three: General (rulers, positions and sizes, the Info panel, dialog distances, c
|
||||
(`document.inspect` → `units`): `document.setUnits {units}` (Document Setup) sets it for that document, and
|
||||
`prefs.set {key: "unitsGeneral", value}` sets it for the active document too (one undo step) and is the units
|
||||
`file.new` starts in when it gets no `units`. Stroke and Type are the preferences `unitsStroke` and `unitsType`.
|
||||
`document.switchUnits` (Cmd/Ctrl+Alt+Shift+U) sets the active document's units to the next unit in the order a
|
||||
right-click on a ruler lists them (Points, Picas, Inches, Millimeters, Centimeters, Pixels, Feet & Inches, Meters,
|
||||
Yards, Feet, then Points again), in one undo step, and replies `{units}`.
|
||||
`numbersWithoutUnitsArePoints` (on by default) makes the desktop app's length fields in picas read a number typed
|
||||
with no unit in points (`12` is 12 pt, `2p6` and `1p` stay picas, `12 mm` millimetres); off, it is in picas. Fields in
|
||||
other units read it in their unit either way, and the Preferences dialog dims the option unless a unit is Picas.
|
||||
@@ -3191,11 +3307,16 @@ Every artboard command that takes an `index` fails with `no such artboard` when
|
||||
clipboard stay as they were.
|
||||
These artboard commands refuse a string, a number or another value that is not true or false with
|
||||
`` `art` must be true or false ``, naming the key, and leave the document, its undo history and the clipboard as they
|
||||
were: `moveArt`, `scaleArt`, `strokes`, `corners`, `patterns`, `lockedAndHidden` and an artboard's `locked` in
|
||||
`artboard.setProps`; `moveArt`, `copy` and `patterns` in `artboard.move`; `art` and `patterns` in `artboard.duplicate`;
|
||||
were: `moveArt`, `scaleArt`, `strokes`, `corners`, `patterns`, `lockedAndHidden` and an artboard's `locked`,
|
||||
`showCenterMark`, `showCrossHairs` and `showVideoSafeAreas` in `artboard.setProps`; `showCenterMark`, `showCrossHairs`
|
||||
and `showVideoSafeAreas` in `artboard.new`; `moveArt`, `copy` and `patterns` in `artboard.move`; `art` and `patterns` in `artboard.duplicate`;
|
||||
and `art` in `artboard.copy` and `artboard.cut`. `artboard.setProps` refuses an artboard's `x`, `y`, `width` or
|
||||
`height` that is not a number, and `artboard.move` refuses such a `dx` or `dy`, with
|
||||
`` `dx` must be a number, not "10" ``. A null is read as a key left out.
|
||||
`artboard.new` refuses an `x`, `y`, `width` or `height` that is not a number with the same message, and
|
||||
`artboard.new` and `artboard.setProps` refuse a `name` that is not a string, such as 5, with
|
||||
`` `name` must be a string ``. Both leave the artboards and the undo history as they were, and read a null as the
|
||||
key left out.
|
||||
|
||||
Scale Artwork with Artboard (#602): `artboard.setProps {index, x?, y?, width?, height?, scaleArt: true}` resizes an
|
||||
artboard and scales the art fully inside it, and its artboard guides, from the old rectangle onto the new one, each side
|
||||
@@ -3696,7 +3817,8 @@ slice ids and the ids of objects with an object slice.
|
||||
## Export for Screens
|
||||
|
||||
`document.exportForScreens` writes every chosen artboard (`range`, `artboards`; default all) in every format row
|
||||
into `folder`, or returns the files as `dataBase64` without one; `zip: true` packs them into one store-only `.zip`.
|
||||
into `folder`, or returns the files as `dataBase64` without one; `zip: true` packs them into one `.zip`,
|
||||
each file deflated, or stored when deflating does not make it smaller.
|
||||
A row is `{format, scale?}` where `scale` is a factor (`2`, `"2x"`), a pixel width (`"100w"` or `width: 100`), a
|
||||
height (`"100h"`) or a resolution (`"72ppi"`); raster rows name their files `@2x`, `@100w`… unless `suffix` says
|
||||
otherwise. `fullDocument` writes one file per row instead (a PDF of every artboard, other formats the bounds of all
|
||||
@@ -4958,7 +5080,9 @@ active the active artboard (the navigator's and the Artboards panel's).
|
||||
`tool.options {tool}` opens what double-clicking the tool's button opens (its params doc lists every tool), in the
|
||||
desktop app. The Flare tool's are its Flare Tool Options (dialog `flareOptions`): `diameter` and `pathLength` (pt),
|
||||
`opacity`, `brightness`, `growth`, `fuzziness`, `longest`, `rayFuzziness` and `largest` (%), `rays`, `rings`,
|
||||
`direction` (°), and `raysOn` and `ringsOn` (the Rays and Rings checkboxes; off, the flare has none). OK keeps them
|
||||
`direction` (°), and `raysOn` and `ringsOn` (the Rays and Rings checkboxes; off, the flare has none). While Alt
|
||||
(Option on macOS) is held, the dialog's Cancel button reads Reset. Reset puts every option back to the tool's default,
|
||||
leaves a click's `x` and `y` unchanged and keeps the dialog open; `ui.dialogReset` does the same. OK keeps the options
|
||||
(`tool.setOption {tool: "flare", values}`) and the next flare dragged out uses them; a click with the tool opens the
|
||||
same dialog, and its OK also draws a flare there. The Artboard tool's are the active artboard's Artboard Options
|
||||
(dialog `artboardOptions`; the Artboard tool's active artboard while it is in use, else the window's; its `x` and `y`
|
||||
@@ -4971,6 +5095,7 @@ for a new artboard, with `new: true` and the name, position and size that `artbo
|
||||
`artboard.new` with the fields and makes the new artboard the active one, and Cancel adds no artboard.
|
||||
`artboard.new` and `artboard.setProps` trim the white space around `name`, and a blank name gives the artboard its
|
||||
default name, "Artboard N" for the Nth artboard.
|
||||
A canvas-click dialog also has `preview` (initially on): tentative settings draw at the remembered click, Reset refreshes that preview, and Cancel discards it without persisting settings. OK accepts one flare as one Undo step. Toolbar-only options draw no artwork.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"tool.setOption","params":{"tool":"flare","values":{"rays":24,"ringsOn":false}}}}
|
||||
@@ -5017,6 +5142,8 @@ under the opposite corner, else of the front-most selected object. A Shift-click
|
||||
as a click does, so with `clickStrokeSplitsPath` on, a Shift-click on a stroke splits it. A marquee splits no stroke,
|
||||
however little the pointer moves: the tool sends no `splitStroke` with `rect`, and `shapeBuilder.merge` does not split
|
||||
a stroke when it has `rect`. With both `rect` and `points`, the command also touches what the points touch.
|
||||
`press_key` Escape during a Shape Builder drag, a marquee included, cancels it: the release sends no
|
||||
`shapeBuilder.merge`.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"shapeBuilder.merge","params":{"rect":[50,50,200,200]}}}
|
||||
@@ -5068,7 +5195,8 @@ horizon; with Lock Station Point the other one swings), the horizon, the third v
|
||||
and right ground-level points (24 px from the origin along each wall's ground line: they move the whole grid; Shift
|
||||
keeps one axis), the left and right extents (separate: `extent` and `extentRight`; Alt drags both, Shift goes by
|
||||
whole cells), the vertical extent and the cell size widget (on the line where the planes meet, a cell up, or more
|
||||
cells while they're small) reshape the grid; Lock Grid stops them. The Plane Switching Widget stays put in a corner
|
||||
cells while they're small) reshape the grid; Lock Grid stops them. `press_key` Escape during a drag of one of them,
|
||||
or of a plane widget, cancels it and puts the grid back. The Plane Switching Widget stays put in a corner
|
||||
of the document window (headless: the first artboard's top-left corner); a press on it picks the plane with any tool
|
||||
while the grid shows (choosing a perspective tool shows it, and `perspective.grid.show` hides it again with the tool
|
||||
still chosen), and the keys 1–4 (`key` with `"1"`…`"4"`) pick the left, horizontal, right and no plane.
|
||||
@@ -5088,7 +5216,10 @@ the options, `editing`), or with no envelope selected the options new envelopes
|
||||
the current shape) switch its kind and keep its content. `object.envelope.options` sets `fidelity` and the options
|
||||
(`antiAlias`, `preserveShape`: clippingMask or transparency, `distortAppearance`, `distortLinearGradients`,
|
||||
`distortPatternFills`; the last two need `distortAppearance`); with no envelope selected it sets them for new
|
||||
envelopes. New envelopes start with Distort Appearance on, as in the reference app; envelopes saved before these
|
||||
envelopes. With `antiAlias` false, the envelope's distorted images are drawn without smoothing, SVG export writes
|
||||
them with `image-rendering: pixelated` and PDF export writes them without `/Interpolate`. With `preserveShape`
|
||||
`transparency`, each piece of a distorted image is the image with a white opacity mask in the piece's shape instead
|
||||
of a clip group, after Expand too. New envelopes start with Distort Appearance on, as in the reference app; envelopes saved before these
|
||||
options existed keep their old look (appearance applied after the distortion).
|
||||
|
||||
`object.envelope.release` gives back the content and the envelope's shape (a grey gradient mesh for warp and mesh
|
||||
@@ -5121,7 +5252,8 @@ Brush Dimensions shared by the seven tools (`width`, `height` in pt, `angle`, `i
|
||||
`showBrush`) and the tool's own: `detail` (1..10); `simplify` (0..100) with `simplifyOn` (Warp, Twirl, Pucker,
|
||||
Bloat); `rate` (Twirl, -180..180°); `complexity` (0..15) and `affectAnchors`, `affectIn`, `affectOut` (Scallop,
|
||||
Crystallize, Wrinkle: what the brush moves); `horizontal`, `vertical` (Wrinkle, 0..1). Alt-drag sizes the brush from
|
||||
its current size (with Shift too it keeps its proportions). With Use Pressure Pen on, each `pointer_gesture` event's
|
||||
its current size (with Shift too it keeps its proportions). `press_key` Escape during a stroke cancels it and adds no
|
||||
undo step; during an Alt-drag it puts the brush size back. With Use Pressure Pen on, each `pointer_gesture` event's
|
||||
`pressure` (0..1) is the intensity there, and `object.liquify` takes points as `[x, y, pressure]`.
|
||||
|
||||
```json
|
||||
@@ -5161,7 +5293,8 @@ distance along the plane's normal), so copies, duplicates and pastes stay attach
|
||||
`perspectiveSelection` tool, a drag moves the selection within its plane (`perspective.move`; Alt copies), the
|
||||
bounding-box handles scale it in plane space (`perspective.transform`; Shift keeps proportions, Alt scales about the
|
||||
centre), and `press_key` `5` during a drag switches the move to perpendicular to the plane (press again to switch
|
||||
back). The arrow keys nudge a selection in perspective by the keyboard increment (`perspective.nudge`; Shift ×10, Alt
|
||||
back). `press_key` Escape during a move, a resize or a plane widget drag cancels it and puts the objects and the
|
||||
grid back. The arrow keys nudge a selection in perspective by the keyboard increment (`perspective.nudge`; Shift ×10, Alt
|
||||
copies). Object › Transform › Transform Again repeats the last perspective move or scale in plane space.
|
||||
|
||||
```json
|
||||
@@ -5215,7 +5348,9 @@ shows, and so do the click-to-size dialogs (`perspective.draw` with `at`: the cl
|
||||
`object.blend.make` blends the selected objects (or `ids`) into a live blend. Spacing and orientation default to
|
||||
what `object.blend.options` set with no blend selected (`object.blend.info` reads them: `target: "defaults"`),
|
||||
else Smooth Color and Align to Page. `starts` gives, per object, the anchor of its first subpath the blend starts
|
||||
from (as the Blend tool's clicks on anchor points do; an open path's last anchor runs it the other way). A blend
|
||||
from (as the Blend tool's clicks on anchor points do; an open path's last anchor runs it the other way). With the
|
||||
Blend tool, `press_key` Escape drops the object clicked first and stops the next click adding to the blend
|
||||
just made; the document and the selection stay as they are. A blend
|
||||
among the objects takes the others in as more key objects, keeping its options, name and transparency:
|
||||
|
||||
```json
|
||||
@@ -5307,7 +5442,8 @@ right/left/down/up; none stored means the smooth mesh through the points, as old
|
||||
`object.envelope.setMeshPoint {id, index, x, y, handle?}` and `object.mesh.movePoint`, `object.mesh.addLine` and
|
||||
`object.mesh.deletePoint` edit them like a gradient mesh's (one undo step each). On the canvas a selected envelope
|
||||
shows its mesh; the Mesh tool and Direct Selection drag its points and the clicked point's handles, and the Mesh tool
|
||||
adds a row and a column where it clicks inside. The Control bar shows Edit Envelope / Edit Contents, the warp's
|
||||
adds a row and a column where it clicks inside. `press_key` Escape during a drag of a point or handle of a mesh
|
||||
envelope or a gradient mesh cancels it: the point goes back and no undo step is added. The Control bar shows Edit Envelope / Edit Contents, the warp's
|
||||
style, orientation, bend and distortions (or the mesh's rows and columns), Reset (an unbent warp, a flat mesh) and
|
||||
Envelope Options.
|
||||
|
||||
|
||||
+11
-3
File diff suppressed because one or more lines are too long
@@ -13,10 +13,14 @@ Click once to select the group; click again to select its visible fill and chang
|
||||
|
||||
While the group or one of its faces is selected (Face Selection mode) and the Shaper or Selection tool is active, an arrow widget sits beside the middle of the right side of the original shapes’ bounds. Click it to enter Construction mode: the original shapes are isolated and nothing is selected, and a click with Shaper selects the group’s original shape under the pointer. Click the widget again to leave Construction mode with the group selected. The arrow points right in Face Selection mode and left in Construction mode.
|
||||
|
||||
To take a shape out of a Shaper Group, drag it with the Selection tool in Construction mode and drop it wholly outside the bounds of the group's original shapes. The shape becomes a path just above the group, keeps its fill and stroke and stays selected, and Construction mode ends. The group is computed again from the shapes left, and a group left with no shapes is removed. The move and the removal are one undo step, Remove from Shaper Group. A shape dropped across the bounds only moves.
|
||||
|
||||
Smart Guides and Snap to Point do not snap to the original shapes. The reference point of the transform tools snaps to an original shape only when that shape is selected, or under the pointer in Construction mode. Outside Construction mode the Lasso does not pick the anchor points of the original shapes, and the Add Anchor Point, Delete Anchor Point, Anchor Point and Scissors tools do not pick the anchor points or segments of an original shape that is not selected.
|
||||
|
||||
Object › Shaper › Release restores the original paths. Expand keeps the visible result as ordinary paths. Each scribble and source edit records one undo step. Sources in a composition must share a parent layer/group; Shaper limits a composition to 64 source paths and 4,096 segments.
|
||||
|
||||
While an opacity mask or a pattern is edited, its editing layer stays isolated and the arrow widget does not show. A double-click with Shaper on a Shaper Group in the mask or pattern art selects the original shape under the pointer without isolating the group's original shapes, the Selection tool moves that selected shape, and a scribble, Release and Expand leave the editing layer isolated.
|
||||
|
||||
Finer gesture heuristics remain to be implemented.
|
||||
|
||||
Behavior reference: [public Shaper documentation](https://helpx.adobe.com/illustrator/using/building-new-shapes-using-shape.html). Rotation steps and independent fill/stroke cases follow the contributor’s supplied requirements. No reference images or product assets are included.
|
||||
|
||||
File diff suppressed because one or more lines are too long
+3
-2
File diff suppressed because one or more lines are too long
@@ -99,6 +99,7 @@ tools, never checked side by side) isn't captured row by row.
|
||||
| Smooth tool smooths only the stretch of path under the drag | done | #1512 |
|
||||
| Drawing modes apply to pastes: Draw Inside takes Paste, Paste in Place and Paste on All Artboards inside the object; Draw Behind puts Paste in Place and Paste on All Artboards behind the selection; Shift+D cycles past Draw Behind with any selection ([drawing modes](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/learn-drawing-basics/drawing-modes-overview.html)) | done | #3705; `pasting_in_draw_inside_puts_the_copies_inside_the_object`, `paste_in_place_in_draw_behind_goes_behind_the_selection`, `shift_d_goes_past_draw_behind_whatever_is_selected` |
|
||||
| Simplify (slider, auto, corner angle threshold), of a whole path or of the part of it selected with Direct Selection ([manually simplify paths](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html)) | done | Tolerance, default-on Preview and original/new point counts (#1272); Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path; the slider under the selection with Auto-Simplify and More Options, opening at the Auto-Simplify level for the selection with that result previewed, and the dialog's option to open it directly with its last settings. With anchors or segments direct-selected, the slider, Auto-Simplify and the dialog refit only each run of selected segments between its end anchors, a closed path's run across its start point included; the rest of the path stays exactly as it was (`tests/simplify_part.rs`, `simplify_slider_and_dialog_simplify_only_the_direct_selected_part_of_a_path`) |
|
||||
| Smooth (Object › Path › Smooth…: slider from minimum to maximum smoothing, Auto-Smooth) ([smooth slider](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/refine-path-segments-with-the-smooth-slider.html)) | done | #4115: the slider under the selection previews `object.path.smooth {level}`, Auto-Smooth picks level 50, Enter or a click outside keeps one undo step and Escape restores; the smoothing rule beyond the two ends is VectorCraft's own (`tests/smooth_path.rs`, `smooth_slider_previews_auto_smooth_and_keeps_the_result`) |
|
||||
| Draw Inside in isolation mode: art drawn or pasted inside the isolated object takes isolation mode into its clip group, clickable and undimmed ([isolation mode](https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/isolate-objects.html), [drawing modes](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/learn-drawing-basics/drawing-modes-overview.html)) | done | #3705; `drawing_inside_an_isolated_object_keeps_the_new_art_in_isolation_mode` |
|
||||
| Simplify (slider, auto, corner angle threshold) | partial | Tolerance, default-on Preview and original/new point counts (#1272); Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path; the slider under the selection with Auto-Simplify and More Options, and the dialog's option to open it directly with its last settings; the slider opens at 1 pt rather than at the Auto-Simplify level, and simplifying only a Direct Selection part of a path remains missing |
|
||||
| Simplify (slider, auto, corner angle threshold) | partial | Tolerance, default-on Preview and original/new point counts (#1272); Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path; the slider under the selection with Auto-Simplify and More Options, opening at the Auto-Simplify level for the selection with that result previewed, and the dialog's option to open it directly with its last settings; simplifying only a Direct Selection part of a path remains missing |
|
||||
@@ -255,6 +256,9 @@ Cmd/Ctrl+Alt+N (`file.newWithoutDialog`, New Without Dialog in the shortcut edit
|
||||
settings the New Document dialog opens on (the first Recent entry, or the Print category's Letter while Recent is
|
||||
empty), without opening the dialog.
|
||||
|
||||
Cmd/Ctrl+Alt+Shift+U (`document.switchUnits`, Switch Units in the shortcut editor) switches the document to the
|
||||
next unit in the order a right-click on a ruler lists them, from Feet back to Points.
|
||||
|
||||
## Panels, Control bar, Properties, context menus
|
||||
|
||||
| Interaction | Status | Evidence |
|
||||
@@ -354,6 +358,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-29 | minor | Object › Path › Smooth… with its slider and Auto-Smooth (#4115); scores and estimates unchanged |
|
||||
| 2026-10-25 | minor | Selection tool: a double-click on a threaded port breaks the thread there (`text.thread.break`); scores and estimates unchanged |
|
||||
| 2026-10-25 | minor | A selected Grid Repeat shows handles: the one below the grid adds or removes rows, the one right of it columns, the one past its bottom-right corner both, and the sliders between the first two columns and rows set the horizontal and vertical spacing; each drag is one undo step and Escape cancels it (#2691); headless canvas and tool regressions; scores and estimates unchanged. |
|
||||
| 2026-10-25 | minor | Floating panels open in OS windows of their own on the desktop (inside the window on the web and on Wayland); saved layouts from earlier versions convert to the shared docking layout (#4135); scores and estimates unchanged (estimated) |
|
||||
@@ -480,6 +485,8 @@ separate. Scores and hours unchanged. [Behavior and command details](development
|
||||
| 2026-10-11 | minor | Edit Toolbar opens the All Tools drawer beside the toolbar instead of a centred dialog, dims the tools the toolbar holds, and edits the toolbar by drag and drop while it is open (#1018); score unchanged (estimated) |
|
||||
| 2026-10-11 | minor | Direct Selection: dragging a path selected whole by an anchor moves it whole; Alt-drag copies by an anchor too, toggles during the drag and snaps onto the original (#1104); score unchanged (estimated) |
|
||||
| 2026-10-10 | major | First checklist, built from the tool sources and tests against Illustrator 2026's public documentation |
|
||||
| 2026-10-28 | minor | Blob Brush Shift locks after clear axial movement, keeps earlier samples fixed, and waits without an off-axis trail. Activation uses raw press displacement in logical screen pixels, with exact quarter-turn boundaries. Explicit application rule; scores and estimates unchanged. |
|
||||
| 2026-10-28 | minor | Flare Tool Options opened by a canvas click previews its settings at that click. Reset refreshes the enabled preview while keeping the dialog open. Cancel discards preview art and tentative options; OK accepts one flare in one Undo step. Editing existing flare artwork remains outside this dialog scope. Scores and estimates unchanged. |
|
||||
|
||||
> **Tab-drag status (2026-10-22):** updated from upstream, including the Turkish catalog and all three tab-drag translations requested in #3479; Selection-tool reset regression-checked and clipboard evidence scoped to internal state; drag lifecycle, insertion and regression contracts documented; behavior, scores and estimates unchanged.
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
# Windows 7 x64 compatibility build (experimental, unsupported)
|
||||
|
||||
Windows 7 is not a supported platform. This build is experimental and community-maintained,
|
||||
and the VectorCraft maintainers have not verified it on Windows 7.
|
||||
|
||||
This separate build targets **64-bit Windows 7 SP1**. It uses OpenGL instead of wgpu,
|
||||
and omits the native AccessKit accessibility adapter. Printer discovery supports the
|
||||
PowerShell version included with Windows 7, and printer setup opens Control Panel.
|
||||
Printing retains the PDF viewer's Print/PrintTo verb on this build: Windows 7 has no
|
||||
Windows.Data.Pdf renderer. The win7 target excludes `craft-print` from its dependency
|
||||
tree; normal desktop builds use the shared spooler. The `windows7` feature and dedicated win7 target must be selected together; the
|
||||
existing compile-time guards reject mixing this feature with the ordinary Windows target.
|
||||
The normal desktop and web builds retain their existing defaults. A vendor graphics driver providing **OpenGL 3.3 or newer**
|
||||
is required; the Windows built-in generic display driver is insufficient. GPU selection
|
||||
in Preferences and `WGPU_POWER_PREF` do not control this OpenGL build.
|
||||
|
||||
## Build or download
|
||||
|
||||
In your GitHub fork, open **Actions → Windows 7 x64 compatibility → Run workflow** (it
|
||||
also runs weekly and on pull requests changing the compatibility path).
|
||||
When it succeeds, download the `vectorcraft-windows7-x64-portable` artifact, extract the
|
||||
contained portable ZIP, then run `vectorcraft.exe` on Windows 7. The CLI is included.
|
||||
No MSI is provided: the existing installer remains for modern Windows.
|
||||
|
||||
To build locally, use a **Windows 10/11 build computer**, with Rust/rustup and Visual Studio
|
||||
2022 C++ build tools (x64 compiler and Windows SDK). Open an **x64 Native Tools Command
|
||||
Prompt for VS 2022**, change to your clone's repository root (the directory containing
|
||||
`Cargo.toml`), and run:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File packaging/windows/windows7.ps1
|
||||
```
|
||||
|
||||
Add `-Test` to also run the app and CLI tests on the build computer.
|
||||
|
||||
Output: `dist/release/vectorcraft-windows7-x64-portable.zip`. The script installs the pinned
|
||||
nightly compiler and `rust-src`, rebuilds the standard library for
|
||||
`x86_64-win7-windows-msvc`, and links the C runtime statically. It never installs Rust on
|
||||
the Windows 7 machine. The optional `CRAFT_FONTS_DIR` setting works as in the normal build.
|
||||
Without it, install fonts for the languages you need on the destination machine.
|
||||
|
||||
The Windows 7 target matters: modern Rust's ordinary `x86_64-pc-windows-msvc` target
|
||||
requires Windows 10, including standard-library and random-number APIs. Selecting OpenGL
|
||||
alone or changing an installer version check cannot fix that.
|
||||
|
||||
References: [Rust's Windows 7 target](https://doc.rust-lang.org/rustc/platform-support/win7-windows-msvc.html)
|
||||
and [Windows baseline change](https://blog.rust-lang.org/2024/02/26/Windows-7/).
|
||||
|
||||
The packaging script checks x64 PE headers, the subsystem version, and known incompatible
|
||||
imports (including `GetSystemTimePreciseAsFileTime`) before creating the ZIP. This is a
|
||||
regression check, not an exhaustive audit of every Windows API.
|
||||
|
||||
The small vendored `windows-link` patch routes `CoTaskMemFree` to its documented
|
||||
`ole32.dll` export on the Windows 7 target; the newer binding otherwise imports
|
||||
`combase.dll`, which is absent on Windows 7. Only this script uses it: it passes
|
||||
`--config "patch.crates-io.windows-link.path='vendor/windows-link'"` to Cargo, so every other
|
||||
build, including the normal Windows one, keeps the checksum-verified crates.io crate. Cargo
|
||||
records the patch in `Cargo.lock`, so the script resolves once, fails if anything other than
|
||||
windows-link's source changed, builds with `--locked`, and then restores `Cargo.lock`.
|
||||
|
||||
## Runtime validation
|
||||
|
||||
The contributor reports that a user ran the portable x64 compatibility build on Windows 7 SP1
|
||||
with an NVIDIA graphics card. Their original build failed at startup with
|
||||
`GetSystemTimePreciseAsFileTime` missing from `kernel32.dll`; the compatibility
|
||||
build resolved that failure. The tested build was based on upstream commit
|
||||
`1f7873273b67007b0aefc63313e0d34a39f45453`. The exact GPU/driver version and completion
|
||||
of every checklist item below were not recorded, and the maintainers have not reproduced it,
|
||||
so the build remains experimental and unsupported.
|
||||
|
||||
## Runtime acceptance checklist
|
||||
|
||||
A successful build on the Windows 2022 CI runner does **not** prove Windows 7 runtime
|
||||
compatibility. Before declaring support, test on a Windows 7 SP1 x64 machine and record
|
||||
its GPU, driver version, Windows updates and any missing-DLL/entry-point error:
|
||||
|
||||
- Start `vectorcraft.exe`; verify a visible canvas, resizing, minimize/restore and exit.
|
||||
- Draw a shape, save a `.vectorcraft` document, close and reopen it.
|
||||
- Open `examples/dusk-poster.vectorcraft`; export PNG and PDF and inspect both.
|
||||
- Exercise native Open/Save dialogs, clipboard text/images and drag-and-drop.
|
||||
- Change a preference, exit and verify it survives restarting.
|
||||
- Run `vectorcraft-cli.exe --version` and a headless export from a Command Prompt in the
|
||||
extracted application folder.
|
||||
|
||||
This build is experimental until that checklist passes. New dependency versions can add
|
||||
newer Windows imports, so retain Cargo.lock and repeat runtime QA after dependency updates.
|
||||
Reference in New Issue
Block a user