Skip to content

Commit 6ca25e4

Browse files
Fix capacity overflow in offset path node (#3308)
* Fix capacity overflow in offset path node * Reverted accuracy to 1e-3 * Refactor offset path segment check * Add squared distance for degenerate cubic bezier curves
1 parent 52e98ea commit 6ca25e4

File tree

1 file changed

+28
-9
lines changed

1 file changed

+28
-9
lines changed

node-graph/gcore/src/vector/algorithms/offset_subpath.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ const CUBIC_REGULARIZATION_ACCURACY: f64 = 0.5;
88
const CUBIC_TO_BEZPATH_ACCURACY: f64 = 1e-3;
99
/// Constant used to determine if `f64`s are equivalent.
1010
pub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-7;
11+
/// Squared version to avoid sqrt in distance checks.
12+
const MAX_ABSOLUTE_DIFFERENCE_SQUARED: f64 = MAX_ABSOLUTE_DIFFERENCE * MAX_ABSOLUTE_DIFFERENCE;
13+
const MAX_FITTED_SEGMENTS: usize = 10000;
1114

1215
/// Reduces the segments of the bezpath into simple subcurves, then offset each subcurve a set `distance` away.
1316
/// The intersections of segments of the subpath are joined using the method specified by the `join` argument.
@@ -19,15 +22,31 @@ pub fn offset_bezpath(bezpath: &BezPath, distance: f64, join: Join, miter_limit:
1922
}
2023

2124
let mut bezpaths = bezpath
22-
.segments()
23-
.map(|bezier| bezier.to_cubic())
24-
.map(|cubic_bez| {
25-
let cubic_offset = kurbo::offset::CubicOffset::new_regularized(cubic_bez, distance, CUBIC_REGULARIZATION_ACCURACY);
26-
27-
kurbo::fit_to_bezpath(&cubic_offset, CUBIC_TO_BEZPATH_ACCURACY)
28-
})
29-
.filter(|bezpath| bezpath.get_seg(1).is_some()) // In some cases the reduced and scaled bézier is marked by is_point (so the subpath is empty).
30-
.collect::<Vec<BezPath>>();
25+
.segments()
26+
.map(|bezier| bezier.to_cubic())
27+
.filter_map(|cubic_bez| {
28+
// Skip degenerate curves where all control points are at the same location.
29+
// Offsetting a point is undefined and causes infinite recursion in fit_to_bezpath.
30+
let start = cubic_bez.p0;
31+
let is_degenerate = start.distance_squared(cubic_bez.p1) < MAX_ABSOLUTE_DIFFERENCE_SQUARED
32+
&& start.distance_squared(cubic_bez.p2) < MAX_ABSOLUTE_DIFFERENCE_SQUARED
33+
&& start.distance_squared(cubic_bez.p3) < MAX_ABSOLUTE_DIFFERENCE_SQUARED;
34+
35+
if is_degenerate {
36+
return None;
37+
}
38+
39+
let cubic_offset = kurbo::offset::CubicOffset::new_regularized(cubic_bez, distance, CUBIC_REGULARIZATION_ACCURACY);
40+
41+
let fitted = kurbo::fit_to_bezpath(&cubic_offset, CUBIC_TO_BEZPATH_ACCURACY);
42+
43+
if fitted.segments().count() > MAX_FITTED_SEGMENTS {
44+
None
45+
} else {
46+
fitted.get_seg(1).is_some().then_some(fitted)
47+
}
48+
})
49+
.collect::<Vec<BezPath>>();
3150

3251
// Clip or join consecutive Subpaths
3352
for i in 0..bezpaths.len() - 1 {

0 commit comments

Comments
 (0)