diff --git a/crates/geom/src/arc.rs b/crates/geom/src/arc.rs index de66388de..c4fe640ae 100644 --- a/crates/geom/src/arc.rs +++ b/crates/geom/src/arc.rs @@ -22,7 +22,7 @@ impl ArcPath { let mut segs = Vec::new(); let mut cum = 0.0; for s in path.to_bezpath().segments() { - let l = s.arclen(ACCURACY); + let l = crate::path::measured_arclen(&s, ACCURACY); if l.is_finite() && l > 1e-9 { segs.push((s, cum, l)); cum += l; @@ -51,7 +51,8 @@ impl ArcPath { let s = if self.closed && self.len > 0.0 { s.rem_euclid(self.len) } else { s.clamp(0.0, self.len) }; let i = self.segs.partition_point(|(_, c, _)| *c <= s).saturating_sub(1); let Some(&(seg, c, l)) = self.segs.get(i) else { return (Point::ZERO, Vec2::new(1.0, 0.0)) }; - let t = seg.inv_arclen((s - c).min(l), ACCURACY).clamp(0.0, 1.0); + // (The solver's precision is relative to the segment's length: a long one needs a looser one.) + let t = seg.inv_arclen((s - c).min(l), ACCURACY.max(l * 1e-9)).clamp(0.0, 1.0); let p = seg.eval(t); let (t0, t1) = ((t - 1e-4).max(0.0), (t + 1e-4).min(1.0)); let d = seg.eval(t1) - seg.eval(t0); diff --git a/crates/geom/src/corners.rs b/crates/geom/src/corners.rs index 46449f43a..fe64d9e64 100644 --- a/crates/geom/src/corners.rs +++ b/crates/geom/src/corners.rs @@ -39,7 +39,7 @@ impl Side { /// The side's length. fn length(&self) -> f64 { - if self.straight() { self.curve.p0.distance(self.curve.p3) } else { self.curve.arclen(ARCLEN_ACCURACY) } + if self.straight() { self.curve.p0.distance(self.curve.p3) } else { crate::path::measured_arclen(&kurbo::PathSeg::Cubic(self.curve), ARCLEN_ACCURACY) } } /// The parameter `s` along the side from the corner, the point there and the direction the diff --git a/crates/geom/src/path.rs b/crates/geom/src/path.rs index 110997efc..f7828fb80 100644 --- a/crates/geom/src/path.rs +++ b/crates/geom/src/path.rs @@ -561,10 +561,27 @@ impl PathData { } /// Total length of all segments. pub fn length(&self) -> f64 { - self.to_bezpath().segments().map(|s| kurbo::ParamCurveArclen::arclen(&s, 1e-6)).sum() + self.to_bezpath().segments().map(|s| measured_arclen(&s, 1e-6)).sum() } } +/// The largest x or y, in size, of a segment's control points for which its length is measured. +/// Document coordinates are far inside it; past it (or not finite) the integrator's arithmetic +/// overflows and it subdivides without end. +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`]. +pub(crate) 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 { + PathSeg::Line(l) => ok(l.p0) && ok(l.p1), + 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 } +} + /// The largest x or y, in size, of a segment's control points for which [`segment_bounds`] /// measures the segment by its extrema. kurbo adds up to eight coordinates together when it finds a /// segment's extrema and evaluates the segment there, and a sum of eight coordinates of this size or