diff --git a/crates/engine/src/cmd/cut.rs b/crates/engine/src/cmd/cut.rs index a0dfe6d2e..a7a2e47f1 100644 --- a/crates/engine/src/cmd/cut.rs +++ b/crates/engine/src/cmd/cut.rs @@ -221,8 +221,8 @@ fn rect_cut(s: &mut Session, p: &Value) -> Result { const C: &str = "path.rectCut"; let rect = match p.get("rect") { Some(v) => { - let a: Vec = v.as_array().map(|a| a.iter().filter_map(Value::as_f64).collect()).unwrap_or_default(); - let [x, y, w, h] = a[..] else { return Err(bad(C, "rect must be [x, y, width, height]")) }; + // All four or nothing: a malformed entry dropped would cut a different region. + let [x, y, w, h] = finite_numbers(v).ok_or_else(|| bad(C, "rect must be [x, y, width, height]"))?; Rect::new(x, y, x + w, y + h).abs() } None => Rect::from_points(point_req(p, "from", C)?, point_req(p, "to", C)?), diff --git a/crates/engine/src/tests_cut.rs b/crates/engine/src/tests_cut.rs index 7bda31d8d..a0a0d2a7b 100644 --- a/crates/engine/src/tests_cut.rs +++ b/crates/engine/src/tests_cut.rs @@ -118,6 +118,24 @@ fn line_cut_that_misses_changes_nothing() { assert!(s.execute("path.lineCut", &json!({"from": [5, 5]})).is_err()); } +#[test] +fn rect_cut_rejects_non_numeric_rectangle_elements_without_modifying_art() { + let mut s = session(); + let id = rect(&mut s, 0.0, 0.0, 100.0, 100.0); + select(&mut s, &[id]); + let original = bounds(&s, id); + let history = undo_depth(&s); + // The old parser dropped the string and used the four remaining values. + for params in [json!([0, "ignored", 10, 50, 50]), json!([0, 10, 50, null, 50]), json!([0, 10, 50, 50, 5])] { + assert!(s.execute("path.rectCut", &json!({"rect": params})).is_err()); + assert!(near(bounds(&s, id), original), "failed cut must not change geometry"); + assert_eq!(undo_depth(&s), history, "failed cut must not add undo history"); + } + // Proper rectangular cuts still work. + s.execute("path.rectCut", &json!({"rect": [10, 10, 70, 70]})).unwrap(); + assert!(near(bounds(&s, id), Rect::new(10.0, 10.0, 80.0, 80.0))); +} + #[test] fn rect_cut_crops_to_real_geometry() { let mut s = session();