From 4b9a611189047eeb691c362ca8fe206676708bb6 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 08:30:44 -0800 Subject: [PATCH] artboard.new: keep sides below 1 pt as given artboard.new no longer raises a width or height below 1 pt to 1 pt, and artboard.setProps raises only a supplied side, so an edit to a small artboard's name or lock keeps its size. --- crates/engine/src/cmd/layer.rs | 17 +++++++++++++---- crates/engine/src/tests/newdoc.rs | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/layer.rs b/crates/engine/src/cmd/layer.rs index 43d9a8e05f..f93cdca7b9 100644 --- a/crates/engine/src/cmd/layer.rs +++ b/crates/engine/src/cmd/layer.rs @@ -850,14 +850,23 @@ fn move_node(s: &mut Session, p: &Value) -> Result { ok() } -fn rect_from(p: &Value, default: Rect) -> Rect { +/// The rectangle from `x`, `y`, `width` and `height`, each defaulting to `default`'s. Sides are kept as given. +fn rect_unclamped(p: &Value, default: Rect) -> Rect { let x = f64_or(p, "x", default.x0); let y = f64_or(p, "y", default.y0); - let w = f64_or(p, "width", default.width()).max(1.0); - let h = f64_or(p, "height", default.height()).max(1.0); + let w = f64_or(p, "width", default.width()); + let h = f64_or(p, "height", default.height()); Rect::new(x, y, x + w, y + h) } +/// [`rect_unclamped`] with each supplied side raised to at least 1 pt, as `artboard.setProps` uses it. +/// A side left out keeps `default`'s, even below 1 pt. +fn rect_from(p: &Value, default: Rect) -> Rect { + let r = rect_unclamped(p, default); + let floor = |key: &str, side: f64| if p.get(key).and_then(Value::as_f64).is_some() { side.max(1.0) } else { side }; + Rect::new(r.x0, r.y0, r.x0 + floor("width", r.width()), r.y0 + floor("height", r.height())) +} + /// An artboard command's `color` param, its background color: None when absent, `Some(None)` for /// null (no color), else `Some(Some(color))`. fn artboard_color(p: &Value, cmd: &str) -> Result>> { @@ -942,7 +951,7 @@ fn artboard_new(s: &mut Session, p: &Value) -> Result { src + side.offset(src) } None if p.get("beside").is_some_and(|v| !v.is_null()) => return Err(bad("artboard.new", "`beside` needs `side`")), - None => rect_from(p, def), + None => rect_unclamped(p, def), }; // A size taken from another artboard (`side`, or width or height left out) keeps to the limit too. if !super::newdoc::side_in_range(r.width()) || !super::newdoc::side_in_range(r.height()) { diff --git a/crates/engine/src/tests/newdoc.rs b/crates/engine/src/tests/newdoc.rs index 0b573a96f3..d2e5f1b115 100644 --- a/crates/engine/src/tests/newdoc.rs +++ b/crates/engine/src/tests/newdoc.rs @@ -291,6 +291,20 @@ fn file_new_and_artboard_new_take_the_same_largest_side() { assert!(err.contains(in_meters), "{p}: {err}"); } assert_eq!(boards(&s).len(), 2); + + // A side below 1 pt is kept as given, as file.new keeps it, and a size taken from such an artboard keeps it too. + let mut s = new_doc(json!({"width": 0.5, "height": 0.25})); + assert_eq!(boards(&s), [Rect::new(0.0, 0.0, 0.5, 0.25)]); + s.execute("artboard.new", &json!({"x": 0, "y": 10, "width": 0.5, "height": 0.75})).unwrap(); + s.execute("artboard.new", &json!({"x": 0, "y": 20})).unwrap(); + assert_eq!(boards(&s)[1..], [Rect::new(0.0, 10.0, 0.5, 10.75), Rect::new(0.0, 20.0, 0.5, 20.75)]); + // artboard.setProps keeps such sides when it changes something else, on a locked artboard too, + // and still raises a supplied side to 1 pt. + s.execute("artboard.setProps", &json!({"index": 1, "name": "Small"})).unwrap(); + s.execute("artboard.setProps", &json!({"index": 2, "locked": true})).unwrap(); + s.execute("artboard.setProps", &json!({"index": 2, "name": "Small locked"})).unwrap(); + s.execute("artboard.setProps", &json!({"index": 0, "height": 0.5})).unwrap(); + assert_eq!(boards(&s), [Rect::new(0.0, 0.0, 0.5, 1.0), Rect::new(0.0, 10.0, 0.5, 10.75), Rect::new(0.0, 20.0, 0.5, 20.75)]); } #[test]