#801: vector.create accepts 0 on an axis the geometry has none of and refuses a flattening size; points scaling tests

This commit is contained in:
bflatastic
2026-10-18 17:54:24 +07:00
parent 55aa0165fd
commit f1d336249a
2 changed files with 42 additions and 13 deletions
+14 -10
View File
@@ -167,15 +167,23 @@ pub fn parse_path_data(d: &str) -> Option<figcraft_geom::BezPath> {
}
/// 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<f64>, height: Option<f64>, bw: f64, bh: f64, what: &str) -> std::result::Result<(f64, f64), String> {
let fit = |asked: Option<f64>, 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<f64>, height: Option<f64>, 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<Value> {
+28 -3
View File
@@ -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::<Vec<_>>();
// 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}");
}