mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Fix #3544: an SVG of one artboard on its own has the artboard's name as its title
The SVG writer always wrote the document's title in <title>
(crates/svg/src/export.rs), so every file of Export As with Use
Artboards, Export for Screens or an `artboard`/`range` export was titled
after the document ("Icons"), whatever its artboard was called. The
writer's `artboard` option can't tell those apart from Save: Save writes
the first artboard too, standing for the whole document.
ExportOptions has a new `artboard_title` (set by the caller, as
`artboard` is, not deserialized): the file is that one artboard, exported
on its own, and its <title> is the artboard's name (XML-escaped as
before), or the document's title when the artboard has no name. The
engine's SVG encoder (fileio/svg.rs) sets it exactly when the export
names its artboards, which is also when a file holds only that
artboard's art (#550): Export As per artboard, document.export /
serialize with `artboard` or `range`, MCP export, and Export for Screens'
artboards, assets, layers and selections (each written as an artboard
named after it). Save and Save As keep the document's title. Export for
Screens' Full Document draws the art bounds as one artboard, which is now
named after the document, so its SVG keeps the document's title too.
docs/file-format-parity.md's SVG row says so.
Tests: title_control_chars::an_artboard_exported_alone_is_titled_after_it
(crates/svg: two named artboards, the second named with markup;
exporting either alone gives its escaped name, the default and an
artboard without artboard_title give the document's title, an unnamed
artboard falls back to it) and
tests_svgenc::an_svg_of_one_artboard_is_titled_after_it (the engine:
`artboard` 0 and 1, useArtboards range all, Save's SVG, Export for
Screens per artboard, an asset and Full Document). Both fail without the
change (the title was "Icons") and pass with it.
This commit is contained in:
committed by
bflatastic
parent
cc499d7e8e
commit
439d32fcf8
@@ -800,10 +800,12 @@ fn items(
|
||||
};
|
||||
if o.full_document {
|
||||
// The bounds of all visible art, as the one artboard of the files that aren't PDFs (a PDF
|
||||
// row writes every artboard of the document itself).
|
||||
// row writes every artboard of the document itself). It is named after the document: an
|
||||
// SVG of one artboard takes the artboard's name as its title (#3544), and this one stands
|
||||
// for the whole document.
|
||||
let (whole, region) = if crop {
|
||||
let bounds = vectorcraft_render::encode::art_bounds(doc).ok_or_else(|| bad(C, "nothing to export: the document has no visible art"))?;
|
||||
(Arc::new(single_artboard(doc, bounds, "Document")), bounds)
|
||||
(Arc::new(single_artboard(doc, bounds, &doc.title)), bounds)
|
||||
} else {
|
||||
(doc.clone(), Rect::ZERO)
|
||||
};
|
||||
|
||||
@@ -179,9 +179,10 @@ pub(super) fn encode(doc: &Document, p: &Value, compressed: bool) -> Result<Enco
|
||||
let doc = flat.as_ref().unwrap_or(doc);
|
||||
let mut enc = Encoded { warnings, ..Encoded::default() };
|
||||
for artboard in boards {
|
||||
// A chosen artboard's file holds the art over it (#550).
|
||||
// A chosen artboard's file holds the art over it (#550) and is named after it (#3544).
|
||||
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 file = ExportOptions { artboard, artboard_title: chosen, ..opts.clone() };
|
||||
let mut out = vectorcraft_svg::export_full(over.as_ref().unwrap_or(doc), &file, native.as_deref());
|
||||
let bytes = out.take_bytes();
|
||||
for l in out.linked {
|
||||
if !enc.linked.iter().any(|e| e.name == l.name) {
|
||||
|
||||
@@ -108,3 +108,39 @@ fn a_chosen_artboard_s_svg_keeps_an_object_whose_shadow_reaches_it() {
|
||||
s.execute("effect.apply", &json!({"effect": "stylize.dropShadow", "ids": [id], "params": {"x": 20, "y": 0, "blur": 0}})).unwrap();
|
||||
assert_eq!(paths(&mut s), 1, "its shadow reaches 10 pt onto the artboard");
|
||||
}
|
||||
|
||||
/// #3544: an SVG of one artboard on its own (an `artboard` or `range`, Export for Screens, an asset)
|
||||
/// has the artboard's (or asset's) name as `<title>`; Save's SVG and Export for Screens' Full
|
||||
/// Document keep the document's.
|
||||
#[test]
|
||||
fn an_svg_of_one_artboard_is_titled_after_it() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"name": "Icons", "width": 24, "height": 24, "artboards": 2})).unwrap();
|
||||
for (index, name) in [(0, "arrow-left"), (1, "arrow-right")] {
|
||||
s.execute("artboard.setProps", &json!({"index": index, "name": name})).unwrap();
|
||||
}
|
||||
let square = s.execute("shape.rectangle", &json!({"x": 4, "y": 4, "width": 16, "height": 16})).unwrap()["id"].clone();
|
||||
let title = |svg: &str| svg.lines().find_map(|l| l.trim().strip_prefix("<title>")?.strip_suffix("</title>")).map(str::to_string);
|
||||
let serialize = |s: &mut Session, p: serde_json::Value| s.execute("document.serialize", &p).unwrap()["text"].as_str().unwrap().to_string();
|
||||
assert_eq!(title(&serialize(&mut s, json!({"format": "svg", "artboard": 1}))).as_deref(), Some("arrow-right"));
|
||||
assert_eq!(title(&serialize(&mut s, json!({"format": "svg", "artboard": 0}))).as_deref(), Some("arrow-left"));
|
||||
assert_eq!(title(&serialize(&mut s, json!({"format": "svg"}))).as_deref(), Some("Icons"), "Save's SVG");
|
||||
let all = s.execute("document.serialize", &json!({"format": "svg", "useArtboards": true, "range": "all"})).unwrap();
|
||||
let titles: Vec<_> = all["files"].as_array().unwrap().iter().map(|f| title(f["text"].as_str().unwrap())).collect();
|
||||
assert_eq!(titles, [Some("arrow-left".into()), Some("arrow-right".into())]);
|
||||
// Export for Screens: each artboard, an asset, and the Full Document.
|
||||
let screens = |s: &mut Session, p: serde_json::Value| -> Vec<Option<String>> {
|
||||
let r = s.execute("document.exportForScreens", &p).unwrap();
|
||||
r["files"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|f| title(&String::from_utf8(vectorcraft_format::base64_decode(f["dataBase64"].as_str().unwrap()).unwrap()).unwrap()))
|
||||
.collect()
|
||||
};
|
||||
let svg = json!([{"format": "svg"}]);
|
||||
assert_eq!(screens(&mut s, json!({"formats": svg})), [Some("arrow-left".into()), Some("arrow-right".into())]);
|
||||
let asset = s.execute("assets.add", &json!({"ids": [square], "name": "Square"})).unwrap()["assets"][0].clone();
|
||||
assert_eq!(screens(&mut s, json!({"assets": [asset], "formats": svg})), [Some("Square".into())]);
|
||||
assert_eq!(screens(&mut s, json!({"fullDocument": true, "formats": svg})), [Some("Icons".into())]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user