From c2a605ce8e25f37b25cfcae6c194bfd188d3a1ad Mon Sep 17 00:00:00 2001 From: Higgs32584 Date: Sun, 18 Oct 2026 18:51:55 -0400 Subject: [PATCH] Save: warn when SVG keeps only one of several artboards A document with more than one artboard saved as SVG/SVGZ without Preserve Editing Capabilities is written as one artboard and reopens with one, so the others were lost without a word. The save now says so (first, since the status bar shows only the first warning) and names the two ways to keep them all. Refs storytold/vectorcraft#2091 Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/fileio/save.rs | 17 ++++++++++++++++- crates/engine/src/cmd/fileio/tests_svg.rs | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/fileio/save.rs b/crates/engine/src/cmd/fileio/save.rs index 2e884a739..ad1011f63 100644 --- a/crates/engine/src/cmd/fileio/save.rs +++ b/crates/engine/src/cmd/fileio/save.rs @@ -322,6 +322,19 @@ fn fidelity_warning(f: &Format) -> Option { }) } +/// What an SVG save loses of a document with several artboards: the file is one artboard and +/// reopens as one, unless it carries the native document (Preserve Editing Capabilities). +fn artboards_warning(f: &Format, doc: &Document, params: &Value) -> Option { + let n = doc.artboards.len(); + let preserved = super::svg::options_map(params).ok().and_then(|m| m.get("preserveEditing").and_then(Value::as_bool)).unwrap_or(false); + (is_svg(f) && n > 1 && !preserved).then(|| { + format!( + "{} keeps 1 of the document's {n} artboards (the first, unless one is chosen) and reopens with only that one: turn on Preserve Editing Capabilities or save as VectorCraft to keep them all", + f.label + ) + }) +} + /// A save of the active document, snapshotted and ready to encode and write anywhere (Background /// Save does both on a worker thread): [`save_job`], then [`SaveJob::write`], then /// [`SaveJob::finish`]. Cheap to clone (the documents are shared). @@ -549,7 +562,9 @@ impl SaveJob { let one = self.encode_one(&artboard_doc(&self.doc, b))?; enc.files.extend(one.files.into_iter().map(|(_, bytes)| (Some(b), bytes))); } - enc.warnings.splice(0..0, fidelity_warning(self.plan.format)); + // Lost artboards first: the status bar shows only the first warning. + let f = self.plan.format; + enc.warnings.splice(0..0, artboards_warning(f, &self.doc, &self.params).into_iter().chain(fidelity_warning(f))); Ok(enc) } diff --git a/crates/engine/src/cmd/fileio/tests_svg.rs b/crates/engine/src/cmd/fileio/tests_svg.rs index d52ab20eb..05cafeb6a 100644 --- a/crates/engine/src/cmd/fileio/tests_svg.rs +++ b/crates/engine/src/cmd/fileio/tests_svg.rs @@ -145,6 +145,23 @@ fn preserve_editing_reopens_the_native_document() { assert_eq!(s.doc().unwrap().doc.artboards.len(), 1); } +#[test] +fn saving_several_artboards_as_plain_svg_warns_they_are_lost() { + let artboards = |r: &Value| r["warnings"].as_array().unwrap().iter().filter(|w| w.as_str().unwrap().contains("artboards")).count(); + let mut s = session(3); + let r = s.execute("document.save", &json!({"format": "svg"})).unwrap(); + assert_eq!(artboards(&r), 1, "{r}"); + // First: the status bar shows only the first warning. + assert!(r["warnings"][0].as_str().unwrap().contains("1 of the document's 3 artboards"), "{r}"); + assert_eq!(artboards(&s.execute("file.saveAs", &json!({"format": "svgz"})).unwrap()), 1); + // Preserve Editing keeps them all (they reopen); a single artboard loses none. + for p in [json!({"format": "svg", "preserveEditing": true}), json!({"format": "svg", "svg": {"preserveEditing": true}})] { + let r = s.execute("document.save", &p).unwrap(); + assert_eq!(artboards(&r), 0, "{p}: {r}"); + } + assert_eq!(artboards(&session(1).execute("document.save", &json!({"format": "svg"})).unwrap()), 0); +} + #[test] fn save_writes_svg_and_remembers_its_options() { let mut s = session(1);