Part of #868: vectorcraft-cli convert writes legacy .ai (--ai-version 7, --hidden-objects, --transparent-images)

This commit is contained in:
Dan Green
2026-10-16 01:36:25 +07:00
committed by bflatastic
parent 67e17c1b66
commit 37cebc47ee
4 changed files with 74 additions and 4 deletions
+16 -2
View File
@@ -4,7 +4,7 @@
//! vectorcraft-cli mcp [--connect 127.0.0.1:7979 | --headless] [--automation-read-root DIR] [--automation-write-root DIR]
//! 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] [--decimals N] [--replace-lossy] [--editing-data]
//! vectorcraft-cli convert IN OUT [--scale 2] [--artboard 0 | --range 1-3,5] [--outline-text] [--decimals N] [--replace-lossy] [--editing-data] [--ai-version 7|pdf] [--hidden-objects layer|omit|visible] [--transparent-images flatten|omit]
//! vectorcraft-cli info FILE
//! vectorcraft-cli bench FILE [--size 2880x1800] [--iters 5]
//! vectorcraft-cli perf [--paths 50000] [--deep]
@@ -84,7 +84,7 @@ USAGE:
vectorcraft-cli commands
Print the command catalogue as JSON.
vectorcraft-cli convert IN OUT [--scale N] [--artboard I | --range R] [--outline-text] [--decimals N] [--replace-lossy] [--editing-data]
vectorcraft-cli convert IN OUT [--scale N] [--artboard I | --range R] [--outline-text] [--decimals N] [--replace-lossy] [--editing-data] [--ai-version 7|pdf] [--hidden-objects layer|omit|visible] [--transparent-images flatten|omit]
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.
@@ -95,6 +95,11 @@ USAGE:
--replace-lossy is given. --editing-data opens a .ai from its editing copy even where that
draws differently from its PDF part (which can be older), as long as it can be read. The
result has the export's warnings, and importWarnings for what reading IN left out.
--ai-version 7 writes a .ai as the legacy version 7 (layers, groups and type kept; one file
per artboard, OUT-<artboard>.ai when there are several; default pdf, PDF-compatible), with
--hidden-objects (default layer: a hidden layer above each layer's hidden objects; omit;
visible), --transparent-images (default flatten, with the art under them; omit) and
--outline-text (type as outlines instead of text).
vectorcraft-cli info FILE
Print a JSON summary: the import warnings (what didn't come in as it was, such as an EPS
@@ -223,6 +228,9 @@ fn convert(args: &[String]) -> Result<(), String> {
let (mut scale, mut artboard, mut range, mut outline_text, mut replace_lossy, mut editing_data) =
(1.0f64, None::<u64>, None::<String>, false, false, false);
let mut decimals = None::<u64>;
// Legacy `.ai` options, passed on as the export's own (`aiVersion`, `hiddenObjects`,
// `transparentImages`); the engine checks their values.
let mut legacy: Vec<(&str, String)> = vec![];
let mut it = args.iter();
while let Some(a) = it.next() {
match a.as_str() {
@@ -233,6 +241,9 @@ fn convert(args: &[String]) -> Result<(), String> {
"--decimals" => decimals = Some(it.next().and_then(|v| v.parse::<u64>().ok()).ok_or("--decimals needs a number")?),
"--replace-lossy" => replace_lossy = true,
"--editing-data" => editing_data = true,
"--ai-version" => legacy.push(("aiVersion", it.next().cloned().ok_or("--ai-version needs 7 or pdf")?)),
"--hidden-objects" => legacy.push(("hiddenObjects", it.next().cloned().ok_or("--hidden-objects needs layer, omit or visible")?)),
"--transparent-images" => legacy.push(("transparentImages", it.next().cloned().ok_or("--transparent-images needs flatten or omit")?)),
f => files.push(f.to_string()),
}
}
@@ -250,6 +261,9 @@ fn convert(args: &[String]) -> Result<(), String> {
if let Some(decimals) = decimals {
params["decimals"] = json!(decimals);
}
for (key, value) in legacy {
params[key] = json!(value);
}
let mut r = h.call("engine.execute", json!({"command": "document.export", "params": params})).map_err(|e| format!("export {output}: {e}"))?;
// What reading the input left out (a missing linked file, an unsupported filter…) matters as
// much as what writing the output did.
+54
View File
@@ -115,3 +115,57 @@ fn convert_reports_import_warnings() {
let w = out["importWarnings"].as_array().unwrap_or_else(|| panic!("{out}"));
assert!(w.iter().any(|w| w.as_str().unwrap().contains("nowhere-to-be-found.png")), "{out}");
}
#[test]
fn convert_writes_legacy_ai_with_its_options() {
// Two artboards, a shown rectangle and a hidden one.
let src = tmp("legacy.vectorcraft");
ok(&[
"run",
"--cmd",
"file.new",
"--params",
r#"{"width":120,"height":80,"artboards":2}"#,
"--cmd",
"shape.rectangle",
"--params",
r#"{"x":10,"y":10,"width":20,"height":10}"#,
"--cmd",
"shape.rectangle",
"--params",
r#"{"x":40,"y":10,"width":20,"height":10}"#,
"--cmd",
"object.hide",
"--export",
&src,
]);
// One file per artboard, named after it; hidden objects in a hidden layer by default.
let out = tmp("legacy.ai");
let r: Value = serde_json::from_str(&ok(&["convert", &src, &out, "--ai-version", "7"])).unwrap();
let files: Vec<&str> = r["files"].as_array().unwrap().iter().filter_map(Value::as_str).collect();
assert_eq!(files.len(), 2, "{r}");
assert!(files.iter().all(|f| std::fs::read(f).unwrap().starts_with(b"%!PS-Adobe-3.0")), "{r}");
assert!(r["warnings"].to_string().contains("hidden layer"), "{r}");
// One artboard is the file itself; the hidden rectangle left out.
let one = tmp("legacy-one.ai");
let r: Value = serde_json::from_str(&ok(&[
"convert",
&src,
&one,
"--ai-version",
"7",
"--range",
"1",
"--hidden-objects",
"omit",
"--transparent-images",
"omit",
]))
.unwrap();
assert!(r["warnings"].to_string().contains("hidden objects are left out"), "{r}");
assert!(std::fs::read(&one).unwrap().starts_with(b"%!PS-Adobe-3.0"));
// A value the engine doesn't take is an error.
let bad = Command::new(BIN).args(["convert", &src, &tmp("bad.ai"), "--ai-version", "7", "--hidden-objects", "behind"]).output().unwrap();
assert!(!bad.status.success());
assert!(String::from_utf8_lossy(&bad.stderr).contains("hiddenObjects"), "{}", String::from_utf8_lossy(&bad.stderr));
}
+1 -1
View File
@@ -115,7 +115,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri
| 2026-10-13 | minor | PDF.W1: PDF export and print composition use the shared guarded writer; drawing, colour policy and editable import stay app-owned. Metadata regression coverage and immutable dependency checks added; scores and hours unchanged. |
| 2026-10-13 | minor | Native documents with a lot of path data (traced or generated art, in layers, symbols or patterns) are saved as compact JSON, about a fifth of the size; small documents stay pretty-printed (#1492); Paste takes a PDF or EPS another app copied as editable vector art: from the macOS pasteboard (with SVG), EPS on Windows beside PDF, and `.eps` files copied in a file manager, with the new command `clipboard.importEps` (#1538, fixes #1499); Opening or placing a file with an image too large to decode says so, and the Links panel shows it on the row (#1540, part of #1479); a multi-artboard `.ai` with notes beside its artboards opens from its editing data, a page's art past its artboard no longer lying over the page before it (#1528); a `.ai` gradient whose stops are listed upwards keeps each midpoint on the stop that starts its part of the ramp (#1530); SVG text on a path follows basic shapes (circle, ellipse, rect, line, polyline, polygon) as their SVG 2 equivalent paths (#1531, fixes #1527); PDFs whose alpha soft masks are drawn as images import with the mask instead of empty art (#1551); EPS and PostScript type set in Type 42 (TrueType) fonts imports at its size instead of 1000 times too large (#1553); PDF and EPS import keep a clipping path whose bounding box covers the page but whose shape doesn't (#1544, fixes #1542) (the `.ai` row for #1528 was written by that PR); scores unchanged (estimated) |
| 2026-10-13 | minor | `.ai` read as its PDF part: a page whose art reaches past its artboard to the left (notes beside a design) moves right in the row until that art clears the page before it, so it is no longer taken for that page's art; a multi-artboard `.ai` with such notes opened as its PDF part ("the art on them differs from the page's"), without the art outside its artboards, and now opens from its editing data |
| 2026-10-12 | minor | Legacy `.ai` of version 7 (Save As › `ai` › Version: Legacy (version 7), #868): written from the published specification, keeping layers, groups, compound paths, clipping groups, gradients and images, one artboard per file chosen in an artboard picker; type as editable text objects (point, area and on a path, fonts by PostScript name, Windows-1252), or outlines (Text: Outlines); hidden objects, which the format can't hide, in a hidden layer of their own, omitted or made visible (Hidden Objects); images with see-through pixels flattened with the art under them or omitted (Transparent Images). The `.ai` reader reads legacy type (`To` … `TO`) and `Ar`; scores unchanged (estimated) |
| 2026-10-12 | minor | Legacy `.ai` of version 7 (Save As › `ai` › Version: Legacy (version 7), #868): written from the published specification, keeping layers, groups, compound paths, clipping groups, gradients and images, one artboard per file chosen in an artboard picker; type as editable text objects (point, area and on a path, fonts by PostScript name, Windows-1252), or outlines (Text: Outlines); hidden objects, which the format can't hide, in a hidden layer of their own, omitted or made visible (Hidden Objects); images with see-through pixels flattened with the art under them or omitted (Transparent Images). The `.ai` reader reads legacy type (`To` … `TO`) and `Ar`. `vectorcraft-cli convert` takes `--ai-version 7`, `--hidden-objects` and `--transparent-images`; scores unchanged (estimated) |
| 2026-10-13 | minor | TIFF: grayscale files with an alpha channel or other extra channels place, relink and open, read as CMYK files with alpha already were (8 and 16 bits, either polarity; #1489, part of #1466); the rows for #1434 (strokes kept apart from a faded or masked fill, a `.ai`'s RGB numbers) and #1435 (SVG knockout groups without masks, `--decimals`) were written by those PRs; scores unchanged (estimated) |
| 2026-10-12 | minor | TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged |
| 2026-10-12 | minor | Affinity import: a text frame too short for its first line (a 64 pt title in a 54 pt frame) is made taller from its top, with a warning in the open result, instead of showing nothing (#1423), as `.ai` area type is (#1392); the spacing Affinity itself fits the line with isn't read; scores unchanged |
+3 -1
View File
@@ -599,7 +599,9 @@ PostScript form, which keeps layers, groups, compound paths and clipping groups
of `range` (default all; `{name}-{artboard}.ai` when several, listed in the result's `files`), with
`transparentImages: "flatten"` (default) or `"omit"` for images with see-through pixels, opacity or a blending mode,
`hiddenObjects: "layer"` (default: a hidden layer of each layer's hidden objects), `"omit"` or `"visible"`, and
`outlineText: true` to write type as outlines instead of text objects.
`outlineText: true` to write type as outlines instead of text objects. From the CLI: `vectorcraft-cli convert in.svg
out.ai --ai-version 7 [--hidden-objects layer|omit|visible] [--transparent-images flatten|omit] [--outline-text]
[--range R]`.
Drive a tool like a mouse: