M4.12: one loader and encoder for every frontend — engine fileio (load/encode/export/save, append-only FORMATS table, OPEN_EXTS, document.formats query, typed per-format options, parse_range); document.open reads native/legacy, SVG/SVGZ, PDF/.ai, .ait (opens untitled like native templates) and PNG/JPEG/GIF/WebP/TIFF/BMP; desktop, web, control channel, headless MCP and the CLI open and encode through it; open dialogs list "All readable files" plus one filter per format

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bflatasticandClaude Opus 5.5 committed 2026-10-04 11:36:51 +07:00
1 parent d99853ed2a
commit 4d503c0327
25 files changed
+1293 -559

No files matched your search

+12 -7
View File
@@ -2,7 +2,7 @@
//!
//! ```text
//! vectorcraft-cli mcp [--connect 127.0.0.1:7979 | --headless]
//! vectorcraft-cli run [--in file.vectorcraft|file.svg] [--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|.png|.pdf|.jpg|.webp|.vectorcraft]... [--scale 2]
//! vectorcraft-cli commands
//! vectorcraft-cli convert IN OUT [--scale 2] [--artboard 0] [--outline-text]
//! vectorcraft-cli info FILE
@@ -28,21 +28,21 @@ USAGE:
(vectorcraft --control 7979), falling back to a headless in-process session.
vectorcraft-cli run [--in FILE] [--cmd ID [--params JSON]]... [--export FILE]... [--scale N]
Headless batch: open FILE (.vectorcraft/.svg) or start a new document, run commands in
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.
vectorcraft-cli commands
Print the command catalogue as JSON.
vectorcraft-cli convert IN OUT [--scale N] [--artboard I] [--outline-text]
Open IN (.vectorcraft, .svg, .pdf/.ai, images) and export OUT by extension (.svg, .pdf, .png,
.jpg, .webp, .vectorcraft). Live effects are kept; --outline-text writes SVG text as paths.
Open IN (any readable format) and export OUT by extension (.svg, .pdf, .png, .jpg, .webp,
.vectorcraft). 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.
vectorcraft-cli bench FILE [--size WxH] [--iters N]
Render FILE (.vectorcraft/.svg) fitted to WxH (default 2880x1800) and print ms per frame
Render FILE (any readable format) fitted to WxH (default 2880x1800) and print ms per frame
(warm), multithreaded and single-threaded.
vectorcraft-cli perf [--paths N]
@@ -50,6 +50,11 @@ USAGE:
synthetic N-path document (default 50000). Exits non-zero if a budget is exceeded.
";
/// The usage text plus the formats `document.open` reads.
fn usage() -> String {
format!("{USAGE}\nReadable formats: .{}\n", vectorcraft_engine::cmd::fileio::OPEN_EXTS.join(", ."))
}
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
let r = match args.first().map(String::as_str) {
@@ -61,14 +66,14 @@ fn main() -> ExitCode {
Some("bench") => bench(&args[1..]),
Some("perf") => perf::run(&args[1..]),
Some("-h" | "--help" | "help") | None => {
print!("{USAGE}");
print!("{}", usage());
Ok(())
}
Some("-V" | "--version") => {
println!("vectorcraft-cli {}", env!("CARGO_PKG_VERSION"));
Ok(())
}
Some(other) => Err(format!("unknown subcommand `{other}`\n\n{USAGE}")),
Some(other) => Err(format!("unknown subcommand `{other}`\n\n{}", usage())),
};
match r {
Ok(()) => ExitCode::SUCCESS,
+75
View File
@@ -0,0 +1,75 @@
//! The CLI opens every readable format (PDF, .ai, images) through the engine's `document.open`.
use std::process::Command;
use serde_json::Value;
const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft-cli");
fn tmp(name: &str) -> String {
let dir = std::env::temp_dir().join(format!("vectorcraft-cli-fileio-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir.join(name).to_string_lossy().to_string()
}
fn ok(args: &[&str]) -> String {
let out = Command::new(BIN).args(args).output().unwrap();
assert!(out.status.success(), "{args:?}: {}", String::from_utf8_lossy(&out.stderr));
String::from_utf8(out.stdout).unwrap()
}
/// `run` a new `width`×`height` document with a rectangle and export it to `out`.
fn make(out: &str, width: u32, height: u32) {
ok(&[
"run",
"--cmd",
"file.new",
"--params",
&format!(r#"{{"width":{width},"height":{height},"artboards":2}}"#),
"--cmd",
"shape.rectangle",
"--params",
r#"{"x":1,"y":1,"width":2,"height":1}"#,
"--export",
out,
]);
}
fn info(file: &str) -> Value {
serde_json::from_str(&ok(&["info", file])).unwrap()
}
#[test]
fn convert_pdf_to_svg_and_info_on_ai() {
let pdf = tmp("in.pdf");
make(&pdf, 120, 80);
let svg = tmp("out.svg");
ok(&["convert", &pdf, &svg]);
assert!(std::fs::read_to_string(&svg).unwrap().contains("<svg"));
let ai = tmp("in.ai");
std::fs::copy(&pdf, &ai).unwrap();
let v = info(&ai);
assert_eq!(v["artboards"].as_array().unwrap().len(), 2, "{v}");
assert_eq!(v["artboards"][0]["rect"][2], 120.0);
// `run --in` takes it too.
ok(&["run", "--in", &ai, "--cmd", "document.inspect"]);
}
#[test]
fn images_open_at_their_pixel_size() {
let webp = tmp("tiny.webp");
make(&webp, 3, 2);
let v = info(&webp);
assert_eq!(v["artboards"][0]["rect"], serde_json::json!([0.0, 0.0, 3.0, 2.0]), "{v}");
assert_eq!(v["kinds"]["Image"], 1, "{v}");
let png = tmp("tiny.png");
ok(&["convert", &webp, &png, "--scale", "2"]);
assert_eq!(&std::fs::read(&png).unwrap()[16..24], [0, 0, 0, 6, 0, 0, 0, 4], "IHDR 6×4");
}
#[test]
fn help_lists_readable_formats() {
let out = ok(&["--help"]);
assert!(out.contains("Readable formats: .vectorcraft") && out.contains(".ait") && out.contains(".webp"), "{out}");
}