Artboards: Convert to Artboards gives each new artboard its id without reading every artboard

Convert to Artboards found the highest artboard id twice for each object it
converted: once for the new artboard's name and once in
Document::next_artboard_id. Converting N objects read about N^2 artboards.

The command now keeps the highest id as it adds artboards, and
Document::next_artboard_id_above gives the next id from it. When an artboard
has id u32::MAX, it falls back to next_artboard_id, which takes the smallest
free id. The ids and names it gives are unchanged.
This commit is contained in:
dotCooCoo
2026-11-11 20:56:36 -08:00
parent fd6b8f6c97
commit 605a74f19f
3 changed files with 50 additions and 2 deletions
+5 -2
View File
@@ -1306,13 +1306,16 @@ fn convert_to_artboards(s: &mut Session, _: &Value) -> Result<Value> {
let symbols = d.symbol_boxes();
let boxes: Vec<(NodeId, Rect)> = roots.iter().filter_map(|id| Some((*id, d.node(*id)?.geometric_bounds_with(&symbols)?))).collect();
let mut converted = vec![];
// The highest artboard id the document has, kept up to date as artboards are added.
let mut top = d.artboards.iter().map(|a| a.id).max();
for (id, b) in boxes {
if b.width() <= 0.0 || b.height() <= 0.0 {
continue;
}
// Named one past the highest artboard id the document has, whatever id it is given.
let number = d.artboards.iter().map(|a| a.id).max().map_or(1, |m| m.saturating_add(1));
let aid = d.next_artboard_id();
let number = top.map_or(1, |m| m.saturating_add(1));
let aid = d.next_artboard_id_above(top);
top = top.max(Some(aid));
d.artboards.push(vectorcraft_doc::Artboard {
id: aid,
name: format!("Artboard {number}"),
+32
View File
@@ -1855,6 +1855,38 @@ fn convert_to_artboards_names_by_the_artboards_the_document_has() {
assert_eq!(name_after(&|s| _ = s.execute("edit.undo", &json!({})).unwrap()), "Artboard 3");
}
/// Convert to Artboards on several objects gives each new artboard an id above the ids given out
/// before and the deleted artboard's, and names it one past the highest id the artboards have,
/// counting the artboards it has just added. With an artboard whose id is `u32::MAX` (a damaged
/// file), each new artboard takes the smallest free id and the name stays at `u32::MAX`.
#[test]
fn convert_to_artboards_gives_each_object_the_next_id_and_name() {
let convert = |s: &mut Session| {
let ids: Vec<NodeId> = (0..3).map(|i| rect(s, 3000.0 + 300.0 * f64::from(i), 0.0, 200.0, 100.0)).collect();
sel(s, &ids);
assert_eq!(s.execute("artboard.convertToArtboards", &json!({})).unwrap()["artboards"], 3);
let d = &s.doc().unwrap().doc;
d.artboards.iter().map(|a| (a.id, a.name.clone())).collect::<Vec<_>>()
};
let named = |pairs: &[(u32, &str)]| pairs.iter().map(|&(id, n)| (id, n.to_string())).collect::<Vec<_>>();
// Artboards 1 and 2 are left after artboard 3 is deleted, so its id stays given out.
let mut s = session();
s.execute("artboard.new", &json!({})).unwrap();
s.execute("artboard.new", &json!({})).unwrap();
s.execute("artboard.delete", &json!({"index": 2})).unwrap();
let ids: Vec<u32> = s.doc().unwrap().doc.artboards.iter().map(|a| a.id).collect();
assert_eq!(ids, [1, 2]);
let got = convert(&mut s);
assert_eq!(got, named(&[(1, "Artboard 1"), (2, "Artboard 2"), (4, "Artboard 3"), (5, "Artboard 5"), (6, "Artboard 6")]));
let mut s = session();
std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc).artboards[0].id = u32::MAX;
let got = convert(&mut s);
let top = format!("Artboard {}", u32::MAX);
assert_eq!(got, named(&[(u32::MAX, "Artboard 1"), (1, &top), (2, &top), (3, &top)]));
}
/// Rearrange All Artboards refuses `columns` that are not a whole number (a fraction, a negative number or a string),
/// and the artboards stay where they were; 1.0, as the Rearrange All Artboards dialog sends a count, is one column.
#[test]