From 06248c6da8fe3264cd4b994589eeef010dc21e78 Mon Sep 17 00:00:00 2001 From: Ashish Mohapatra Date: Sat, 25 Oct 2025 01:16:57 +0530 Subject: [PATCH 1/4] Fix capacity overflow in offset path node --- .../src/vector/algorithms/offset_subpath.rs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs b/node-graph/gcore/src/vector/algorithms/offset_subpath.rs index 776a06a1db..ca8ba15b45 100644 --- a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs +++ b/node-graph/gcore/src/vector/algorithms/offset_subpath.rs @@ -5,9 +5,10 @@ use kurbo::{BezPath, Join, ParamCurve, PathEl, PathSeg}; /// Value to control smoothness and mathematical accuracy to offset a cubic Bezier. const CUBIC_REGULARIZATION_ACCURACY: f64 = 0.5; /// Accuracy of fitting offset curve to Bezier paths. -const CUBIC_TO_BEZPATH_ACCURACY: f64 = 1e-3; +const CUBIC_TO_BEZPATH_ACCURACY: f64 = 1e-2; /// Constant used to determine if `f64`s are equivalent. pub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-7; +const MAX_FITTED_SEGMENTS: usize = 10000; /// Reduces the segments of the bezpath into simple subcurves, then offset each subcurve a set `distance` away. /// The intersections of segments of the subpath are joined using the method specified by the `join` argument. @@ -19,15 +20,30 @@ pub fn offset_bezpath(bezpath: &BezPath, distance: f64, join: Join, miter_limit: } let mut bezpaths = bezpath - .segments() - .map(|bezier| bezier.to_cubic()) - .map(|cubic_bez| { - let cubic_offset = kurbo::offset::CubicOffset::new_regularized(cubic_bez, distance, CUBIC_REGULARIZATION_ACCURACY); - - kurbo::fit_to_bezpath(&cubic_offset, CUBIC_TO_BEZPATH_ACCURACY) - }) - .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). - .collect::>(); + .segments() + .map(|bezier| bezier.to_cubic()) + .filter_map(|cubic_bez| { + let start = cubic_bez.p0; + let is_degenerate = + (cubic_bez.p1 - start).hypot() < MAX_ABSOLUTE_DIFFERENCE && (cubic_bez.p2 - start).hypot() < MAX_ABSOLUTE_DIFFERENCE && (cubic_bez.p3 - start).hypot() < MAX_ABSOLUTE_DIFFERENCE; + + if is_degenerate { + return None; + } + + let cubic_offset = kurbo::offset::CubicOffset::new_regularized(cubic_bez, distance, CUBIC_REGULARIZATION_ACCURACY); + + let fitted = kurbo::fit_to_bezpath(&cubic_offset, CUBIC_TO_BEZPATH_ACCURACY); + + if fitted.segments().count() > MAX_FITTED_SEGMENTS { + None + } else if fitted.get_seg(1).is_some() { + Some(fitted) + } else { + None + } + }) + .collect::>(); // Clip or join consecutive Subpaths for i in 0..bezpaths.len() - 1 { From 3ff1c524793ff9ff2c7998e4fd2e342cb1181d3a Mon Sep 17 00:00:00 2001 From: Ashish Mohapatra Date: Sat, 25 Oct 2025 10:12:30 +0530 Subject: [PATCH 2/4] Reverted accuracy to 1e-3 --- node-graph/gcore/src/vector/algorithms/offset_subpath.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs b/node-graph/gcore/src/vector/algorithms/offset_subpath.rs index ca8ba15b45..f2a6079765 100644 --- a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs +++ b/node-graph/gcore/src/vector/algorithms/offset_subpath.rs @@ -5,7 +5,7 @@ use kurbo::{BezPath, Join, ParamCurve, PathEl, PathSeg}; /// Value to control smoothness and mathematical accuracy to offset a cubic Bezier. const CUBIC_REGULARIZATION_ACCURACY: f64 = 0.5; /// Accuracy of fitting offset curve to Bezier paths. -const CUBIC_TO_BEZPATH_ACCURACY: f64 = 1e-2; +const CUBIC_TO_BEZPATH_ACCURACY: f64 = 1e-3; /// Constant used to determine if `f64`s are equivalent. pub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-7; const MAX_FITTED_SEGMENTS: usize = 10000; From 0fdbcef49125865af0edbcb76e5d3872dfc0a38a Mon Sep 17 00:00:00 2001 From: Ashish Mohapatra Date: Mon, 27 Oct 2025 15:56:41 +0530 Subject: [PATCH 3/4] Refactor offset path segment check --- node-graph/gcore/src/vector/algorithms/offset_subpath.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs b/node-graph/gcore/src/vector/algorithms/offset_subpath.rs index f2a6079765..2831956ded 100644 --- a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs +++ b/node-graph/gcore/src/vector/algorithms/offset_subpath.rs @@ -37,10 +37,8 @@ pub fn offset_bezpath(bezpath: &BezPath, distance: f64, join: Join, miter_limit: if fitted.segments().count() > MAX_FITTED_SEGMENTS { None - } else if fitted.get_seg(1).is_some() { - Some(fitted) } else { - None + fitted.get_seg(1).is_some().then_some(fitted) } }) .collect::>(); From 24cb5771dc0f5ad470c7d83c304465a50a4af7d8 Mon Sep 17 00:00:00 2001 From: Ashish Mohapatra Date: Mon, 27 Oct 2025 16:43:57 +0530 Subject: [PATCH 4/4] Add squared distance for degenerate cubic bezier curves --- node-graph/gcore/src/vector/algorithms/offset_subpath.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs b/node-graph/gcore/src/vector/algorithms/offset_subpath.rs index 2831956ded..e4d43e7516 100644 --- a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs +++ b/node-graph/gcore/src/vector/algorithms/offset_subpath.rs @@ -8,6 +8,8 @@ const CUBIC_REGULARIZATION_ACCURACY: f64 = 0.5; const CUBIC_TO_BEZPATH_ACCURACY: f64 = 1e-3; /// Constant used to determine if `f64`s are equivalent. pub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-7; +/// Squared version to avoid sqrt in distance checks. +const MAX_ABSOLUTE_DIFFERENCE_SQUARED: f64 = MAX_ABSOLUTE_DIFFERENCE * MAX_ABSOLUTE_DIFFERENCE; const MAX_FITTED_SEGMENTS: usize = 10000; /// Reduces the segments of the bezpath into simple subcurves, then offset each subcurve a set `distance` away. @@ -23,9 +25,12 @@ pub fn offset_bezpath(bezpath: &BezPath, distance: f64, join: Join, miter_limit: .segments() .map(|bezier| bezier.to_cubic()) .filter_map(|cubic_bez| { + // Skip degenerate curves where all control points are at the same location. + // Offsetting a point is undefined and causes infinite recursion in fit_to_bezpath. let start = cubic_bez.p0; - let is_degenerate = - (cubic_bez.p1 - start).hypot() < MAX_ABSOLUTE_DIFFERENCE && (cubic_bez.p2 - start).hypot() < MAX_ABSOLUTE_DIFFERENCE && (cubic_bez.p3 - start).hypot() < MAX_ABSOLUTE_DIFFERENCE; + let is_degenerate = start.distance_squared(cubic_bez.p1) < MAX_ABSOLUTE_DIFFERENCE_SQUARED + && start.distance_squared(cubic_bez.p2) < MAX_ABSOLUTE_DIFFERENCE_SQUARED + && start.distance_squared(cubic_bez.p3) < MAX_ABSOLUTE_DIFFERENCE_SQUARED; if is_degenerate { return None;