diff --git a/libraries/bezier-rs/src/bezier/core.rs b/libraries/bezier-rs/src/bezier/core.rs
index 7f3886c2..29e67060 100644
--- a/libraries/bezier-rs/src/bezier/core.rs
+++ b/libraries/bezier-rs/src/bezier/core.rs
@@ -14,7 +14,7 @@ impl Bezier {
}
/// Create a quadratic bezier using the provided DVec2s as the start, handle, and end points.
- ///
+ ///
pub fn from_linear_dvec2(p1: DVec2, p2: DVec2) -> Self {
Bezier {
start: p1,
@@ -69,7 +69,7 @@ impl Bezier {
/// - `t` - A representation of how far along the curve the provided point should occur at. The default value is 0.5.
/// Note that when `t = 0` or `t = 1`, the expectation is that the `point_on_curve` should be equal to `start` and `end` respectively.
/// In these cases, if the provided values are not equal, this function will use the `point_on_curve` as the `start`/`end` instead.
- ///
+ ///
pub fn quadratic_through_points(start: DVec2, point_on_curve: DVec2, end: DVec2, t: Option) -> Self {
let t = t.unwrap_or(DEFAULT_T_VALUE);
if t == 0. {
diff --git a/libraries/bezier-rs/src/bezier/lookup.rs b/libraries/bezier-rs/src/bezier/lookup.rs
index 379abcb3..3756cbb7 100644
--- a/libraries/bezier-rs/src/bezier/lookup.rs
+++ b/libraries/bezier-rs/src/bezier/lookup.rs
@@ -65,7 +65,7 @@ impl Bezier {
/// Calculate the coordinates of the point `t` along the curve.
/// Expects `t` to be within the inclusive range `[0, 1]`.
- ///
+ ///
pub fn evaluate(&self, t: TValue) -> DVec2 {
let t = self.t_value_to_parametric(t);
self.unrestricted_parametric_evaluate(t)
@@ -73,7 +73,7 @@ impl Bezier {
/// Return a selection of equidistant points on the bezier curve.
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
- ///
+ ///
pub fn compute_lookup_table(&self, steps: Option, tvalue_type: Option) -> Vec {
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
let tvalue_type = tvalue_type.unwrap_or(TValueType::Parametric);
@@ -91,7 +91,7 @@ impl Bezier {
/// Return an approximation of the length of the bezier curve.
/// - `num_subdivisions` - Number of subdivisions used to approximate the curve. The default value is 1000.
- ///
+ ///
pub fn length(&self, num_subdivisions: Option) -> f64 {
match self.handles {
BezierHandles::Linear => self.start.distance(self.end),
@@ -118,7 +118,7 @@ impl Bezier {
/// Returns the parametric `t`-value that corresponds to the closest point on the curve to the provided point.
/// Uses a searching algorithm akin to binary search that can be customized using the optional [ProjectionOptions] struct.
- ///
+ ///
pub fn project(&self, point: DVec2, options: Option) -> f64 {
let options = options.unwrap_or_default();
let ProjectionOptions {
diff --git a/libraries/bezier-rs/src/bezier/solvers.rs b/libraries/bezier-rs/src/bezier/solvers.rs
index 29e711b5..077f16cd 100644
--- a/libraries/bezier-rs/src/bezier/solvers.rs
+++ b/libraries/bezier-rs/src/bezier/solvers.rs
@@ -9,7 +9,7 @@ impl Bezier {
/// Returns a list of lists of points representing the De Casteljau points for all iterations at the point `t` along the curve using De Casteljau's algorithm.
/// The `i`th element of the list represents the set of points in the `i`th iteration.
/// More information on the algorithm can be found in the [De Casteljau section](https://pomax.github.io/bezierinfo/#decasteljau) in Pomax's primer.
- ///
+ ///
pub fn de_casteljau_points(&self, t: TValue) -> Vec> {
let t = self.t_value_to_parametric(t);
let bezier_points = match self.handles {
@@ -34,7 +34,7 @@ impl Bezier {
/// Returns a [Bezier] representing the derivative of the original curve.
/// - This function returns `None` for a linear segment.
- ///
+ ///
pub fn derivative(&self) -> Option {
match self.handles {
BezierHandles::Linear => None,
@@ -61,7 +61,7 @@ impl Bezier {
}
/// Returns a normalized unit vector representing the tangent at the point `t` along the curve.
- ///
+ ///
pub fn tangent(&self, t: TValue) -> DVec2 {
let t = self.t_value_to_parametric(t);
let tangent = self.non_normalized_tangent(t);
@@ -73,14 +73,14 @@ impl Bezier {
}
/// Returns a normalized unit vector representing the direction of the normal at the point `t` along the curve.
- ///
+ ///
pub fn normal(&self, t: TValue) -> DVec2 {
self.tangent(t).perp()
}
/// Returns the curvature, a scalar value for the derivative at the point `t` along the curve.
/// Curvature is 1 over the radius of a circle with an equivalent derivative.
- ///
+ ///
pub fn curvature(&self, t: TValue) -> f64 {
let t = self.t_value_to_parametric(t);
let (d, dd) = match &self.derivative() {
@@ -127,7 +127,7 @@ impl Bezier {
/// Returns two lists of `t`-values representing the local extrema of the `x` and `y` parametric curves respectively.
/// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`.
- ///
+ ///
pub fn local_extrema(&self) -> [Vec; 2] {
self.unrestricted_local_extrema()
.into_iter()
@@ -138,7 +138,7 @@ impl Bezier {
}
/// Return the min and max corners that represent the bounding box of the curve.
- ///
+ ///
pub fn bounding_box(&self) -> [DVec2; 2] {
// Start by taking min/max of endpoints.
let mut endpoints_min = self.start.min(self.end);
@@ -199,7 +199,7 @@ impl Bezier {
/// Returns list of parametric `t`-values representing the inflection points of the curve.
/// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`.
- ///
+ ///
pub fn inflections(&self) -> Vec {
self.unrestricted_inflections().into_iter().filter(|&t| t > 0. && t < 1.).collect::>()
}
@@ -256,7 +256,7 @@ impl Bezier {
/// If the provided curve is linear, then zero intersection points will be returned along colinear segments.
/// - `error` - For intersections where the provided bezier is non-linear, `error` defines the threshold for bounding boxes to be considered an intersection point.
/// - `minimum_separation` - The minimum difference between adjacent `t` values in sorted order
- ///
+ ///
pub fn intersections(&self, other: &Bezier, error: Option, minimum_separation: Option) -> Vec {
// TODO: Consider using the `intersections_between_vectors_of_curves` helper function here
// Otherwise, use bounding box to determine intersections
@@ -355,7 +355,7 @@ impl Bezier {
// TODO: Use an `impl Iterator` return type instead of a `Vec`
/// Returns a list of parametric `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
- ///
+ ///
pub fn self_intersections(&self, error: Option) -> Vec<[f64; 2]> {
if self.handles == BezierHandles::Linear || matches!(self.handles, BezierHandles::Quadratic { .. }) {
return vec![];
@@ -387,7 +387,7 @@ impl Bezier {
}
/// Returns a list of parametric `t` values that correspond to the intersection points between the curve and a rectangle defined by opposite corners.
- ///
+ ///
pub fn rectangle_intersections(&self, corner1: DVec2, corner2: DVec2) -> Vec {
[
Bezier::from_linear_coordinates(corner1.x, corner1.y, corner2.x, corner1.y),
@@ -402,7 +402,7 @@ impl Bezier {
/// Returns a cubic bezier which joins this with the provided bezier curve.
/// The resulting path formed by the Bezier curves is continuous up to the first derivative.
- ///
+ ///
pub fn join(&self, other: &Bezier) -> Bezier {
let handle1 = self.non_normalized_tangent(1.) / 3. + self.end;
let handle2 = other.start - other.non_normalized_tangent(0.) / 3.;
diff --git a/libraries/bezier-rs/src/bezier/transform.rs b/libraries/bezier-rs/src/bezier/transform.rs
index 047b1bfa..2e7b7c3d 100644
--- a/libraries/bezier-rs/src/bezier/transform.rs
+++ b/libraries/bezier-rs/src/bezier/transform.rs
@@ -37,7 +37,7 @@ impl Bezier {
}
/// Returns the pair of Bezier curves that result from splitting the original curve at the point `t` along the curve.
- ///
+ ///
pub fn split(&self, t: TValue) -> [Bezier; 2] {
let t = self.t_value_to_parametric(t);
let split_point = self.evaluate(TValue::Parametric(t));
@@ -83,7 +83,7 @@ impl Bezier {
/// Returns the Bezier curve representing the sub-curve between the two provided points.
/// It will start at the point corresponding to the smaller of `t1` and `t2`, and end at the point corresponding to the larger of `t1` and `t2`.
- ///
+ ///
pub fn trim(&self, t1: TValue, t2: TValue) -> Bezier {
let (mut t1, mut t2) = (self.t_value_to_parametric(t1), self.t_value_to_parametric(t2));
// If t1 is equal to t2, return a bezier comprised entirely of the same point
@@ -122,7 +122,7 @@ impl Bezier {
}
/// Returns a Bezier curve that results from rotating the curve around the origin by the given angle (in radians).
- ///
+ ///
pub fn rotate(&self, angle: f64) -> Bezier {
let rotation_matrix = DMat2::from_angle(angle);
self.apply_transformation(|point| rotation_matrix.mul_vec2(point))
@@ -251,7 +251,7 @@ impl Bezier {
/// The function takes the following parameter:
/// - `step_size` - Dictates the granularity at which the function searches for reducible subcurves. The default value is `0.01`.
/// A small granularity may increase the chance the function does not introduce gaps, but will increase computation time.
- ///
+ ///
pub fn reduce(&self, step_size: Option) -> Vec {
self.reduced_curves_and_t_values(step_size).0
}
@@ -355,7 +355,7 @@ impl Bezier {
/// Offset takes the following parameter:
/// - `distance` - The offset's distance from the curve. Positive values will offset the curve in the same direction as the endpoint normals,
/// while negative values will offset in the opposite direction.
- ///
+ ///
pub fn offset(&self, distance: f64) -> Subpath {
if self.is_point() {
return Subpath::from_bezier(self);
@@ -422,7 +422,7 @@ impl Bezier {
/// The 'caps', the linear segments at opposite ends of the outline, intersect the original curve at the midpoint of the cap.
/// Outline takes the following parameter:
/// - `distance` - The outline's distance from the curve.
- ///
+ ///
pub fn outline(&self, distance: f64, cap: Cap) -> Subpath {
let (pos_offset, neg_offset) = if self.is_point() {
(
@@ -442,13 +442,13 @@ impl Bezier {
/// Version of the `outline` function which draws the outline at the specified distances away from the curve.
/// The outline begins `start_distance` away, and gradually move to being `end_distance` away.
- ///
+ ///
pub fn graduated_outline(&self, start_distance: f64, end_distance: f64, cap: Cap) -> Subpath {
self.skewed_outline(start_distance, end_distance, end_distance, start_distance, cap)
}
/// Version of the `graduated_outline` function that allows for the 4 corners of the outline to be different distances away from the curve.
- ///
+ ///
pub fn skewed_outline(&self, distance1: f64, distance2: f64, distance3: f64, distance4: f64, cap: Cap) -> Subpath {
let (pos_offset, neg_offset) = if self.is_point() {
(
@@ -468,7 +468,7 @@ impl Bezier {
/// Approximate a bezier curve with circular arcs.
/// The algorithm can be customized using the [ArcsOptions] structure.
- ///
+ ///
pub fn arcs(&self, arcs_options: ArcsOptions) -> Vec {
let ArcsOptions {
strategy: maximize_arcs,
diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs
index 1b7b67f7..bccd0787 100644
--- a/libraries/bezier-rs/src/subpath/core.rs
+++ b/libraries/bezier-rs/src/subpath/core.rs
@@ -241,7 +241,7 @@ impl Subpath {
}
/// Construct a cubic spline from a list of points.
- /// Based on https://mathworld.wolfram.com/CubicSpline.html
+ /// Based on .
pub fn new_cubic_spline(points: Vec) -> Self {
// Number of points = number of points to find handles for
let len_points = points.len();
diff --git a/libraries/bezier-rs/src/subpath/lookup.rs b/libraries/bezier-rs/src/subpath/lookup.rs
index f1e3a051..174a8f98 100644
--- a/libraries/bezier-rs/src/subpath/lookup.rs
+++ b/libraries/bezier-rs/src/subpath/lookup.rs
@@ -8,7 +8,7 @@ use glam::DVec2;
impl Subpath {
/// Return a selection of equidistant points on the bezier curve.
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
- ///
+ ///
pub fn compute_lookup_table(&self, steps: Option, tvalue_type: Option) -> Vec {
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
let tvalue_type = tvalue_type.unwrap_or(TValueType::Parametric);
@@ -26,7 +26,7 @@ impl Subpath {
/// Return the sum of the approximation of the length of each `Bezier` curve along the `Subpath`.
/// - `num_subdivisions` - Number of subdivisions used to approximate the curve. The default value is `1000`.
- ///
+ ///
pub fn length(&self, num_subdivisions: Option) -> f64 {
self.iter().fold(0., |accumulator, bezier| accumulator + bezier.length(num_subdivisions))
}
@@ -97,7 +97,7 @@ impl Subpath {
/// Returns the segment index and `t` value that corresponds to the closest point on the curve to the provided point.
/// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure.
- ///
+ ///
pub fn project(&self, point: DVec2, options: Option) -> Option<(usize, f64)> {
if self.is_empty() {
return None;
diff --git a/libraries/bezier-rs/src/subpath/manipulators.rs b/libraries/bezier-rs/src/subpath/manipulators.rs
index 286f6850..011fad49 100644
--- a/libraries/bezier-rs/src/subpath/manipulators.rs
+++ b/libraries/bezier-rs/src/subpath/manipulators.rs
@@ -14,17 +14,17 @@ impl Subpath {
self.closed = new_closed;
}
- /// Access a [ManipulatorGroup] from a [ManipulatorGroupId].
+ /// Access a [ManipulatorGroup] from a ManipulatorGroupId.
pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup> {
self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id)
}
- /// Access a mutable [ManipulatorGroup] from a [ManipulatorGroupId].
+ /// Access a mutable [ManipulatorGroup] from a ManipulatorGroupId.
pub fn manipulator_mut_from_id(&mut self, id: ManipulatorGroupId) -> Option<&mut ManipulatorGroup> {
self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id)
}
- /// Access the index of a [ManipulatorGroup] from a [ManipulatorGroupId].
+ /// Access the index of a [ManipulatorGroup] from a ManipulatorGroupId.
pub fn manipulator_index_from_id(&self, id: ManipulatorGroupId) -> Option {
self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id)
}
diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs
index 72730ffe..5327aa11 100644
--- a/libraries/bezier-rs/src/subpath/solvers.rs
+++ b/libraries/bezier-rs/src/subpath/solvers.rs
@@ -9,7 +9,7 @@ use std::f64::consts::PI;
impl Subpath {
/// Calculate the point on the subpath based on the parametric `t`-value provided.
/// Expects `t` to be within the inclusive range `[0, 1]`.
- ///
+ ///
pub fn evaluate(&self, t: SubpathTValue) -> DVec2 {
let (segment_index, t) = self.t_value_to_parametric(t);
self.get_segment(segment_index).unwrap().evaluate(TValue::Parametric(t))
@@ -23,7 +23,7 @@ impl Subpath {
/// - `error`: an optional f64 value to provide an error bound
/// - `minimum_separation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two.
- ///
+ ///
pub fn intersections(&self, other: &Bezier, error: Option, minimum_separation: Option) -> Vec<(usize, f64)> {
self.iter()
.enumerate()
@@ -35,7 +35,7 @@ impl Subpath {
/// This function expects the following:
/// - other: a [Bezier] curve to check intersections against
/// - error: an optional f64 value to provide an error bound
- ///
+ ///
pub fn subpath_intersections(&self, other: &Subpath, error: Option, minimum_separation: Option) -> Vec<(usize, f64)> {
let mut intersection_t_values: Vec<(usize, f64)> = other.iter().flat_map(|bezier| self.intersections(&bezier, error, minimum_separation)).collect();
intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
@@ -48,7 +48,7 @@ impl Subpath {
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two
///
/// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out.
- ///
+ ///
pub fn self_intersections(&self, error: Option, minimum_separation: Option) -> Vec<(usize, f64)> {
let mut intersections_vec = Vec::new();
let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
@@ -69,14 +69,14 @@ impl Subpath {
}
/// Returns a normalized unit vector representing the tangent on the subpath based on the parametric `t`-value provided.
- ///
+ ///
pub fn tangent(&self, t: SubpathTValue) -> DVec2 {
let (segment_index, t) = self.t_value_to_parametric(t);
self.get_segment(segment_index).unwrap().tangent(TValue::Parametric(t))
}
/// Returns a normalized unit vector representing the direction of the normal on the subpath based on the parametric `t`-value provided.
- ///
+ ///
pub fn normal(&self, t: SubpathTValue) -> DVec2 {
let (segment_index, t) = self.t_value_to_parametric(t);
self.get_segment(segment_index).unwrap().normal(TValue::Parametric(t))
@@ -84,7 +84,7 @@ impl Subpath {
/// Returns two lists of `t`-values representing the local extrema of the `x` and `y` parametric subpaths respectively.
/// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`.
- ///
+ ///
pub fn local_extrema(&self) -> [Vec; 2] {
let number_of_curves = self.len_segments() as f64;
@@ -99,7 +99,7 @@ impl Subpath {
}
/// Return the min and max corners that represent the bounding box of the subpath.
- ///
+ ///
pub fn bounding_box(&self) -> Option<[DVec2; 2]> {
self.iter().map(|bezier| bezier.bounding_box()).reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
}
@@ -138,7 +138,15 @@ impl Subpath {
}
/// Returns the manipulator point that is needed for a miter join if it is possible.
- pub(crate) fn miter_line_join(&self, other: &Subpath) -> Option> {
+ /// - `miter_limit`: Defines a limit for the ratio between the miter length and the stroke width.
+ /// Alternatively, this can be interpreted as limiting the angle that the miter can form.
+ /// When the limit is exceeded, no manipulator group will be returned.
+ /// This value should be at least 1. If not, the default of 4 will be used.
+ pub(crate) fn miter_line_join(&self, other: &Subpath, miter_limit: Option) -> Option> {
+ let miter_limit = match miter_limit {
+ Some(miter_limit) if miter_limit >= 1. => miter_limit,
+ _ => 4.,
+ };
let in_segment = self.get_segment(self.len_segments() - 1).unwrap();
let out_segment = other.get_segment(0).unwrap();
let in_tangent = in_segment.tangent(TValue::Parametric(1.));
@@ -151,9 +159,13 @@ impl Subpath {
if !normalized_in_tangent.abs_diff_eq(normalized_out_tangent, MAX_ABSOLUTE_DIFFERENCE) && !normalized_in_tangent.abs_diff_eq(-normalized_out_tangent, MAX_ABSOLUTE_DIFFERENCE) {
let intersection = line_intersection(in_segment.end(), in_tangent, out_segment.start(), out_tangent);
+ let start_to_intersection = intersection - in_segment.end();
+ let intersection_to_end = out_segment.start() - intersection;
+
// Draw the miter join if the intersection occurs in the correct direction with respect to the path
- if (intersection - in_segment.end()).normalize().abs_diff_eq(in_tangent, MAX_ABSOLUTE_DIFFERENCE)
- && (out_segment.start() - intersection).normalize().abs_diff_eq(out_tangent, MAX_ABSOLUTE_DIFFERENCE)
+ if start_to_intersection.normalize().abs_diff_eq(in_tangent, MAX_ABSOLUTE_DIFFERENCE)
+ && intersection_to_end.normalize().abs_diff_eq(out_tangent, MAX_ABSOLUTE_DIFFERENCE)
+ && miter_limit >= 1. / (start_to_intersection.angle_between(-intersection_to_end).abs() / 2.).sin()
{
return Some(ManipulatorGroup {
anchor: intersection,
@@ -225,7 +237,7 @@ impl Subpath {
/// Returns the curvature, a scalar value for the derivative at the point `t` along the subpath.
/// Curvature is 1 over the radius of a circle with an equivalent derivative.
- ///
+ ///
pub fn curvature(&self, t: SubpathTValue) -> f64 {
let (segment_index, t) = self.t_value_to_parametric(t);
self.get_segment(segment_index).unwrap().curvature(TValue::Parametric(t))
diff --git a/libraries/bezier-rs/src/subpath/transform.rs b/libraries/bezier-rs/src/subpath/transform.rs
index d47507d1..2e802272 100644
--- a/libraries/bezier-rs/src/subpath/transform.rs
+++ b/libraries/bezier-rs/src/subpath/transform.rs
@@ -22,7 +22,7 @@ impl Subpath {
/// Returns either one or two Subpaths that result from splitting the original Subpath at the point corresponding to `t`.
/// If the original Subpath was closed, a single open Subpath will be returned.
/// If the original Subpath was open, two open Subpaths will be returned.
- ///
+ ///
pub fn split(&self, t: SubpathTValue) -> (Subpath, Option>) {
let (segment_index, t) = self.t_value_to_parametric(t);
let curve = self.get_segment(segment_index).unwrap();
@@ -126,7 +126,7 @@ impl Subpath {
/// The resulting Subpath will wind from the given `t1` to `t2`.
/// That means, if the value of `t1` > `t2`, it will cross the break between endpoints from `t1` to `t = 1 = 0` to `t2`.
/// If a path winding in the reverse direction is desired, call `trim` on the `Subpath` returned from `Subpath::reverse`.
- ///
+ ///
pub fn trim(&self, t1: SubpathTValue, t2: SubpathTValue) -> Subpath {
// Return a clone of the Subpath if it is not long enough to be a valid Bezier
if self.manipulator_groups.is_empty() {
@@ -334,7 +334,7 @@ impl Subpath {
}
/// Returns a subpath that results from rotating this subpath around the origin by the given angle (in radians).
- ///
+ ///
pub fn rotate(&self, angle: f64) -> Subpath {
let mut rotated_subpath = self.clone();
@@ -411,8 +411,8 @@ impl Subpath {
drop_common_point[j] = false;
match join {
Join::Bevel => {}
- Join::Miter => {
- let miter_manipulator_group = subpaths[i].miter_line_join(&subpaths[j]);
+ Join::Miter(miter_limit) => {
+ let miter_manipulator_group = subpaths[i].miter_line_join(&subpaths[j], miter_limit);
if let Some(miter_manipulator_group) = miter_manipulator_group {
subpaths[i].manipulator_groups.push(miter_manipulator_group);
}
@@ -448,9 +448,9 @@ impl Subpath {
drop_common_point[0] = false;
match join {
Join::Bevel => {}
- Join::Miter => {
+ Join::Miter(miter_limit) => {
let last_subpath_index = subpaths.len() - 1;
- let miter_manipulator_group = subpaths[last_subpath_index].miter_line_join(&subpaths[0]);
+ let miter_manipulator_group = subpaths[last_subpath_index].miter_line_join(&subpaths[0], miter_limit);
if let Some(miter_manipulator_group) = miter_manipulator_group {
subpaths[last_subpath_index].manipulator_groups.push(miter_manipulator_group);
}
@@ -526,7 +526,7 @@ impl Subpath {
/// an approximate outline around the subpath at a specified distance from the curve. Outline takes the following parameters:
/// - `distance` - The outline's distance from the curve.
/// - `join` - The join type used to cap the endpoints of open bezier curves, and join successive subpath segments.
- ///
+ ///
pub fn outline(&self, distance: f64, join: Join, cap: Cap) -> (Subpath, Option>) {
let is_point = self.is_point();
let (pos_offset, neg_offset) = if is_point {
diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs
index 62b1c5dc..cc050a81 100644
--- a/libraries/bezier-rs/src/utils.rs
+++ b/libraries/bezier-rs/src/utils.rs
@@ -36,17 +36,25 @@ pub enum SubpathTValue {
}
#[derive(Copy, Clone)]
-/// Enum to represent the join type between subpaths.
-/// As defined in SVG: https://www.w3.org/TR/SVG2/painting.html#LineJoin.
+/// Represents the shape of the join between two segments of a path which meet at an angle.
+/// Bevel provides a flat connection, Miter provides a sharp connection, and Round provides a rounded connection.
+/// As defined in SVG: .
pub enum Join {
+ /// The join is a straight line between the end points of the offset path sides from the two connecting segments.
Bevel,
- Miter,
+ /// Optional f64 is the miter limit, which defaults to 4 if `None` or a value less than 1 is provided.
+ /// The miter limit is used to prevent highly sharp angles from resulting in excessively long miter joins.
+ /// If the miter limit is exceeded, the join will be converted to a bevel join.
+ /// The value is the ratio of the miter length to the stroke width.
+ /// When that ratio is greater than the miter limit, a bevel join is used instead.
+ Miter(Option),
+ /// The join is a circular arc between the end points of the offset path sides from the two connecting segments.
Round,
}
#[derive(Copy, Clone)]
/// Enum to represent the cap type at the ends of an outline
-/// As defined in SVG: https://www.w3.org/TR/SVG2/painting.html#LineCaps.
+/// As defined in SVG: .
pub enum Cap {
Butt,
Round,
diff --git a/website/other/bezier-rs-demos/src/features/subpath-features.ts b/website/other/bezier-rs-demos/src/features/subpath-features.ts
index b64f05a4..fa49c8ad 100644
--- a/website/other/bezier-rs-demos/src/features/subpath-features.ts
+++ b/website/other/bezier-rs-demos/src/features/subpath-features.ts
@@ -126,7 +126,7 @@ const subpathFeatures = {
},
offset: {
name: "Offset",
- callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.offset(options.distance, options.join),
+ callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.offset(options.distance, options.join, options.miter_limit),
inputOptions: [
{
variable: "distance",
@@ -136,11 +136,18 @@ const subpathFeatures = {
default: 10,
},
joinOptions,
+ {
+ variable: "join: Miter - limit",
+ min: 1,
+ max: 10,
+ step: 0.25,
+ default: 4,
+ },
],
},
outline: {
name: "Outline",
- callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.outline(options.distance, options.join, options.cap),
+ callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.outline(options.distance, options.join, options.cap, options.miter_limit),
inputOptions: [
{
variable: "distance",
@@ -150,6 +157,13 @@ const subpathFeatures = {
default: 10,
},
joinOptions,
+ {
+ variable: "join: Miter - limit",
+ min: 1,
+ max: 10,
+ step: 0.25,
+ default: 4,
+ },
{ ...capOptions, isDisabledForClosed: true },
],
},
diff --git a/website/other/bezier-rs-demos/src/utils/options.ts b/website/other/bezier-rs-demos/src/utils/options.ts
index 0a3d03e2..26d592b4 100644
--- a/website/other/bezier-rs-demos/src/utils/options.ts
+++ b/website/other/bezier-rs-demos/src/utils/options.ts
@@ -1,4 +1,4 @@
-import { BEZIER_T_VALUE_VARIANTS, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types";
+import { BEZIER_T_VALUE_VARIANTS, CAP_VARIANTS, JOIN_VARIANTS, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types";
export const tSliderOptions = {
min: 0,
@@ -50,12 +50,12 @@ export const joinOptions = {
variable: "join",
default: 0,
inputType: "dropdown",
- options: ["Bevel", "Miter", "Round"],
+ options: JOIN_VARIANTS,
};
export const capOptions = {
variable: "cap",
default: 0,
inputType: "dropdown",
- options: ["Butt", "Round", "Square"],
+ options: CAP_VARIANTS,
};
diff --git a/website/other/bezier-rs-demos/src/utils/types.ts b/website/other/bezier-rs-demos/src/utils/types.ts
index b1c5fb7f..de40fa9a 100644
--- a/website/other/bezier-rs-demos/src/utils/types.ts
+++ b/website/other/bezier-rs-demos/src/utils/types.ts
@@ -95,3 +95,6 @@ export interface DemoPane extends HTMLElement {
export const BEZIER_T_VALUE_VARIANTS = ["Parametric", "Euclidean"] as const;
export const SUBPATH_T_VALUE_VARIANTS = ["GlobalParametric", "GlobalEuclidean"] as const;
+
+export const CAP_VARIANTS = ["Butt", "Round", "Square"] as const;
+export const JOIN_VARIANTS = ["Bevel", "Miter", "Round"] as const;
diff --git a/website/other/bezier-rs-demos/wasm/src/subpath.rs b/website/other/bezier-rs-demos/wasm/src/subpath.rs
index 3706f8fd..cca7e2c2 100644
--- a/website/other/bezier-rs-demos/wasm/src/subpath.rs
+++ b/website/other/bezier-rs-demos/wasm/src/subpath.rs
@@ -441,8 +441,8 @@ impl WasmSubpath {
wrap_svg_tag(format!("{}{}", self.to_default_svg(), trimmed_subpath_svg))
}
- pub fn offset(&self, distance: f64, join: i32) -> String {
- let join = parse_join(join);
+ pub fn offset(&self, distance: f64, join: i32, miter_limit: f64) -> String {
+ let join = parse_join(join, miter_limit);
let offset_subpath = self.0.offset(distance, join);
let mut offset_svg = String::new();
@@ -451,8 +451,8 @@ impl WasmSubpath {
wrap_svg_tag(format!("{}{offset_svg}", self.to_default_svg()))
}
- pub fn outline(&self, distance: f64, join: i32, cap: i32) -> String {
- let join = parse_join(join);
+ pub fn outline(&self, distance: f64, join: i32, cap: i32, miter_limit: f64) -> String {
+ let join = parse_join(join, miter_limit);
let cap = parse_cap(cap);
let (outline_piece1, outline_piece2) = self.0.outline(distance, join, cap);
diff --git a/website/other/bezier-rs-demos/wasm/src/utils.rs b/website/other/bezier-rs-demos/wasm/src/utils.rs
index 58dc1329..00f21daf 100644
--- a/website/other/bezier-rs-demos/wasm/src/utils.rs
+++ b/website/other/bezier-rs-demos/wasm/src/utils.rs
@@ -1,9 +1,9 @@
use bezier_rs::{Cap, Join};
-pub fn parse_join(join: i32) -> Join {
+pub fn parse_join(join: i32, miter_limit: f64) -> Join {
match join {
0 => Join::Bevel,
- 1 => Join::Miter,
+ 1 => Join::Miter(Some(miter_limit)),
2 => Join::Round,
_ => panic!("Unexpected Join value: '{}'", join),
}