Merge pull request #3613 from Taha-Mahmoodi/fix/3544-svg-title-artboard

Fix #3544: an SVG of one artboard on its own has the artboard's name as its title
This commit is contained in:
BFlat
2026-10-22 19:19:23 +07:00
committed by GitHub
7 changed files with 89 additions and 7 deletions
+4 -2
View File
@@ -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)
};
+3 -2
View File
@@ -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())]);
}