M1: refuse negative vector sizes

A negative width or height was clamped to zero by the scale helper, producing a layer smaller than asked for; vector.create now says so. Regression test covers width -10 with height 8.
This commit is contained in:
phonedu
2026-10-18 19:00:25 +08:00
parent aff3c4f18b
commit fec902b7d5
3 changed files with 11 additions and 4 deletions
+6 -1
View File
@@ -170,8 +170,13 @@ pub fn parse_path_data(d: &str) -> Option<figcraft_geom::BezPath> {
/// 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.
fn vector_scale(width: Option<f64>, height: Option<f64>, bw: f64, bh: f64, what: &str) -> std::result::Result<(f64, f64), String> {
for (axis, asked) in [("width", width), ("height", height)] {
if asked.is_some_and(|a| a < 0.0) {
return Err(format!("`{axis}` can't be negative"));
}
}
let fit = |asked: Option<f64>, have: f64, axis: &str| match asked {
Some(a) if have > 1e-9 => Ok(a.max(0.0) / have),
Some(a) if have > 1e-9 => Ok(a / have),
Some(_) => Err(format!("`{axis}` can't size {what} with no {} extent", if axis == "width" { "horizontal" } else { "vertical" })),
None => Ok(1.0),
};
+3 -1
View File
@@ -468,7 +468,9 @@ 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.
// A size that would collapse both axes is refused too, and so is a negative one.
let e = s.execute("vector.create", &json!({"path": path, "width": 0})).unwrap_err().to_string();
assert!(e.contains("0×0"), "{e}");
let e = s.execute("vector.create", &json!({"path": path, "width": -10, "height": 8})).unwrap_err().to_string();
assert!(e.contains("can't be negative"), "{e}");
}
+2 -2
View File
@@ -429,8 +429,8 @@ on; `key` resolves keys the way the canvas does. Commands behind keys:
`vector.create {path | points}` makes the layer's box from the geometry; `width`/`height` scale
the geometry to the size asked for (with both, each side independently; with one, the other keeps
its proportion). A path or point set that draws nothing is refused instead of making an invisible
0×0 layer, as is a size on an axis the geometry has none of and a size that would collapse both
axes.
0×0 layer, as is a size on an axis the geometry has none of, a negative size, and a size that
would collapse both axes.
Vector edit mode is driven with `run_command` (or `pointer`/`key` for gestures):
`vector.edit.begin {id? | ids?}` (default the selection; converts shapes to vectors; several layers