mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Port all remaining Subpath producers to BezPath and delete the legacy subpath module (#4457)
This commit is contained in:
committed by
Dennis Kobert
parent
89617462b6
commit
9fbd44d21d
@@ -53,10 +53,6 @@ pub mod artboard {
|
||||
pub use graphic_types::artboard::*;
|
||||
}
|
||||
|
||||
pub mod subpath {
|
||||
pub use vector_types::subpath::*;
|
||||
}
|
||||
|
||||
pub mod gradient {
|
||||
pub use vector_types::{Gradient, GradientStop};
|
||||
}
|
||||
|
||||
@@ -2,18 +2,17 @@ use core_types::attribute::{Attr, Opacity, OpacityFill, Transform as TransformAt
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::node::Lane;
|
||||
use core_types::{ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DAffine2;
|
||||
use graphic_types::appearance::Appearance;
|
||||
use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms};
|
||||
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers};
|
||||
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
|
||||
use graphic_types::vector_types::vector::PointId;
|
||||
use graphic_types::vector_types::vector::VectorExt;
|
||||
use graphic_types::vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||
use graphic_types::{Graphic, IntoGraphicList, Vector};
|
||||
use linesweeper::topology::Topology;
|
||||
use linesweeper::{BinaryOp, FillRule, binary_op};
|
||||
use smallvec::SmallVec;
|
||||
use vector_types::kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
|
||||
use vector_types::kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathEl, PathSeg, Point, QuadBez};
|
||||
pub use vector_types::vector::misc::BooleanOperation;
|
||||
|
||||
// TODO: Fix boolean ops to work by removing .transform() and .one_instance_*() calls,
|
||||
@@ -216,8 +215,8 @@ fn boolean_operation_on_vector_list(vector: &List<Vector>, boolean_operation: Bo
|
||||
}
|
||||
};
|
||||
let contours = top.contours(|winding| winding.is_inside(boolean_operation));
|
||||
for subpath in from_bez_paths(contours.contours().map(|c| &c.path)) {
|
||||
row.element_mut().append_subpath(subpath, false);
|
||||
for contour in contours.contours() {
|
||||
row.element_mut().append_bezpath(closed(contour.path.clone()));
|
||||
}
|
||||
|
||||
list.push(row);
|
||||
@@ -403,65 +402,36 @@ fn quantize_segment(seg: PathSeg) -> PathSeg {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_bez_path(vector: &Vector, transform: DAffine2) -> BezPath {
|
||||
let mut path = BezPath::new();
|
||||
for subpath in vector.stroke_bezier_paths() {
|
||||
push_subpath(&mut path, &subpath, transform);
|
||||
/// Every operand and result region is treated as closed, so an open path gets its closing segment here.
|
||||
fn closed(mut path: BezPath) -> BezPath {
|
||||
if !path.elements().is_empty() && path.elements().last() != Some(&PathEl::ClosePath) {
|
||||
path.close_path();
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
fn push_subpath(path: &mut BezPath, subpath: &Subpath<PointId>, transform: DAffine2) {
|
||||
fn to_bez_path(vector: &Vector, transform: DAffine2) -> BezPath {
|
||||
let transform = Affine::new(transform.to_cols_array());
|
||||
let mut first = true;
|
||||
let mut path = BezPath::new();
|
||||
|
||||
for seg in subpath.iter_closed() {
|
||||
let quantized = quantize_segment(transform * seg);
|
||||
if first {
|
||||
first = false;
|
||||
path.move_to(quantized.start());
|
||||
for subpath in vector.stroke_bezpath_iter() {
|
||||
let mut first = true;
|
||||
|
||||
for segment in closed(subpath).segments() {
|
||||
let quantized = quantize_segment(transform * segment);
|
||||
if first {
|
||||
first = false;
|
||||
path.move_to(quantized.start());
|
||||
}
|
||||
path.push(quantized.as_path_el());
|
||||
}
|
||||
path.push(quantized.as_path_el());
|
||||
}
|
||||
path.close_path();
|
||||
}
|
||||
|
||||
fn from_bez_paths<'a>(paths: impl Iterator<Item = &'a BezPath>) -> Vec<Subpath<PointId>> {
|
||||
let mut all_subpaths = Vec::new();
|
||||
|
||||
for path in paths {
|
||||
let cubics: Vec<CubicBez> = path.segments().map(|segment| segment.to_cubic()).collect();
|
||||
let mut manipulators_list = Vec::new();
|
||||
let mut current_start = None;
|
||||
|
||||
for (index, cubic) in cubics.iter().enumerate() {
|
||||
let d = |p: Point| DVec2::new(p.x, p.y);
|
||||
let [start, handle1, handle2, end] = [d(cubic.p0), d(cubic.p1), d(cubic.p2), d(cubic.p3)];
|
||||
|
||||
if current_start.is_none() {
|
||||
// Use the correct in-handle (None) and out-handle for the start point
|
||||
manipulators_list.push(ManipulatorGroup::new(start, None, Some(handle1)));
|
||||
} else {
|
||||
// Update the out-handle of the previous point
|
||||
if let Some(last) = manipulators_list.last_mut() {
|
||||
last.out_handle = Some(handle1);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the end point with the correct in-handle and out-handle (None)
|
||||
manipulators_list.push(ManipulatorGroup::new(end, Some(handle2), None));
|
||||
|
||||
current_start = Some(end);
|
||||
|
||||
// Check if this is the last segment
|
||||
if index == cubics.len() - 1 {
|
||||
all_subpaths.push(Subpath::new(manipulators_list, true));
|
||||
manipulators_list = Vec::new(); // Reset manipulators for the next path
|
||||
}
|
||||
if !first {
|
||||
path.close_path();
|
||||
}
|
||||
}
|
||||
|
||||
all_subpaths
|
||||
path
|
||||
}
|
||||
|
||||
pub fn boolean_intersect(a: &BezPath, b: &BezPath) -> Vec<BezPath> {
|
||||
@@ -479,9 +449,11 @@ mod tests {
|
||||
use super::*;
|
||||
use core_types::Color;
|
||||
use core_types::record::Group;
|
||||
use glam::DVec2;
|
||||
use graphic_types::vector_types::kurbo::Shape;
|
||||
|
||||
fn square(corner: DVec2) -> Vector {
|
||||
Vector::from_subpath(Subpath::<PointId>::new_rectangle(corner, corner + DVec2::ONE))
|
||||
Vector::from_bezpath(graphic_types::vector_types::kurbo::Rect::new(corner.x, corner.y, corner.x + 1., corner.y + 1.).to_path(graphic_types::vector_types::kurbo::DEFAULT_ACCURACY))
|
||||
}
|
||||
|
||||
fn black_paint() -> List<Graphic<'static>> {
|
||||
|
||||
@@ -185,7 +185,17 @@ mod test {
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{FieldWrite, FrameClaim, Layout, RecordSource, Served, capture, element_write};
|
||||
use core_types::value::ValueSource;
|
||||
use vector_types::subpath::Subpath;
|
||||
|
||||
/// An open polyline through the anchors, standing in for the deleted `Subpath::from_anchors`.
|
||||
fn polyline(anchors: &[DVec2]) -> Vector {
|
||||
let mut bezpath = vector_types::kurbo::BezPath::new();
|
||||
let Some((&first, rest)) = anchors.split_first() else { return Vector::default() };
|
||||
bezpath.move_to(vector_types::vector::misc::dvec2_to_point(first));
|
||||
for &anchor in rest {
|
||||
bezpath.line_to(vector_types::vector::misc::dvec2_to_point(anchor));
|
||||
}
|
||||
Vector::from_bezpath(bezpath)
|
||||
}
|
||||
|
||||
struct TransformSource {
|
||||
layout: Layout,
|
||||
@@ -398,10 +408,7 @@ mod test {
|
||||
let row0_transform = DAffine2::from_translation(DVec2::new(100., 0.));
|
||||
let points = VectorRows {
|
||||
layout: vector_rows_layout(),
|
||||
rows: vec![
|
||||
(Vector::from_subpath(Subpath::from_anchors(row0.clone(), false)), row0_transform),
|
||||
(Vector::from_subpath(Subpath::from_anchors(row1.clone(), false)), DAffine2::IDENTITY),
|
||||
],
|
||||
rows: vec![(polyline(&row0), row0_transform), (polyline(&row1), DAffine2::IDENTITY)],
|
||||
};
|
||||
let content_layout = transform_layout();
|
||||
let content = PositionProbe { layout: content_layout.clone() };
|
||||
@@ -442,7 +449,7 @@ mod test {
|
||||
let positions: Vec<DVec2> = vec![DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
|
||||
let points = VectorRows {
|
||||
layout: vector_rows_layout(),
|
||||
rows: vec![(Vector::from_subpath(Subpath::from_anchors(positions.clone(), false)), DAffine2::IDENTITY)],
|
||||
rows: vec![(polyline(&positions), DAffine2::IDENTITY)],
|
||||
};
|
||||
let content_layout = transform_layout();
|
||||
let content = PositionProbe { layout: content_layout.clone() };
|
||||
|
||||
@@ -8,13 +8,13 @@ use skrifa::outline::{DrawSettings, OutlinePen};
|
||||
use skrifa::raw::FontRef as ReadFontsRef;
|
||||
use skrifa::{MetadataProvider, OutlineGlyph};
|
||||
use vector_types::ATTR_EDITOR_CLICK_TARGET;
|
||||
use vector_types::subpath::{ManipulatorGroup, Subpath};
|
||||
use vector_types::vector::{PointId, Vector};
|
||||
use vector_types::kurbo::{Affine, BezPath, Point, Rect, Shape};
|
||||
use vector_types::vector::{Vector, VectorExt};
|
||||
|
||||
pub struct PathBuilder {
|
||||
current_subpath: Subpath<PointId>,
|
||||
origin: DVec2,
|
||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||
/// Contours of the glyph currently being drawn, accumulated as a single path.
|
||||
glyph_bezpath: BezPath,
|
||||
pub vector_list: List<Vector>,
|
||||
/// Per-glyph AABBs collected in single-item mode, published as `ATTR_EDITOR_CLICK_TARGET` in `finalize()`.
|
||||
merged_click_target_bboxes: Vec<[DVec2; 2]>,
|
||||
@@ -28,14 +28,12 @@ pub struct PathBuilder {
|
||||
/// `local_transforms` stays stable when all glyphs are clipped during a resize drag.
|
||||
first_glyph_offset: DVec2,
|
||||
scale: f64,
|
||||
id: PointId,
|
||||
}
|
||||
|
||||
impl PathBuilder {
|
||||
pub fn new(per_glyph_items: bool, scale: f64, text_frame_size: DVec2, first_glyph_offset: DVec2) -> Self {
|
||||
Self {
|
||||
current_subpath: Subpath::new(Vec::new(), false),
|
||||
glyph_subpaths: Vec::new(),
|
||||
glyph_bezpath: BezPath::new(),
|
||||
vector_list: if per_glyph_items { List::new() } else { List::new_from_element(Vector::default()) },
|
||||
merged_click_target_bboxes: Vec::new(),
|
||||
merged_click_target_baselines: Vec::new(),
|
||||
@@ -43,13 +41,12 @@ impl PathBuilder {
|
||||
text_frame_size,
|
||||
first_glyph_offset,
|
||||
scale,
|
||||
id: PointId::ZERO,
|
||||
origin: DVec2::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn point(&self, x: f32, y: f32) -> DVec2 {
|
||||
DVec2::new(self.origin.x + x as f64, self.origin.y - y as f64) * self.scale
|
||||
fn point(&self, x: f32, y: f32) -> Point {
|
||||
Point::new((self.origin.x + x as f64) * self.scale, (self.origin.y - y as f64) * self.scale)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -66,26 +63,23 @@ impl PathBuilder {
|
||||
let location_ref = LocationRef::new(normalized_coords);
|
||||
let settings = DrawSettings::unhinted(Size::new(size), location_ref);
|
||||
glyph.draw(settings, self).unwrap();
|
||||
let has_geometry = !self.glyph_subpaths.is_empty();
|
||||
let has_geometry = !self.glyph_bezpath.is_empty();
|
||||
|
||||
// Apply transforms in correct order: style-based skew first, then user-requested skew
|
||||
// This ensures font synthesis (italic) is applied before user transformations
|
||||
for glyph_subpath in &mut self.glyph_subpaths {
|
||||
if let Some(style_skew) = style_skew {
|
||||
glyph_subpath.apply_transform(style_skew);
|
||||
}
|
||||
|
||||
glyph_subpath.apply_transform(skew);
|
||||
if let Some(style_skew) = style_skew {
|
||||
self.glyph_bezpath.apply_affine(Affine::new(style_skew.to_cols_array()));
|
||||
}
|
||||
self.glyph_bezpath.apply_affine(Affine::new(skew.to_cols_array()));
|
||||
|
||||
let glyph_bbox = subpaths_bounding_box(&self.glyph_subpaths);
|
||||
let glyph_bbox = bezpath_bounding_box(&self.glyph_bezpath);
|
||||
|
||||
if per_glyph_items {
|
||||
// Frame in item-local space: top-left at `-glyph_offset` so the item transform cancels it
|
||||
// back to the layer-local frame origin, regardless of which glyph survived
|
||||
let frame_in_item_local = DAffine2::from_scale_angle_translation(self.text_frame_size, 0., -glyph_offset);
|
||||
|
||||
let item = Item::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false))
|
||||
let item = Item::new_from_element(Vector::from_bezpath(core::mem::take(&mut self.glyph_bezpath)))
|
||||
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset))
|
||||
.with_attribute(ATTR_EDITOR_TEXT_FRAME, frame_in_item_local);
|
||||
self.vector_list.push(item);
|
||||
@@ -93,10 +87,9 @@ impl PathBuilder {
|
||||
// Defer click target creation to `finalize()` where adjacent AABBs get widened
|
||||
self.per_glyph_bboxes.push(glyph_bbox);
|
||||
} else {
|
||||
for subpath in self.glyph_subpaths.drain(..) {
|
||||
// Unwrapping here is ok because `self.vector_list` is initialized with a single `List<Vector>` item
|
||||
self.vector_list.element_mut(0).unwrap().append_subpath(subpath, false);
|
||||
}
|
||||
// Unwrapping here is ok because `self.vector_list` is initialized with a single `List<Vector>` item
|
||||
self.vector_list.element_mut(0).unwrap().append_bezpath(core::mem::take(&mut self.glyph_bezpath));
|
||||
|
||||
if let Some(bbox) = glyph_bbox {
|
||||
self.merged_click_target_bboxes.push(bbox);
|
||||
self.merged_click_target_baselines.push(glyph_offset.y);
|
||||
@@ -197,8 +190,8 @@ impl PathBuilder {
|
||||
// Project back to glyph-local and stamp as click targets
|
||||
for (entry, widened) in entries.iter().zip(layer_bboxes.iter()) {
|
||||
let glyph_local = [widened[0] - entry.1, widened[1] - entry.1];
|
||||
let rect = Subpath::new_rectangle(glyph_local[0], glyph_local[1]);
|
||||
self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, entry.0, Some(Vector::from_subpaths([rect], false)));
|
||||
let rect = rectangle_bezpath(glyph_local[0], glyph_local[1]);
|
||||
self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, entry.0, Some(Vector::from_bezpath(rect)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,8 +200,11 @@ impl PathBuilder {
|
||||
let mut bboxes = self.merged_click_target_bboxes;
|
||||
widen_horizontal_gaps(&mut bboxes, &self.merged_click_target_baselines);
|
||||
|
||||
let widened_subpaths: Vec<_> = bboxes.iter().map(|[min, max]| Subpath::new_rectangle(*min, *max)).collect();
|
||||
self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, 0, Some(Vector::from_subpaths(widened_subpaths, false)));
|
||||
let mut widened_bezpath = BezPath::new();
|
||||
for [min, max] in &bboxes {
|
||||
widened_bezpath.extend(rectangle_bezpath(*min, *max));
|
||||
}
|
||||
self.vector_list.set_attribute(ATTR_EDITOR_CLICK_TARGET, 0, Some(Vector::from_bezpath(widened_bezpath)));
|
||||
}
|
||||
|
||||
// Fill in text frame for items that don't have one yet (single-item mode, where item 0 = identity)
|
||||
@@ -253,40 +249,37 @@ fn widen_horizontal_gaps(bboxes: &mut [[DVec2; 2]], baselines: &[f64]) {
|
||||
}
|
||||
}
|
||||
|
||||
fn subpaths_bounding_box(subpaths: &[Subpath<PointId>]) -> Option<[DVec2; 2]> {
|
||||
subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box())
|
||||
.reduce(|[a_min, a_max], [b_min, b_max]| [a_min.min(b_min), a_max.max(b_max)])
|
||||
fn bezpath_bounding_box(bezpath: &BezPath) -> Option<[DVec2; 2]> {
|
||||
if bezpath.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rect = bezpath.bounding_box();
|
||||
Some([DVec2::new(rect.x0, rect.y0), DVec2::new(rect.x1, rect.y1)])
|
||||
}
|
||||
|
||||
fn rectangle_bezpath(corner1: DVec2, corner2: DVec2) -> BezPath {
|
||||
Rect::new(corner1.x, corner1.y, corner2.x, corner2.y).to_path(0.)
|
||||
}
|
||||
|
||||
impl OutlinePen for PathBuilder {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
if !self.current_subpath.is_empty() {
|
||||
self.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
}
|
||||
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(self.point(x, y), self.id.next_id()));
|
||||
self.glyph_bezpath.move_to(self.point(x, y));
|
||||
}
|
||||
|
||||
fn line_to(&mut self, x: f32, y: f32) {
|
||||
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(self.point(x, y), self.id.next_id()));
|
||||
self.glyph_bezpath.line_to(self.point(x, y));
|
||||
}
|
||||
|
||||
fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||
let [handle, anchor] = [self.point(x1, y1), self.point(x2, y2)];
|
||||
self.current_subpath.last_manipulator_group_mut().unwrap().out_handle = Some(handle);
|
||||
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_with_id(anchor, None, None, self.id.next_id()));
|
||||
self.glyph_bezpath.quad_to(self.point(x1, y1), self.point(x2, y2));
|
||||
}
|
||||
|
||||
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
|
||||
let [handle1, handle2, anchor] = [self.point(x1, y1), self.point(x2, y2), self.point(x3, y3)];
|
||||
self.current_subpath.last_manipulator_group_mut().unwrap().out_handle = Some(handle1);
|
||||
self.current_subpath
|
||||
.push_manipulator_group(ManipulatorGroup::new_with_id(anchor, Some(handle2), None, self.id.next_id()));
|
||||
self.glyph_bezpath.curve_to(self.point(x1, y1), self.point(x2, y2), self.point(x3, y3));
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.current_subpath.set_closed(true);
|
||||
self.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
self.glyph_bezpath.close_path();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,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};
|
||||
@@ -18,7 +20,7 @@ fn circle(
|
||||
radius: f64,
|
||||
) -> Vector {
|
||||
let radius = radius.abs();
|
||||
Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
|
||||
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.
|
||||
@@ -36,7 +38,7 @@ fn arc(
|
||||
sweep_angle: Angle,
|
||||
arc_type: ArcType,
|
||||
) -> Vector {
|
||||
Vector::from_subpath(subpath::Subpath::new_arc(
|
||||
Vector::from_bezpath(shapes::arc_bezpath(
|
||||
radius,
|
||||
start_angle / 360. * std::f64::consts::TAU,
|
||||
sweep_angle / 360. * std::f64::consts::TAU,
|
||||
@@ -56,7 +58,7 @@ fn spiral(
|
||||
#[default(25)] outer_radius: f64,
|
||||
#[default(90.)] angular_resolution: f64,
|
||||
) -> Vector {
|
||||
Vector::from_subpath(subpath::Subpath::new_spiral(
|
||||
Vector::from_bezpath(shapes::spiral_bezpath(
|
||||
inner_radius,
|
||||
outer_radius,
|
||||
turns,
|
||||
@@ -82,7 +84,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 {
|
||||
@@ -130,7 +132,7 @@ fn rectangle(
|
||||
radii
|
||||
};
|
||||
|
||||
Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., radii))
|
||||
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.
|
||||
@@ -158,8 +160,7 @@ fn regular_polygon<T: AsU64>(
|
||||
radius: f64,
|
||||
) -> Vector {
|
||||
let points = sides.as_u64();
|
||||
let radius: f64 = radius * 2.;
|
||||
Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))
|
||||
Vector::from_bezpath(shapes::regular_polygon_bezpath(DVec2::ZERO, points, radius))
|
||||
}
|
||||
|
||||
/// Generates an n-pointed star shape with inner and outer points at chosen radii from the center.
|
||||
@@ -179,10 +180,7 @@ fn star<T: AsU64>(
|
||||
radius_2: f64,
|
||||
) -> Vector {
|
||||
let points = sides.as_u64();
|
||||
let diameter: f64 = radius_1 * 2.;
|
||||
let inner_diameter = radius_2 * 2.;
|
||||
|
||||
Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))
|
||||
Vector::from_bezpath(shapes::star_polygon_bezpath(DVec2::ZERO, points, radius_1, radius_2))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
@@ -235,11 +233,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.)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,12 +260,12 @@ fn arrow(
|
||||
#[default(30)] head_width: PixelLength,
|
||||
#[default(20)] head_length: PixelLength,
|
||||
) -> Vector {
|
||||
Vector::from_subpath(subpath::Subpath::new_arrow(DVec2::ZERO, arrow_to, shaft_width, head_width, head_length))
|
||||
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: PixelSize) -> Vector {
|
||||
Vector::from_subpath(subpath::Subpath::new_line(DVec2::ZERO, line_to))
|
||||
Vector::from_bezpath(shapes::line_bezpath(DVec2::ZERO, line_to))
|
||||
}
|
||||
|
||||
trait GridSpacing {
|
||||
@@ -353,9 +347,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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,15 @@ use std::collections::{HashMap, HashSet};
|
||||
use vector_types::ATTR_GRADIENT_FORM;
|
||||
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, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
||||
@@ -734,11 +735,11 @@ pub fn merge_by_distance<'e, V: MapVectorContent + Clone + Send + Sync + CacheHa
|
||||
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),
|
||||
@@ -1149,20 +1150,22 @@ fn auto_tangents<'e, V: MapVectorContent + Clone + Send + Sync + CacheHash + 'st
|
||||
let (source, transform) = map_vectors(ctx.arena(), source, *lane_transform, |source, transform| {
|
||||
let mut result = Vector { ..Default::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];
|
||||
@@ -2895,7 +2898,7 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic<'static>>, progres
|
||||
|
||||
/// 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;
|
||||
@@ -2943,7 +2946,7 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic<'static>>, progres
|
||||
|
||||
/// 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();
|
||||
@@ -3400,7 +3403,7 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic<'static>>, progres
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -3997,17 +4000,17 @@ fn centroid(_: impl Ctx, vector: IList<Vector>, centroid_type: CentroidType) ->
|
||||
|
||||
for index in 0..vector.len() {
|
||||
let element = vector.element_ref(index);
|
||||
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 {
|
||||
let transform: DAffine2 = vector.lane(index).attr::<TransformAttr>();
|
||||
let subpath_centroid = transform.transform_point2(subpath_centroid);
|
||||
let path_centroid = transform.transform_point2(path_centroid);
|
||||
|
||||
sum += area_or_length;
|
||||
centroid += area_or_length * subpath_centroid;
|
||||
centroid += area_or_length * path_centroid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4108,11 +4111,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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user