From 2bd02ecb2a1580c5148b1c849dd424f944aa5275 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 04:29:09 -0800 Subject: [PATCH] Paths: a quadratic segment whose points coincide measures zero length instead of NaN measured_arclen returned kurbo's arc length unchecked. For a quadratic segment whose three points coincide, kurbo 0.13.1's QuadBez::arclen multiplies 0 by infinity and returns NaN. measured_arclen now returns 0 when the measurement is not finite, the same value it gives a segment it does not measure. Finite results are unchanged. --- crates/geom/src/path.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/geom/src/path.rs b/crates/geom/src/path.rs index 59a8f30660..3c1b45a52b 100644 --- a/crates/geom/src/path.rs +++ b/crates/geom/src/path.rs @@ -688,7 +688,8 @@ fn box_gap(c: &CubicBez, p: Point) -> Option { const MEASURE_LIMIT: f64 = 1e9; /// The length of `seg` to `accuracy`; 0 for a segment whose control points are not finite or past -/// [`MEASURE_LIMIT`]. +/// [`MEASURE_LIMIT`], and 0 when the measurement is not finite (kurbo gives NaN for a quadratic +/// segment whose three points coincide). pub fn measured_arclen(seg: &PathSeg, accuracy: f64) -> f64 { let ok = |p: Point| p.x.is_finite() && p.y.is_finite() && p.x.abs() <= MEASURE_LIMIT && p.y.abs() <= MEASURE_LIMIT; let fits = match seg { @@ -696,7 +697,11 @@ pub fn measured_arclen(seg: &PathSeg, accuracy: f64) -> f64 { PathSeg::Quad(q) => ok(q.p0) && ok(q.p1) && ok(q.p2), PathSeg::Cubic(c) => ok(c.p0) && ok(c.p1) && ok(c.p2) && ok(c.p3), }; - if fits { kurbo::ParamCurveArclen::arclen(seg, accuracy) } else { 0.0 } + if !fits { + return 0.0; + } + let len = kurbo::ParamCurveArclen::arclen(seg, accuracy); + if len.is_finite() { len } else { 0.0 } } /// The largest x or y, in size, of a segment's control points for which [`segment_bounds`] @@ -1056,6 +1061,19 @@ mod tests { assert!((square().length() - 40.0).abs() < 1e-6); } + /// A quadratic segment whose three points coincide measures 0, not NaN; quadratic segments + /// with only two points together, and an ordinary one, keep kurbo's length. + #[test] + fn measured_arclen_of_a_quad_whose_points_coincide_is_zero() { + let quad = |a: (f64, f64), b: (f64, f64), c: (f64, f64)| PathSeg::Quad(kurbo::QuadBez::new(a, b, c)); + assert_eq!(measured_arclen(&quad((5.0, 5.0), (5.0, 5.0), (5.0, 5.0)), 1e-6), 0.0); + assert_eq!(measured_arclen(&quad((0.0, 0.0), (0.0, 0.0), (0.0, 0.0)), 1e-3), 0.0); + assert!((measured_arclen(&quad((0.0, 0.0), (0.0, 0.0), (10.0, 0.0)), 1e-6) - 10.0).abs() < 1e-6); + assert!((measured_arclen(&quad((0.0, 0.0), (10.0, 0.0), (0.0, 0.0)), 1e-6) - 10.0).abs() < 1e-6); + let ordinary = quad((0.0, 0.0), (10.0, 0.0), (10.0, 10.0)); + assert_eq!(measured_arclen(&ordinary, 1e-6), kurbo::ParamCurveArclen::arclen(&ordinary, 1e-6)); + } + #[test] fn serde_roundtrip() { let p = square();