diff --git a/crates/engine/src/cmd/fileio/svg.rs b/crates/engine/src/cmd/fileio/svg.rs index 920998a51..be0ee2487 100644 --- a/crates/engine/src/cmd/fileio/svg.rs +++ b/crates/engine/src/cmd/fileio/svg.rs @@ -45,9 +45,24 @@ pub const OPTIONS: &[FormatOption] = &[ default: "\"layerNames\"", description: "layerNames (readable ids from names) | minimal (only referenced ids) | unique (content-hashed prefix on every id and class)", }, - FormatOption { name: "decimals", ty: "integer", default: "3", description: "coordinate precision, 1–7 decimal places" }, - FormatOption { name: "minify", ty: "boolean", default: "false", description: "no XML declaration, indentation or line breaks" }, - FormatOption { name: "responsive", ty: "boolean", default: "false", description: "no width/height: the SVG scales to its container" }, + FormatOption { + name: "decimals", + ty: "integer", + default: "3", + description: "coordinate precision, 1–7 decimal places", + }, + FormatOption { + name: "minify", + ty: "boolean", + default: "false", + description: "no XML declaration, indentation or line breaks", + }, + FormatOption { + name: "responsive", + ty: "boolean", + default: "false", + description: "no width/height: the SVG scales to its container", + }, FormatOption { name: "preserveEditing", ty: "boolean", @@ -120,7 +135,9 @@ pub fn options_map(p: &Value) -> Result, String> { match o.get("svg") { None | Some(Value::Null) => {} Some(Value::Object(svg)) => m.extend(svg.clone()), - Some(_) => return Err("`svg` must be an object of SVG options (see document.formats)".into()), + Some(_) => { + return Err("`svg` must be an object of SVG options (see document.formats)".into()); + } } Ok(m) } @@ -131,12 +148,20 @@ pub fn options_map(p: &Value) -> Result, String> { fn plan(doc: &Document, p: &Value) -> Result<(ExportOptions, Vec>, bool), String> { let mut m = options_map(p)?; // Fonts: unless chosen, Document Setup → Type → Export decides (appearance = outlines). - if !m.contains_key("outlineText") && doc.setup.export_text == vectorcraft_doc::ExportText::Appearance { + if !m.contains_key("outlineText") + && doc.setup.export_text == vectorcraft_doc::ExportText::Appearance + { m.insert("outlineText".into(), Value::Bool(true)); } - let picks: Map = ARTBOARD_PARAMS.iter().chain(["useArtboards"].iter()).filter_map(|k| m.remove_entry(*k)).collect(); - let boards = Boards::deserialize(Value::Object(picks)).map_err(|e| format!("SVG options: {e}"))?; - let opts = ExportOptions::deserialize(Value::Object(m)).map_err(|e| format!("SVG options: {e}"))?; + let picks: Map = ARTBOARD_PARAMS + .iter() + .chain(["useArtboards"].iter()) + .filter_map(|k| m.remove_entry(*k)) + .collect(); + let boards = + Boards::deserialize(Value::Object(picks)).map_err(|e| format!("SVG options: {e}"))?; + let opts = + ExportOptions::deserialize(Value::Object(m)).map_err(|e| format!("SVG options: {e}"))?; opts.check().map_err(|e| format!("SVG options: {e}"))?; let n = doc.artboards.len(); if n == 0 || boards.use_artboards == Some(false) { @@ -144,7 +169,15 @@ fn plan(doc: &Document, p: &Value) -> Result<(ExportOptions, Vec>, } let named = boards.pick.resolve(n)?; let chosen = named.is_some(); - Ok((opts, named.unwrap_or_else(|| vec![0]).into_iter().map(Some).collect(), chosen)) + Ok(( + opts, + named + .unwrap_or_else(|| vec![0]) + .into_iter() + .map(Some) + .collect(), + chosen, + )) } /// What a save of `doc` as SVG (format `label`) with options `p` loses of several artboards: the @@ -155,7 +188,7 @@ pub(super) fn lost_artboards_warning(doc: &Document, p: &Value, label: &str) -> let preserved = plan(doc, p).is_ok_and(|(opts, ..)| opts.preserve_editing); (n > 1 && !preserved).then(|| { format!( - "1 of the document's {n} artboards" + "{label} keeps 1 of the document's {n} artboards: the file reopens with one. Turn on Preserve Editing Capabilities to keep them all." ) }) } @@ -165,23 +198,44 @@ pub(super) fn lost_artboards_warning(doc: &Document, p: &Value, label: &str) -> pub(super) fn encode(doc: &Document, p: &Value, compressed: bool) -> Result { let (opts, boards, chosen) = plan(doc, p)?; // Preserve editing embeds the native document (once, shared by every file). - let native = opts.preserve_editing.then(|| vectorcraft_format::save(doc, false)); + let native = opts + .preserve_editing + .then(|| vectorcraft_format::save(doc, false)); // Draw linked files as vectors when available, or their previews with a warning. Keep // that preparation out of the native attachment so missing links remain relinkable. let (full, warnings) = crate::cmd::place::document::full_documents(doc); let doc = &*full; // A chosen artboard's file holds the art over it; otherwise the file holds all of it. - let regions: Option> = - chosen.then(|| boards.iter().flatten().filter_map(|b| doc.artboards.get(*b)).map(|a| a.rect).collect()); + let regions: Option> = chosen.then(|| { + boards + .iter() + .flatten() + .filter_map(|b| doc.artboards.get(*b)) + .map(|a| a.rect) + .collect() + }); let overflow = crate::cmd::fonts::export_overflow_warning(doc, &doc.layers, regions.as_deref()); // SVG has no filters for the Photoshop-style effects: their objects go in as images. let flat = crate::cmd::rasterfx::flatten_pixel_effects(doc); let doc = flat.as_ref().unwrap_or(doc); - let mut enc = Encoded { warnings, ..Encoded::default() }; + let mut enc = Encoded { + warnings, + ..Encoded::default() + }; for artboard in boards { // A chosen artboard's file holds the art over it (#550). - let over = artboard.filter(|_| chosen).and_then(|b| doc.artboards.get(b)).map(|a| super::export::art_over(doc, a.rect)); - let mut out = vectorcraft_svg::export_full(over.as_ref().unwrap_or(doc), &ExportOptions { artboard, ..opts.clone() }, native.as_deref()); + let over = artboard + .filter(|_| chosen) + .and_then(|b| doc.artboards.get(b)) + .map(|a| super::export::art_over(doc, a.rect)); + let mut out = vectorcraft_svg::export_full( + over.as_ref().unwrap_or(doc), + &ExportOptions { + artboard, + ..opts.clone() + }, + native.as_deref(), + ); let bytes = out.take_bytes(); for l in out.linked { if !enc.linked.iter().any(|e| e.name == l.name) { @@ -193,7 +247,14 @@ pub(super) fn encode(doc: &Document, p: &Value, compressed: bool) -> Result