diff --git a/crates/engine/src/cmd/create.rs b/crates/engine/src/cmd/create.rs index 84fa419a..3a9f6d67 100644 --- a/crates/engine/src/cmd/create.rs +++ b/crates/engine/src/cmd/create.rs @@ -167,15 +167,23 @@ pub fn parse_path_data(d: &str) -> Option { } /// How much a vector's path is scaled to take the size asked for: with both sides, each scales -/// independently (as resizing a vector does); with one, the other keeps its proportion. A size -/// asked for on an axis the geometry has none of is an error, not silently ignored. +/// independently (as resizing a vector does); with one, the other keeps its proportion. A size on +/// an axis the geometry has none of is an error unless it is 0 (a line's height), and a size of 0 +/// or less on an axis it has is refused rather than flattening it. fn vector_scale(width: Option, height: Option, bw: f64, bh: f64, what: &str) -> std::result::Result<(f64, f64), String> { let fit = |asked: Option, have: f64, axis: &str| match asked { - Some(a) if have > 1e-9 => Ok(a.max(0.0) / have), - Some(_) => Err(format!("`{axis}` can't size {what} with no {} extent", if axis == "width" { "horizontal" } else { "vertical" })), None => Ok(1.0), + Some(a) if have <= 1e-9 => { + if a == 0.0 { + Ok(1.0) + } else { + Err(format!("`{axis}` can't size {what} with no {} extent", if axis == "width" { "horizontal" } else { "vertical" })) + } + } + Some(a) if a > 0.0 && (a / have).is_finite() => Ok(a / have), + Some(_) => Err(format!("`{axis}` must be a positive, finite size: 0 or less would flatten {what}")), }; - let (sx, sy) = match (width, height) { + Ok(match (width, height) { (Some(_), Some(_)) => (fit(width, bw, "width")?, fit(height, bh, "height")?), (Some(_), None) => { let s = fit(width, bw, "width")?; @@ -186,11 +194,7 @@ fn vector_scale(width: Option, height: Option, bw: f64, bh: f64, what: (s, s) } (None, None) => (1.0, 1.0), - }; - if sx <= 0.0 && sy <= 0.0 { - return Err(format!("`width` and `height` would make {what} 0×0")); - } - Ok((sx, sy)) + }) } pub fn create(s: &mut Session, p: &Value) -> Result { diff --git a/crates/engine/src/vector_behaviour_tests.rs b/crates/engine/src/vector_behaviour_tests.rs index 35d61074..fb734085 100644 --- a/crates/engine/src/vector_behaviour_tests.rs +++ b/crates/engine/src/vector_behaviour_tests.rs @@ -468,7 +468,32 @@ fn vector_paths_take_the_requested_size_and_refuse_empty_geometry() { assert!(e.contains("horizontal extent"), "{e}"); let e = s.execute("vector.create", &json!({"path": "M0 0 L24 0", "height": 16})).unwrap_err().to_string(); assert!(e.contains("vertical extent"), "{e}"); - // A size that would collapse both axes is refused too. - let e = s.execute("vector.create", &json!({"path": path, "width": 0})).unwrap_err().to_string(); - assert!(e.contains("0×0"), "{e}"); + // But 0 on an axis it has none of is what it already is: a horizontal line with height 0. + let line = id_of(&ex(&mut s, "vector.create", json!({"path": "M0 0 L24 0", "width": 16, "height": 0}))["id"]).unwrap(); + let n = s.doc().unwrap().node(line).unwrap(); + assert_eq!((n.width, n.height), (16.0, 0.0)); + // A size of 0 or less on an axis it has would flatten it, so it is refused, alone or with the other. + for p in [json!({"path": path, "width": 0}), json!({"path": path, "width": 16, "height": 0}), json!({"path": path, "width": -4, "height": 8})] { + let e = s.execute("vector.create", &p).unwrap_err().to_string(); + assert!(e.contains("positive"), "{p}: {e}"); + } +} + +#[test] +fn vector_points_take_the_requested_size() { + let mut s = Session::new(); + let points = json!([[10, 20], [34, 20], [34, 32]]); + let corners = |s: &Session, id: NodeId| net(s, id).vertices.iter().map(|v| (v.x, v.y)).collect::>(); + // Both sides: each axis scales on its own; the vertices move with the box. + let both = id_of(&ex(&mut s, "vector.create", json!({"points": points, "x": 10, "y": 20, "width": 48, "height": 6}))["id"]).unwrap(); + let n = s.doc().unwrap().node(both).unwrap(); + assert_eq!((n.width, n.height), (48.0, 6.0)); + assert_eq!(corners(&s, both), vec![(0.0, 0.0), (48.0, 0.0), (48.0, 6.0)]); + // One side: the other keeps its proportion. + let one = id_of(&ex(&mut s, "vector.create", json!({"points": points, "height": 24}))["id"]).unwrap(); + let n = s.doc().unwrap().node(one).unwrap(); + assert_eq!((n.width, n.height), (48.0, 24.0)); + assert_eq!(corners(&s, one), vec![(0.0, 0.0), (48.0, 0.0), (48.0, 24.0)]); + let e = s.execute("vector.create", &json!({"points": points, "width": 16, "height": -1})).unwrap_err().to_string(); + assert!(e.contains("positive"), "{e}"); }