diff --git a/crates/engine/src/cmd/shape.rs b/crates/engine/src/cmd/shape.rs index 10c737ee..12c44b8b 100644 --- a/crates/engine/src/cmd/shape.rs +++ b/crates/engine/src/cmd/shape.rs @@ -170,13 +170,14 @@ pub fn specs() -> Vec { } /// Keeps a side length in `0..=MAX_PT` so a huge value cannot exhaust memory when rendering. +/// An overflow to infinity lands on the limit; NaN becomes 0. fn clamp_side(v: f64) -> f64 { - if v.is_finite() { v.clamp(0.0, MAX_PT) } else { 0.0 } + if v.is_nan() { 0.0 } else { v.clamp(0.0, MAX_PT) } } /// Keeps a coordinate within `±POS_LIMIT`. fn clamp_pos(v: f64) -> f64 { - if v.is_finite() { v.clamp(-POS_LIMIT, POS_LIMIT) } else { 0.0 } + if v.is_nan() { 0.0 } else { v.clamp(-POS_LIMIT, POS_LIMIT) } } fn move_by(s: &mut Session, p: &Value) -> Result { @@ -216,8 +217,8 @@ fn resize(s: &mut Session, p: &Value) -> Result { x.h = clamp_side(x.h); // Rotated shapes resize about their centre; others keep the top-left. if x.rot != 0.0 { - x.x = cx - x.w / 2.0; - x.y = cy - x.h / 2.0; + x.x = clamp_pos(cx - x.w / 2.0); + x.y = clamp_pos(cy - x.h / 2.0); } } Ok(()) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index b8cf0ace..7afcb102 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3647,4 +3647,13 @@ fn shape_geometry_commands_limit_size_and_position() { s.execute("shape.move", &json!({"x": 1e100, "y": -1e100})).unwrap(); let x = xfrm(&s); assert!(within(&s), "move stored {x:?}"); + // A move that overflows to infinity lands on the limit, not back at 0. + s.execute("shape.move", &json!({"x": 1e308, "dx": 1e308})).unwrap(); + assert_eq!(xfrm(&s).x, 100_000.0); + // A rotated shape resizes about its centre and still stays inside the limit. + s.execute("shape.setBounds", &json!({"x": 99_000, "y": 0, "w": 10, "h": 10})).unwrap(); + s.execute("shape.rotate", &json!({"deg": 45})).unwrap(); + s.execute("shape.resize", &json!({"w": 1e100})).unwrap(); + let x = xfrm(&s); + assert!(within(&s), "rotated resize stored {x:?}"); }