diff --git a/crates/geom/src/units.rs b/crates/geom/src/units.rs index 7b5aae775..2e138c185 100644 --- a/crates/geom/src/units.rs +++ b/crates/geom/src/units.rs @@ -110,6 +110,10 @@ impl Unit { /// Ruler tick spacing (major, subdivisions) in points for a given zoom (screen px per pt). pub fn ruler_ticks(self, zoom: f64) -> (f64, u32) { + // A non-positive zoom can keep the major-spacing loop growing forever. Clamp + // extreme positive values too, so saved/corrupt viewport state cannot overflow + // the spacing calculation before the view restores a usable zoom. + let zoom = if zoom.is_finite() && zoom > 0.0 { zoom.clamp(1e-6, 1e6) } else { 1.0 }; let (base, subs): (f64, &[u32]) = match self { Unit::Inches | Unit::InchesDecimal => (PT_PER_INCH, &[8, 4, 2]), Unit::Picas => (PT_PER_PICA, &[6, 3, 2]), @@ -357,6 +361,20 @@ mod tests { } } + #[test] + fn ruler_ticks_reject_invalid_zoom_without_hanging_or_overflowing() { + for unit in Unit::ALL { + let fallback = unit.ruler_ticks(1.0); + for bad in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -1.0, -0.0, 0.0] { + assert_eq!(unit.ruler_ticks(bad), fallback, "{unit:?}: {bad}"); + } + for extreme in [f64::MIN_POSITIVE, 1e-300, 1e300, f64::MAX] { + let (major, subdivisions) = unit.ruler_ticks(extreme); + assert!(major.is_finite() && major > 0.0 && subdivisions > 0, "{unit:?}: {extreme}"); + } + } + } + #[test] fn ruler_ticks_are_reasonable() { for u in Unit::ALL {