Fix #3627: convert to PDF omits editing data by default

## Summary
`vectorcraft-cli convert` to PDF now passes `preserveEditing: false`, matching
a command-line converter expectation while Save As PDF in the app still uses
the VectorCraft Default preset. `--preserve-editing` opts in and adds a JSON
warning when the native document attachment is written.

## Tests
Integration test in `vectorcraft-cli` checks default PDF output has no editing
attachment and `--preserve-editing` embeds it with a warning.

Closes storytold/vectorcraft#3627

Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-10-26 09:09:44 +07:00
committed by bflatastic
co-authored by nrq6993-a11y
parent 94832657d1
commit 7855d8ede4
2 changed files with 51 additions and 7 deletions
+31 -7
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}'] | --commands FILE|-]... [--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] [--ai-version 7|pdf] [--hidden-objects layer|omit|visible] [--transparent-images flatten|omit]
//! vectorcraft-cli convert IN OUT [--scale 2] [--artboard 0 | --range 1-3,5] [--outline-text] [--decimals N] [--replace-lossy] [--editing-data] [--preserve-editing] [--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]
@@ -90,7 +90,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] [--ai-version 7|pdf] [--hidden-objects layer|omit|visible] [--transparent-images flatten|omit]
vectorcraft-cli convert IN OUT [--scale N] [--artboard I | --range R] [--outline-text] [--decimals N] [--replace-lossy] [--editing-data] [--preserve-editing] [--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). --scale sizes a raster OUT (pixels per point, default 1); other formats leave it
out. --artboard is 0-based, --range 1-based (\"1-3,5\"); a PDF gets every artboard
@@ -101,10 +101,12 @@ USAGE:
SVG's data-* attributes; --outline-text writes SVG text as paths, --decimals sets SVG's coordinate
precision (1–7, default 3). OUT may not be IN when reading
IN left things out (hidden text, art or layers it could not read; the notes say which) unless
--replace-lossy is given. --editing-data opens a .ai or an EPS with editing data from its editing copy
--replace-lossy is given. --editing-data opens a .ai or an EPS with editing data from its editing copy
even where that draws differently from its PDF part or page (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.
it can be read. A PDF OUT omits the embedded native document unless --preserve-editing is
given (Save As PDF in the app still defaults to keeping it). With --preserve-editing the
result's warnings say the attachment was written.
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;
@@ -274,8 +276,8 @@ fn convert(args: &[String]) -> Result<(), String> {
return help();
}
let mut files = vec![];
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 scale, mut artboard, mut range, mut outline_text, mut replace_lossy, mut editing_data, mut preserve_editing) =
(1.0f64, None::<u64>, None::<String>, false, 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.
@@ -290,6 +292,7 @@ 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,
"--preserve-editing" => preserve_editing = 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")?)),
@@ -319,6 +322,9 @@ fn convert(args: &[String]) -> Result<(), String> {
if let Some(decimals) = decimals {
params["decimals"] = json!(decimals);
}
if vectorcraft_engine::cmd::fileio::format_for_name(&output).is_some_and(|f| f.id == "pdf") {
params["preserveEditing"] = json!(preserve_editing);
}
("document.export", params)
};
for (key, value) in legacy {
@@ -330,6 +336,24 @@ fn convert(args: &[String]) -> Result<(), String> {
if let (Some(w), Some(out)) = (opened.get("warnings").filter(|w| w.as_array().is_some_and(|a| !a.is_empty())), r.as_object_mut()) {
out.insert("importWarnings".into(), w.clone());
}
if preserve_editing
&& vectorcraft_engine::cmd::fileio::format_for_name(&output).is_some_and(|f| f.id == "pdf")
&& let Some(obj) = r.as_object_mut()
{
let path = obj.get("path").and_then(|p| p.as_str()).unwrap_or(&output);
const ATTACHMENT: &str = "vectorcraft-editing.vectorcraft";
let embedded = std::fs::read(path).is_ok_and(|bytes| bytes.windows(ATTACHMENT.len()).any(|w| w == ATTACHMENT.as_bytes()));
if embedded {
let note = "The PDF embeds the native VectorCraft document as an attachment (preserveEditing).";
if let Some(Value::Array(w)) = obj.get_mut("warnings") {
if !w.iter().any(|w| w.as_str() == Some(note)) {
w.push(json!(note));
}
} else {
obj.insert("warnings".into(), json!([note]));
}
}
}
outln!("{r}");
Ok(())
}