Merge pull request #275 from storytold/fix/issue-257

Fix #257: format lists name every format the engine reads and writes
This commit is contained in:
BFlat authored and GitHub committed 2026-10-07 12:13:50 +07:00
commit ba2ee7a174
6 files changed
+59 -12

No files matched your search

+1 -1
View File
@@ -88,7 +88,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist
- **Automation:**
- Actions panel (record/playback, persisted), generic parameter dialogs for every "…" command.
- JSON-lines control channel with real egui pointer and keyboard injection.
- MCP server with 25 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, any command), attached to the running app or headless.
- MCP server with 25 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, any command), attached to the running app or headless. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings.
- MCP protocol: five prompts (`prompts/list` / `prompts/get`: poster, icon set, recolour, trace-and-style, export set), `completion/complete` for prompt arguments and template variables read from the live catalogues (formats, effect ids, command ids, trace presets, swatch and object ids), four resource templates (`vectorcraft://object/{id}`, `//command/{id}`, `//effect/{id}`, `//swatch/{name}`) so an agent reads one thing instead of the whole document, and `logging/setLevel` with `notifications/message`. `docs/mcp.md` has a Protocol section covering the capability table and what is deliberately absent (subscriptions, progress, cancellation, sampling, elicitation, Streamable HTTP — all of which need the transport to interleave reads).
- Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`).
- Actions panel that records and plays back commands.
+10 -8
View File
@@ -2,7 +2,7 @@
//!
//! ```text
//! vectorcraft-cli mcp [--connect 127.0.0.1:7979 | --headless]
//! vectorcraft-cli run [--in FILE] [--cmd id [--params '{json}']]... [--export out.svg|.png|.pdf|.jpg|.webp|.vectorcraft]... [--scale 2]
//! vectorcraft-cli run [--in FILE] [--cmd id [--params '{json}']]... [--export out.svg]... [--scale 2]
//! vectorcraft-cli commands
//! vectorcraft-cli convert IN OUT [--scale 2] [--artboard 0 | --range 1-3,5] [--outline-text]
//! vectorcraft-cli info FILE
@@ -59,16 +59,17 @@ USAGE:
vectorcraft-cli run [--in FILE] [--cmd ID [--params JSON]]... [--export FILE]... [--scale N]
Headless batch: open FILE (any readable format) or start a new document, run commands in
order, export (.svg, .png, .pdf, .jpg, .webp, .vectorcraft by extension). Prints one JSON result per step.
order, export each FILE in the format its extension picks (see Writable formats). Prints one
JSON result per step.
vectorcraft-cli commands
Print the command catalogue as JSON.
vectorcraft-cli convert IN OUT [--scale N] [--artboard I | --range R] [--outline-text]
Open IN (any readable format) and export OUT by extension (.svg, .pdf, .png, .jpg, .webp,
.vectorcraft). --artboard is 0-based, --range 1-based (\"1-3,5\"); a PDF gets every artboard
unless one of them is given, the other formats the first. Live effects are kept;
--outline-text writes SVG text as paths.
Open IN (any readable format) and export OUT in the format its extension picks (see Writable
formats). --artboard is 0-based, --range 1-based (\"1-3,5\"); a PDF gets every artboard
unless one of them is given, EPS the bounds of the art, the other formats the first artboard.
Live effects are kept; --outline-text writes SVG text as paths.
vectorcraft-cli info FILE
Print a JSON summary: title, colour mode, units, artboards, object counts by kind, fonts.
@@ -82,9 +83,10 @@ USAGE:
synthetic N-path document (default 50000). Exits non-zero if a budget is exceeded.
";
/// The usage text plus the formats `document.open` reads.
/// The usage text plus the formats `document.open` reads and `document.export` writes.
fn usage() -> String {
format!("{USAGE}\nReadable formats: .{}\n", vectorcraft_engine::cmd::fileio::OPEN_EXTS.join(", ."))
use vectorcraft_engine::cmd::fileio::{OPEN_EXTS, export_extensions};
format!("{USAGE}\nReadable formats: .{}\nWritable formats: .{}\n", OPEN_EXTS.join(", ."), export_extensions().join(", ."))
}
fn main() -> ExitCode {
+12
View File
@@ -581,6 +581,18 @@ pub const OPEN_EXTS: &[&str] = &[
"eps",
];
/// The extension that picks each writable format when exporting (the format's first; PNG-8 shares
/// `.png` with PNG, so `.png` comes once), in [`FORMATS`] order.
pub fn export_extensions() -> Vec<&'static str> {
let mut v: Vec<&'static str> = Vec::new();
for e in FORMATS.iter().filter(|f| f.write).filter_map(|f| f.extensions.first()) {
if !v.contains(e) {
v.push(e);
}
}
v
}
/// Text files: File → Place sets them as area type (Text Import Options).
pub const TEXT_EXTS: &[&str] = &["txt"];
+33
View File
@@ -150,6 +150,39 @@ fn open_exts_cover_every_readable_format() {
assert_eq!(filters.last(), Some(&("Plug-ins", crate::cmd::plugin::EXTS)), "File › Open installs plug-ins");
}
/// The extensions usage texts list for export: one per writable format, each picking that format
/// (`.png` picks PNG; PNG-8 is asked for by `format`).
#[test]
fn export_extensions_name_every_writable_format() {
let exts = export_extensions();
assert_eq!(
exts,
[
"vectorcraft",
"svg",
"svgz",
"pdf",
"png",
"jpg",
"gif",
"webp",
"tif",
"bmp",
"vctemplate",
"txt",
"dxf",
"eps",
"emf",
"wmf",
"tga",
"psd"
]
);
for e in exts {
assert!(format_for_name(&format!("x.{e}")).is_some_and(|f| f.write), "{e}");
}
}
#[test]
fn formats_query_lists_readers_writers_and_options() {
let mut s = Session::new();
+1 -1
View File
@@ -275,7 +275,7 @@ pub fn tool_definitions() -> Vec<Value> {
tool(
"export",
"Export",
"Export the active document with the engine's document.export (the same bytes in the app and headless): svg (artboard viewBox), pdf (one page per artboard: all, or `artboard` / `range`), png/jpg/webp/gif/png8/tiff/bmp/tga/psd (one rendered artboard; png8 is an indexed .png; psd keeps the layers), txt (the stories, back to front) or vectorcraft (a native copy; the document keeps its path). `selection: true` exports only the selected objects, cropped to their bounds. Template layers are left out; live effects are kept (geometry baked, SVG filters for shadows/glows/blur). Without `path` the bytes come back as dataBase64.",
"Export the active document with the engine's document.export (the same bytes in the app and headless), in any format of the `format` enum: vectors (svg/svgz with the artboard as viewBox, eps, dxf, emf, wmf), pdf (one page per artboard: all, or `artboard` / `range`), rasters (one rendered artboard: png, jpg, webp, gif, png8 as an indexed .png, tiff, bmp, tga, psd with its layers), txt (the stories, back to front), vectorcraft or template (a native copy or template; the document keeps its path). run_command document.formats lists each format's options. `selection: true` exports only the selected objects, cropped to their bounds. Template layers are left out; live effects are kept (geometry baked, SVG filters for shadows/glows/blur). Without `path` the bytes come back as dataBase64.",
obj(
json!({
"format": {"type": "string", "enum": FORMATS.iter().filter(|f| f.write).map(|f| f.id).collect::<Vec<_>>(), "description": "Default: from the path's extension"},
+2 -2
View File
@@ -159,9 +159,9 @@ objects' fills or strokes differ (`fillMixed` / `strokeMixed`, drawn as a "?" pr
| `invoke_menu` | `{command, params?}` | Invokes a menu item by command id. Includes UI commands such as `view.*` and `window.*` in remote mode. |
| `open_panel` | `{panel}` | Remote only. |
| `screenshot` | `{path?, scale?, artboard?, window?}` | Returns MCP image content (`image/png`, base64) plus a text block. Renders the artboard; `window:true` captures the app window (remote only). |
| `open_file` | `{path}` | Opens any readable file as a new active document: `.vectorcraft`/`.drawcraft`, `.svg`/`.svgz`, `.pdf`/`.ai`, `.ait`, PNG, JPEG, GIF, WebP, TIFF, BMP (an image opens as a document of its pixel size). Templates open as a new untitled document. PDF, `.ai` and SVG files saved with Preserve Editing reopen as the document they carry. `run_command document.formats` lists the formats. |
| `open_file` | `{path}` | Opens any readable file as a new active document: `.vectorcraft`/`.drawcraft`, `.vctemplate`, `.svg`/`.svgz`, `.pdf`/`.ai`, `.ait`, `.eps`, `.dxf`, `.emf`, `.wmf`, PNG, JPEG, GIF, WebP, TIFF, BMP (an image opens as a document of its pixel size). Templates (`.vctemplate`, `.ait`) open as a new untitled document. PDF, `.ai` and SVG files saved with Preserve Editing reopen as the document they carry. `run_command document.formats` lists the formats. |
| `save_file` | `{path?}` | Runs `document.save`: the document's own file in its own format (native `.vectorcraft` unless it was opened from or saved as SVG, PDF or a restorable `.ai`; then `warnings` say what that format loses). A path's extension picks the format (`.vectorcraft`, `.vctemplate`, `.pdf`, `.svg`, `.svgz`, `.ai`: a PDF carrying the native document, which reopens editable). |
| `export` | `{path?, format?, scale?, artboard?, range?, selection?, outlineText?, options?}` | `svg`, `pdf`, `png`, `jpg`, `webp`, `gif`, `png8` (an indexed `.png`), `tiff`, `bmp`, `tga`, `psd` (layered), `txt` (the document's text) or `vectorcraft` (the list comes from `document.formats`). When `format` is omitted, it comes from the path's extension. PDF writes one page per artboard: all of them, or `artboard` (0-based) / `range` (`"1-3, 5"`, 1-based); the other formats write one artboard. `options` carries more format options (e.g. `{"quality": 80}` for JPEG). `selection: true` exports the selected objects cropped to their bounds (the reply adds their `bounds`, and reports `format` and the encoder's `warnings` as a whole-document export does); `outlineText: true` writes SVG text as paths. Template layers are left out, live effects are kept, and exporting `vectorcraft` never changes the document's path. Without `path` the bytes come back as `dataBase64`. Both backends run the same `document.export` call. |
| `export` | `{path?, format?, scale?, artboard?, range?, selection?, outlineText?, options?}` | `svg`, `svgz`, `pdf`, `eps`, `dxf`, `emf`, `wmf`, `png`, `jpg`, `webp`, `gif`, `png8` (an indexed `.png`), `tiff`, `bmp`, `tga`, `psd` (layered), `txt` (the document's text), `vectorcraft` or `template` (a native template). The tool's `format` enum and `document.formats` list them, generated from the engine's format table. When `format` is omitted, it comes from the path's extension. PDF writes one page per artboard: all of them, or `artboard` (0-based) / `range` (`"1-3, 5"`, 1-based); the other formats write one artboard. `options` carries more format options (e.g. `{"quality": 80}` for JPEG). `selection: true` exports the selected objects cropped to their bounds (the reply adds their `bounds`, and reports `format` and the encoder's `warnings` as a whole-document export does); `outlineText: true` writes SVG text as paths. Template layers are left out, live effects are kept, and exporting `vectorcraft` never changes the document's path. Without `path` the bytes come back as `dataBase64`. Both backends run the same `document.export` call. |
| `add_text` | `{text, x?, y?, width?, height?, path?, mode?, pathEffect?, size?, font?, color?}` | Point type at (x, y); area type with `width`/`height`; or `path` + `mode` (`area`/`onPath`) to flow text in or along a path, with `pathEffect` (`rainbow`, `skew`, `3dRibbon`, `stairStep`, `gravity`). |
| `apply_effect` | `{effect?, params?, ids?}` | Appends a live effect. Without `effect`, returns the effect catalogue with parameters and defaults. |
| `pathfinder` | `{operation, ids?}` | `unite`, `minusFront`, `intersect`, `exclude`, `divide`, `trim`, `merge`, `crop`, `outline`, `minusBack`. For a live version, apply the `pathfinder.*` effect to a group (`effect.apply` groups several loose selected objects first). |