Merge pull request #3875 from storytold/fix/3868-screens-one-artboard

Export for Screens: a document row for an artboard keeps only that artboard's art (#3868)
This commit is contained in:
BFlat
2026-10-24 04:11:32 +07:00
committed by GitHub
3 changed files with 38 additions and 12 deletions
+10 -5
View File
@@ -385,22 +385,27 @@ fn separate_boards(cmd: &str, f: &Format, doc: &Document, p: &Value) -> Result<V
pub(super) fn artboard_doc(doc: &Document, i: usize) -> Document {
let mut d = doc.clone();
let Some(a) = doc.artboards.get(i) else { return d };
let r = a.rect;
d.artboards = vec![a.clone()];
// It opens fitted to its artboard, not at the master file's view.
keep_art_on(&mut d, a.rect);
d
}
/// Reduce `d` to the objects touching `r`, in their layers (resources are kept), opening
/// fitted to its artboard rather than at the master file's view.
pub(super) fn keep_art_on(d: &mut Document, r: vectorcraft_geom::Rect) {
d.last_view = None;
d.layers_open = None;
for layer in &mut d.layers {
keep_on(layer, r);
}
d
}
/// Keep in layer `n` (and its sublayers) only the objects touching `r`.
/// Keep in layer `n` (and its sublayers) only the objects touching `r`, and those without bounds.
fn keep_on(n: &mut Arc<vectorcraft_doc::Node>, r: vectorcraft_geom::Rect) {
let touches = |b: vectorcraft_geom::Rect| b.x0 <= r.x1 && b.x1 >= r.x0 && b.y0 <= r.y1 && b.y1 >= r.y0;
if let Some(children) = Arc::make_mut(n).children_mut() {
children.retain(|c| c.is_layer() || c.visual_bounds().is_some_and(touches));
// An object whose bounds can't be measured (type in a missing font) stays: dropping it loses art.
children.retain(|c| c.is_layer() || c.visual_bounds().is_none_or(touches));
for c in children.iter_mut().filter(|c| c.is_layer()) {
keep_on(c, r);
}
+15 -2
View File
@@ -784,8 +784,21 @@ fn encode_and_write(export: ScreenExport, writer: &mut dyn FnMut(&str, &[u8]) ->
write(all, "pdf", &options)?
} else if r.whole_document() && item.artboard.is_some() {
let b = item.artboard.unwrap_or(0);
let d = board_docs.entry(b).or_insert_with(|| Arc::new(single_artboard(&item.doc, item.region, &item.name))).clone();
write(&d, r.format.id, &r.options_for(item.region, None)?)?
// That artboard alone, as Save's separateArtboards writes it: its art, not the others' (#3868).
let d = board_docs
.entry(b)
.or_insert_with(|| {
let mut d = single_artboard(&item.doc, item.region, &item.name);
super::save::keep_art_on(&mut d, item.region);
Arc::new(d)
})
.clone();
let mut options = r.options_for(item.region, None)?;
// An EPS page is the artboard, not its art's bounds: an artboard without art still writes.
if r.format.id == "eps" {
options["useArtboards"] = json!(true);
}
write(&d, r.format.id, &options)?
} else {
write(&item.doc, r.format.id, &r.options_for(item.region, item.artboard)?)?
};
+13 -5
View File
@@ -134,17 +134,25 @@ fn layers_and_the_selection_are_exported_by_name() {
assert!(s.execute("document.exportForScreens", &json!({"selection": "one"})).is_err(), "nothing selected");
}
/// Document formats write each artboard as a document of its own: one artboard, its art.
/// Document formats write each artboard as a document of its own: one artboard, its art and
/// not the other artboards' (#3868).
#[test]
fn document_formats_write_one_artboard_each() {
let mut s = session(2);
let second = s.doc().unwrap().doc.artboards[1].rect;
s.execute("shape.ellipse", &json!({"x": second.x0 + 5.0, "y": 5, "width": 10, "height": 10})).unwrap();
let r = export(&mut s, json!({"formats": [{"format": "vectorcraft"}, {"format": "ai"}, {"format": "eps"}]}));
let n = names(&r);
assert_eq!(n, ["Artboard-1.vectorcraft", "Artboard-1.ai", "Artboard-1.eps", "Artboard-2.vectorcraft", "Artboard-2.ai", "Artboard-2.eps"]);
let native = &files(&r)[0].1;
let mut t = Session::new();
t.execute("document.open", &json!({"name": "a.vectorcraft", "dataBase64": vectorcraft_format::base64_encode(native)})).unwrap();
assert_eq!(t.doc().unwrap().doc.artboards.len(), 1, "that artboard alone");
let all = files(&r);
for (name, bytes) in &all {
let mut t = Session::new();
t.execute("document.open", &json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(bytes)})).unwrap();
let d = &t.doc().unwrap().doc;
assert_eq!(d.artboards.len(), 1, "{name}: that artboard alone");
let objects: usize = d.layers.iter().filter_map(|l| l.children()).map(|c| c.len()).sum();
assert_eq!(objects, 1, "{name}: only its own artboard's art");
}
}
#[test]