diff --git a/crates/pathops/src/pathfinder.rs b/crates/pathops/src/pathfinder.rs index d3919f07b..593c6215c 100644 --- a/crates/pathops/src/pathfinder.rs +++ b/crates/pathops/src/pathfinder.rs @@ -614,11 +614,20 @@ fn outline_edges(arr: &Arrangement, shapes: &[Shape]) -> Vec { }) .collect(); ts.sort_by(f64::total_cmp); - let start_is_junction = ts.first().is_some_and(|&t| t < 1e-9); + // Junctions are compared where they are, not by parameter: a shape's own anchor and + // a crossing computed a hair from it are one junction, and one within `reach` of an + // end of the segment is that end. Else a sliver piece is left between them (#3870). + let at = |t: f64| kurbo::ParamCurve::eval(&c, t); + let start_is_junction = ts.first().is_some_and(|&t| at(t).distance(c.p0) <= reach); let mut cuts: Vec = vec![0.0]; - cuts.extend(ts.into_iter().filter(|&t| t > 1e-9 && t < 1.0 - 1e-9)); + for t in ts { + let p = at(t); + let last = at(cuts.last().copied().unwrap_or(0.0)); + if p.distance(last) > reach && p.distance(c.p3) > reach { + cuts.push(t); + } + } cuts.push(1.0); - cuts.dedup_by(|a, b| (*a - *b).abs() < 1e-9); for (k, w) in cuts.windows(2).enumerate() { let sub = c.subsegment(w[0]..w[1]); let seg = if line { Seg::line(sub.p0, sub.p3) } else { Seg { c: sub, line: false } }; diff --git a/crates/pathops/tests/ops.rs b/crates/pathops/tests/ops.rs index 871f531ff..0833e5b26 100644 --- a/crates/pathops/tests/ops.rs +++ b/crates/pathops/tests/ops.rs @@ -219,6 +219,17 @@ fn pf_outline_edges() { assert!(close(len, 80.0, 1e-9)); } +/// #3870: a circle whose anchors sit on the square's edge it crosses gives the four edges of the +/// arrangement, meeting at those anchors, and no sliver between an anchor and the crossing +/// computed a hair from it. +#[test] +fn pf_outline_has_no_sliver_where_an_anchor_is_the_crossing() { + let s = vec![shape(rect(0.0, 0.0, 100.0, 100.0), 1), shape(circle(100.0, 100.0, 50.0), 2)]; + let r = pathfinder(PathfinderOp::Outline, &s); + assert_eq!(r.len(), 4, "{r:?}"); + assert!(r.iter().all(|s| s.path.length() > 1.0), "no sliver: {r:?}"); +} + #[test] fn pf_outline_dedups_shared_edges() { // Two squares sharing an edge exactly.