Port all remaining Subpath producers to BezPath and delete the legacy subpath module (#4457)

* Port all remaining Subpath producers to BezPath and delete the legacy subpath module

* Reset contour state at each MoveTo so an open contour's segments don't leak into the next region

* Give the polygon and star constructors a true center and radius instead of compensated arguments
This commit is contained in:
Keavon Chambers
2026-08-18 15:28:58 -07:00
committed by GitHub
parent 8f1b2bed5f
commit e3b968f7e2
43 changed files with 961 additions and 1291 deletions

View File

@@ -4,7 +4,9 @@ use core_types::{CacheHash, Ctx};
use dyn_any::DynAny;
use glam::DVec2;
use graphic_types::Vector;
use vector_types::subpath;
use vector_types::vector::VectorExt;
use vector_types::vector::algorithms::shapes;
use vector_types::vector::misc::BezierHandles;
use vector_types::vector::misc::{ArcType, AsU64, BoxCorners, GridType};
use vector_types::vector::misc::{HandleId, SpiralType};
use vector_types::vector::{PointId, SegmentId, StrokeId};
@@ -19,7 +21,7 @@ fn circle(
radius: Item<f64>,
) -> Item<Vector> {
let radius = radius.element().abs();
Item::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
Item::new_from_element(Vector::from_bezpath(shapes::ellipse_bezpath(DVec2::splat(-radius), DVec2::splat(radius))))
}
/// Generates an arc shape forming a portion of a circle which may be open, closed, or a pie slice.
@@ -38,7 +40,7 @@ fn arc(
arc_type: Item<ArcType>,
) -> Item<Vector> {
let (radius, start_angle, sweep_angle, arc_type) = (*radius.element(), *start_angle.element(), *sweep_angle.element(), arc_type.into_element());
Item::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc(
Item::new_from_element(Vector::from_bezpath(shapes::arc_bezpath(
radius,
start_angle / 360. * std::f64::consts::TAU,
sweep_angle / 360. * std::f64::consts::TAU,
@@ -65,7 +67,7 @@ fn spiral(
*outer_radius.element(),
*angular_resolution.element(),
);
Item::new_from_element(Vector::from_subpath(subpath::Subpath::new_spiral(
Item::new_from_element(Vector::from_bezpath(shapes::spiral_bezpath(
inner_radius,
outer_radius,
turns,
@@ -91,7 +93,7 @@ fn ellipse(
let corner1 = -radius;
let corner2 = radius;
let mut ellipse = Vector::from_subpath(subpath::Subpath::new_ellipse(corner1, corner2));
let mut ellipse = Vector::from_bezpath(shapes::ellipse_bezpath(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
@@ -139,7 +141,7 @@ fn rectangle(
radii
};
Item::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., radii)))
Item::new_from_element(Vector::from_bezpath(shapes::rounded_rectangle_bezpath(size / -2., size / 2., radii)))
}
/// Builds a set of four corner values, such as a rectangle's corner radii, from a list of one, two, three, or four values.
@@ -167,8 +169,7 @@ fn regular_polygon<T: AsU64>(
radius: Item<f64>,
) -> Item<Vector> {
let points = sides.element().as_u64();
let radius: f64 = *radius.element() * 2.;
Item::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
Item::new_from_element(Vector::from_bezpath(shapes::regular_polygon_bezpath(DVec2::ZERO, points, *radius.element())))
}
/// Generates an n-pointed star shape with inner and outer points at chosen radii from the center.
@@ -188,10 +189,7 @@ fn star<T: AsU64>(
radius_2: Item<f64>,
) -> Item<Vector> {
let points = sides.element().as_u64();
let diameter: f64 = *radius_1.element() * 2.;
let inner_diameter = *radius_2.element() * 2.;
Item::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
Item::new_from_element(Vector::from_bezpath(shapes::star_polygon_bezpath(DVec2::ZERO, points, *radius_1.element(), *radius_2.element())))
}
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
@@ -249,11 +247,7 @@ fn qr_code(
for x in 0..dimension {
if qr_code.get_module(x as i32, y as i32) {
let corner1 = DVec2::new(x as f64, y as f64);
let corner2 = corner1 + DVec2::splat(1.);
vector.append_subpath(
subpath::Subpath::from_anchors([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true),
false,
);
vector.append_bezpath(shapes::rectangle_bezpath(corner1, corner1 + DVec2::splat(1.)));
}
}
}
@@ -281,12 +275,12 @@ fn arrow(
#[default(20)] head_length: Item<PixelLength>,
) -> Item<Vector> {
let (arrow_to, shaft_width, head_width, head_length) = (*arrow_to.element(), *shaft_width.element(), *head_width.element(), *head_length.element());
Item::new_from_element(Vector::from_subpath(subpath::Subpath::new_arrow(DVec2::ZERO, arrow_to, shaft_width, head_width, head_length)))
Item::new_from_element(Vector::from_bezpath(shapes::arrow_bezpath(DVec2::ZERO, arrow_to, shaft_width, head_width, head_length)))
}
#[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default(100., 100.)] line_to: Item<PixelSize>) -> Item<Vector> {
Item::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(DVec2::ZERO, *line_to.element())))
Item::new_from_element(Vector::from_bezpath(shapes::line_bezpath(DVec2::ZERO, *line_to.element())))
}
trait GridSpacing {
@@ -370,9 +364,7 @@ fn grid<T: GridSpacing>(
// Helper function to connect points with line segments.
let mut push_segment = |to_index: Option<usize>| {
if let Some(other_index) = to_index {
vector
.segment_domain
.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);
vector.segment_domain.push(segment_id.next_id(), other_index, current_index, BezierHandles::Linear, StrokeId::ZERO);
}
};

View File

@@ -1,7 +1,8 @@
use glam::DVec2;
use graphic_types::Vector;
use std::collections::VecDeque;
use vector_types::subpath;
use vector_types::vector::VectorExt;
use vector_types::vector::algorithms::shapes;
pub fn merge_qr_squares(qr_code: &qrcodegen::QrCode) -> Vector {
let mut vector = Vector::default();
@@ -106,7 +107,7 @@ pub fn merge_qr_squares(qr_code: &qrcodegen::QrCode) -> Vector {
}
if !simplified.is_empty() {
vector.append_subpath(subpath::Subpath::from_anchors(simplified, true), false);
vector.append_bezpath(shapes::polyline_bezpath(simplified, true));
}
}
}

View File

@@ -23,14 +23,15 @@ use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use vector_types::GradientForm;
use vector_types::gradient::{build_transform_with_y_preservation, initial_gradient_transform_for_bounding_box};
use vector_types::subpath::{BezierHandles, ManipulatorGroup};
use vector_types::vector::algorithms::bezpath_algorithms::{self, TValue, eval_pathseg_euclidean, evaluate_bezpath, split_bezpath, tangent_on_bezpath};
use vector_types::vector::algorithms::bezpath_algorithms::{
self, TValue, bezpath_area_centroid_and_area, bezpath_length_centroid_and_length, eval_pathseg_euclidean, evaluate_bezpath, split_bezpath, tangent_on_bezpath,
};
use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
use vector_types::vector::algorithms::offset_subpath::offset_bezpath;
use vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
use vector_types::vector::misc::{
CentroidType, ExtrudeJoiningAlgorithm, HandleId, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, bezpath_from_manipulator_groups,
bezpath_to_manipulator_groups, handles_to_segment, is_linear, point_to_dvec2, segment_to_handles,
BezierHandles, CentroidType, ExtrudeJoiningAlgorithm, HandleId, InterpolationDistribution, ManipulatorGroup, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns,
bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, handles_to_segment, is_linear, point_to_dvec2, segment_to_handles,
};
use vector_types::vector::style::{DashPattern, Gradient, GradientSettings, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
@@ -692,11 +693,11 @@ fn merge_by_distance<V: MapVectorItems + Send + Sync + 'static>(
pub mod extrude_algorithms {
use glam::DVec2;
use kurbo::{ParamCurve, ParamCurveDeriv};
use vector_types::subpath::BezierHandles;
use vector_types::vector::StrokeId;
use vector_types::vector::misc::BezierHandles;
use vector_types::vector::misc::ExtrudeJoiningAlgorithm;
/// Convert [`kurbo::CubicBez`] to [`vector_types::subpath::BezierHandles`].
/// Convert [`kurbo::CubicBez`] to [`vector_types::vector::misc::BezierHandles`].
fn cubic_to_handles(cubic_bez: kurbo::CubicBez) -> BezierHandles {
BezierHandles::Cubic {
handle_start: DVec2::new(cubic_bez.p1.x, cubic_bez.p1.y),
@@ -1114,20 +1115,22 @@ async fn auto_tangents<V: MapVectorItems + 'n + Send>(
let mut result = Vector::default();
for mut subpath in source.stroke_bezier_paths() {
subpath.apply_transform(transform);
for (mut manipulators_list, is_closed) in source.stroke_manipulator_groups() {
for manipulator in &mut manipulators_list {
manipulator.anchor = transform.transform_point2(manipulator.anchor);
manipulator.in_handle = manipulator.in_handle.map(|handle| transform.transform_point2(handle));
manipulator.out_handle = manipulator.out_handle.map(|handle| transform.transform_point2(handle));
}
let manipulators_list = subpath.manipulator_groups();
if manipulators_list.len() < 2 {
// Not enough points for softening or handle removal
result.append_subpath(subpath, true);
result.append_manipulator_groups(&manipulators_list, is_closed, true);
continue;
}
let mut new_manipulators_list = Vec::with_capacity(manipulators_list.len());
// Track which manipulator indices were given auto-tangent (colinear) handles
let mut auto_tangented = vec![false; manipulators_list.len()];
let is_closed = subpath.closed();
for i in 0..manipulators_list.len() {
let current = &manipulators_list[i];
@@ -2514,7 +2517,7 @@ async fn morph(
/// Subdivides the last segment of a manipulator group list at its midpoint, adding one new manipulator.
/// For closed paths, the "last segment" is the closing segment from the last back to the first manipulator.
fn subdivide_last_manipulator_segment(manips: &mut Vec<ManipulatorGroup<PointId>>, closed: bool) {
fn subdivide_last_manipulator_segment(manips: &mut Vec<ManipulatorGroup>, closed: bool) {
let len = manips.len();
if len < 2 {
return;
@@ -2562,7 +2565,7 @@ async fn morph(
/// Pushes a subpath (list of manipulators) directly into a Vector's point, segment, and region domains,
/// bypassing the BezPath intermediate representation used by `append_bezpath`.
fn push_manipulators_to_vector(vector: &mut Vector, manips: &[ManipulatorGroup<PointId>], closed: bool, point_id: &mut PointId, segment_id: &mut SegmentId) {
fn push_manipulators_to_vector(vector: &mut Vector, manips: &[ManipulatorGroup], closed: bool, point_id: &mut PointId, segment_id: &mut SegmentId) {
let Some(first) = manips.first() else { return };
let first_point_index = vector.point_domain.ids().len();
@@ -3047,7 +3050,7 @@ async fn morph(
}
// Build interpolated manipulator groups
let mut interpolated: Vec<ManipulatorGroup<PointId>> = source_manips
let mut interpolated: Vec<ManipulatorGroup> = source_manips
.iter()
.zip(target_manips.iter())
.map(|(s, t)| ManipulatorGroup {
@@ -3552,14 +3555,14 @@ fn element_centroid(element: &Vector, transform: DAffine2, centroid_type: Centro
let mut centroid = DVec2::ZERO;
let mut sum = 0.;
for subpath in element.stroke_bezier_paths() {
for bezpath in element.stroke_bezpath_iter() {
let partial = match centroid_type {
CentroidType::Area => subpath.area_centroid_and_area(Some(1e-3), Some(1e-3)).filter(|(_, area)| *area > 0.),
CentroidType::Length => subpath.length_centroid_and_length(None, true),
CentroidType::Area => bezpath_area_centroid_and_area(&bezpath, Some(1e-3), Some(1e-3)).filter(|(_, area)| *area > 0.),
CentroidType::Length => bezpath_length_centroid_and_length(&bezpath, None, true),
};
if let Some((subpath_centroid, area_or_length)) = partial {
if let Some((path_centroid, area_or_length)) = partial {
sum += area_or_length;
centroid += area_or_length * transform.transform_point2(subpath_centroid);
centroid += area_or_length * transform.transform_point2(path_centroid);
}
}
@@ -3671,11 +3674,11 @@ mod test {
.collect()
}
// The Rectangle and Ellipse generators define the framework's fill winding convention; each is built from these
// subpath constructors (`Subpath::new_rectangle` / `Subpath::new_ellipse`), so their winding is the source of truth.
use vector_types::subpath::Subpath;
let rectangle = Vector::from_subpath(Subpath::new_rectangle(DVec2::new(-50., -50.), DVec2::new(50., 50.)));
let ellipse = Vector::from_subpath(Subpath::new_ellipse(DVec2::new(-50., -25.), DVec2::new(50., 25.)));
// The Rectangle and Ellipse generators define the framework's fill winding convention, built from these
// shape constructors (`rectangle_bezpath` / `ellipse_bezpath`), so their winding is the source of truth.
use vector_types::vector::algorithms::shapes::{ellipse_bezpath, rectangle_bezpath};
let rectangle = Vector::from_bezpath(rectangle_bezpath(DVec2::new(-50., -50.), DVec2::new(50., 50.)));
let ellipse = Vector::from_bezpath(ellipse_bezpath(DVec2::new(-50., -25.), DVec2::new(50., 25.)));
let expected = subpath_winding_signs(&rectangle)[0];
assert_eq!(subpath_winding_signs(&ellipse)[0], expected, "Rectangle and Ellipse should agree on winding");