diff --git a/crates/geom/src/measure.rs b/crates/geom/src/measure.rs index 03de47cd0..717ec9d69 100644 --- a/crates/geom/src/measure.rs +++ b/crates/geom/src/measure.rs @@ -8,11 +8,16 @@ pub const FLATTEN_TOL: f64 = 0.05; /// The most flattened points a path is measured with; longer paths are refused. pub const MAX_SAMPLES: usize = 40_000; -/// Every coordinate is finite and of a sane magnitude: within [`crate::MAX_COORD`], the cap the -/// shape builders hold their sizes, radii and angles to (inclusive, so a path built at the cap -/// still measures). +/// How far out a measured coordinate may lie. The shape builders hold their sizes, radii and +/// angles to [`crate::MAX_COORD`], so a path built at the cap has its points there and the +/// control points of its curves a little past them (the cubics approximating an arc reach past +/// the arc's box by up to about a tenth of the radius). Half as much again keeps every built path +/// measurable and still refuses the absurd. +pub const MAX_MEASURED_COORD: f64 = crate::MAX_COORD * 1.5; + +/// Every coordinate is finite and of a sane magnitude (within [`MAX_MEASURED_COORD`]). pub fn finite_path(path: &BezPath) -> bool { - let ok = |p: &Point| p.x.is_finite() && p.y.is_finite() && p.x.abs() <= crate::MAX_COORD && p.y.abs() <= crate::MAX_COORD; + let ok = |p: &Point| p.x.is_finite() && p.y.is_finite() && p.x.abs() <= MAX_MEASURED_COORD && p.y.abs() <= MAX_MEASURED_COORD; path.elements().iter().all(|el| match el { PathEl::MoveTo(p) | PathEl::LineTo(p) => ok(p), PathEl::QuadTo(a, b) => ok(a) && ok(b), diff --git a/crates/geom/src/tests.rs b/crates/geom/src/tests.rs index 3e9fdce1e..24f47e1c5 100644 --- a/crates/geom/src/tests.rs +++ b/crates/geom/src/tests.rs @@ -68,11 +68,25 @@ fn paths_at_the_cap_still_measure() { line.move_to(Point::new(-MAX_COORD, 0.0)); line.line_to(Point::new(MAX_COORD, -MAX_COORD)); assert!(polylines(&line).is_some(), "a line between the cap's corners measures"); + // A curve at the cap: the cubics approximating the arc have control points past the cap, and + // the path still measures. + let arc = ellipse(MAX_COORD, 1.0, -0.15, 0.2, 0.0); + let reach = arc + .elements() + .iter() + .flat_map(|el| match el { + kurbo::PathEl::CurveTo(a, b, _) => vec![a.x, b.x], + _ => vec![], + }) + .fold(0.0, f64::max); + assert!(reach > MAX_COORD, "a control point lies past the cap: {reach}"); + assert!(finite_path(&arc)); + assert!(polylines(&arc).is_some(), "an arc at the cap measures"); let mut past = BezPath::new(); past.move_to(Point::new(0.0, 0.0)); - past.line_to(Point::new(MAX_COORD + 1.0, 0.0)); + past.line_to(Point::new(2.0 * MAX_COORD, 0.0)); assert!(!finite_path(&past)); - assert!(polylines(&past).is_none(), "a coordinate past the cap is refused"); + assert!(polylines(&past).is_none(), "a coordinate well past the cap is refused"); } #[test]