Fix #4151: verify continuous angular direction for cubic arc conversion (#4151)

This commit is contained in:
el-j
2026-10-29 09:57:34 +07:00
committed by bflatastic
parent a24326e046
commit c0d7d22d42
2 changed files with 38 additions and 0 deletions
+25
View File
@@ -127,9 +127,34 @@ fn circular_subpath(sub: &Sub) -> Option<CircularSub> {
return None;
}
// Verify that the curve progresses monotonically around the circle without reversing,
// both along each cubic segment's angular derivative and across sampled intervals,
// and that closed subpaths make exactly one full turn while open subpaths do not exceed one turn.
let mut total_turn = 0.0;
let mut dir: Option<bool> = None;
let ang_threshold = radius * radius * 1e-9;
let mut seg_from = sub.start;
for seg in &sub.segs {
let Seg::Cubic(a, b, to) = *seg else { return None };
for step in 0..=32 {
let t = f64::from(step) / 32.0;
let p = cubic_point(seg_from, a, b, to, t);
let u = 1.0 - t;
let dx = 3.0 * u * u * (a.x - seg_from.x) + 6.0 * u * t * (b.x - a.x) + 3.0 * t * t * (to.x - b.x);
let dy = 3.0 * u * u * (a.y - seg_from.y) + 6.0 * u * t * (b.y - a.y) + 3.0 * t * t * (to.y - b.y);
let cross = (p.x - centre.x) * dy - (p.y - centre.y) * dx;
if cross.abs() > ang_threshold {
let is_ccw = cross > 0.0;
if let Some(expected_ccw) = dir {
if is_ccw != expected_ccw {
return None;
}
} else {
dir = Some(is_ccw);
}
}
}
seg_from = to;
}
for w in samples.windows(2) {
let (p0, p1) = (w[0], w[1]);
let (v0x, v0y) = (p0.x - centre.x, p0.y - centre.y);
+13
View File
@@ -279,6 +279,19 @@ fn a_closed_sliver_along_a_circle_is_not_a_circle() {
assert_eq!(kinds(&modern), ["SPLINE"]);
}
#[test]
fn cubic_with_hidden_angular_reversal_is_not_promoted_to_arc() {
let d = doc_with(|d| {
let mut b = vectorcraft_geom::BezPath::new();
b.move_to(Point::new(-8.404028665134, 62.061475842818));
b.curve_to(Point::new(-13.285253116737, 62.103123153545), Point::new(52.811163576591, 52.471253544459), Point::new(60.0, 50.0));
vec![path(d, PathData::from_bezpath(&b), Paint::None, Paint::solid(Color::BLACK), 1.0)]
});
let dxf = export(&d, &opts(&d)).unwrap();
assert_ne!(kinds(&dxf), ["ARC"]);
assert_eq!(kinds(&dxf), ["SPLINE"]);
}
#[test]
fn spline_closed_flags_follow_path_intent_across_versions_without_changing_geometry() {
let cases = [(true, true), (true, false), (false, false), (false, true)];