Open by a relative path finds relative links; convert reports import warnings

document.open (and file.newFromTemplate) with a path relative to the
working directory took the SVG's folder from Path::parent(), which is
"" for a bare file name and was then dropped, so relatively linked
images were "not found" (and, linked by their bare name, not listed in
missingLinks either). A relative path is now made absolute first: its
folder is the working directory, as for the absolute path.

vectorcraft-cli convert printed only the export's warnings; what reading
the input left out now comes as importWarnings.

Fixes #1604
This commit is contained in:
Gert Pellin authored and bflatastic committed 2026-10-13 13:36:40 +07:00
1 parent eb25a85d90
commit 13bd24ba79
4 files changed
+59 -8

No files matched your search

+12 -6
View File
@@ -93,7 +93,8 @@ USAGE:
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 from its editing copy even where that
draws differently from its PDF part (which can be older), as long as it can be read.
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.
vectorcraft-cli info FILE
Print a JSON summary: the import warnings (what didn't come in as it was, such as an EPS
@@ -237,19 +238,24 @@ fn convert(args: &[String]) -> Result<(), String> {
}
let [input, output] = <[String; 2]>::try_from(files).map_err(|_| "convert needs IN and OUT")?;
let mut h = Headless::new();
if editing_data {
let opened = if editing_data {
let params = json!({"path": input, "editingData": "always"});
h.call("engine.execute", json!({"command": "document.open", "params": params})).map_err(|e| format!("open {input}: {e}"))?;
h.call("engine.execute", json!({"command": "document.open", "params": params})).map_err(|e| format!("open {input}: {e}"))?
} else {
h.call("app.open", json!({"path": input})).map_err(|e| format!("open {input}: {e}"))?;
}
h.call("app.open", json!({"path": input})).map_err(|e| format!("open {input}: {e}"))?
};
// A conversion keeps hidden layers and objects, written hidden, where the format can (SVG,
// PSD).
let mut params = json!({"path": output, "scale": scale, "artboard": artboard, "range": range, "outlineText": outline_text, "hiddenLayers": true, "acknowledgeLoss": replace_lossy});
if let Some(decimals) = decimals {
params["decimals"] = json!(decimals);
}
let r = h.call("engine.execute", json!({"command": "document.export", "params": params})).map_err(|e| format!("export {output}: {e}"))?;
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.
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());
}
outln!("{r}");
Ok(())
}
+16
View File
@@ -99,3 +99,19 @@ fn info_reports_why_an_eps_read_partly() {
assert!(w.contains("`frobnicate`"), "{v}");
assert_eq!(v["kinds"]["Path"], 1, "{v}");
}
/// #1604: convert reports what reading the input left out (here a missing linked image) as
/// importWarnings, next to the export's own warnings.
#[test]
fn convert_reports_import_warnings() {
let svg = tmp("missing-link.svg");
std::fs::write(
&svg,
r#"<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><image width="40" height="40" href="nowhere-to-be-found.png"/></svg>"#,
)
.unwrap();
let png = tmp("missing-link.png");
let out: Value = serde_json::from_str(ok(&["convert", &svg, &png]).trim()).unwrap();
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}");
}