diff --git a/crates/geom/src/path.rs b/crates/geom/src/path.rs index 4455c4ed9..d836d2e02 100644 --- a/crates/geom/src/path.rs +++ b/crates/geom/src/path.rs @@ -278,7 +278,7 @@ impl SubPath { } let mut pos = vec![0.0]; for i in 0..self.segment_count() { - let len = kurbo::ParamCurveArclen::arclen(&self.segment(i), 1e-3); + let len = measured_arclen(&PathSeg::Cubic(self.segment(i)), 1e-3); pos.push(pos.last().copied().unwrap_or(0.0) + len); } pos @@ -621,7 +621,7 @@ 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 { +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 { PathSeg::Line(l) => ok(l.p0) && ok(l.p1), @@ -838,6 +838,13 @@ mod tests { assert_eq!(c.kind, AnchorKind::Corner); } + /// Handles too large to measure give the curve no length instead of subdividing it without end. + #[test] + fn anchor_positions_of_an_unmeasurable_curve_are_finite() { + let p = curve(Point::new(100.0, 1e300), Point::new(300.0, -1e300)); + assert_eq!(p.subpaths[0].anchor_arc_positions(), [0.0, 0.0]); + } + #[test] fn length_of_square() { assert!((square().length() - 40.0).abs() < 1e-6); diff --git a/crates/pathops/src/edit.rs b/crates/pathops/src/edit.rs index 32b47b7b9..2a39cccfa 100644 --- a/crates/pathops/src/edit.rs +++ b/crates/pathops/src/edit.rs @@ -3,7 +3,8 @@ use std::collections::{BTreeMap, BTreeSet}; -use kurbo::{CubicBez, ParamCurveArclen, Point, Rect, Vec2}; +use kurbo::{CubicBez, PathSeg, Point, Rect, Vec2}; +use vectorcraft_geom::path::measured_arclen; use vectorcraft_geom::{Anchor, AnchorKind, PathData, SubPath}; use crate::boolean::{Seg, segs_to_subpath}; @@ -115,7 +116,8 @@ fn simplify_run(run: &[Seg], opts: &SimplifyOptions) -> Vec { } let mut pts = Vec::new(); for (k, s) in piece.iter().enumerate() { - let len = s.c.arclen(1e-6); + // Guarded: kurbo subdivides without end on huge finite coordinates. + let len = measured_arclen(&PathSeg::Cubic(s.c), 1e-6); let m = ((len / (tol * 0.5)).ceil() as usize).clamp(4, 256); sample(&s.c, m, &mut pts, k == 0); } @@ -454,7 +456,7 @@ fn seam_error(original: &SubPath, before: &SubPath, fit: &SubPath, tol: f64) -> let mut pts = Vec::new(); for j in (0..sp.segment_count()).filter(|&j| !seam_only || is_seam(j)) { let c = sp.segment(j); - let m = ((c.arclen(1e-6) / (tol.max(1e-6) * 0.5)).ceil() as usize).clamp(4, 256); + let m = ((measured_arclen(&PathSeg::Cubic(c), 1e-6) / (tol.max(1e-6) * 0.5)).ceil() as usize).clamp(4, 256); sample(&c, m, &mut pts, true); } pts