Merge pull request #3899 from storytold/fix/3870-outline-sliver

Pathfinder Outline: junctions a hair apart are one cut, so no sliver path is left (#3870)
This commit is contained in:
BFlat
2026-10-24 05:30:53 +07:00
committed by GitHub
2 changed files with 23 additions and 3 deletions
+12 -3
View File
@@ -614,11 +614,20 @@ fn outline_edges(arr: &Arrangement, shapes: &[Shape]) -> Vec<Shape> {
})
.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<f64> = 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 } };
+11
View File
@@ -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.