# vectorcraft-pathops Path operations for VectorCraft: booleans, the Pathfinder panel, Shape Builder regions, offset path, outline stroke, simplify and the other Object → Path commands. Everything takes and returns `vectorcraft_geom::PathData`. ## Booleans (curve-preserving) ```rust pub enum BoolOp { Union, Intersect, Difference, Xor } pub fn boolean(a: &PathData, a_rule: FillRule, b: &PathData, b_rule: FillRule, op: BoolOp) -> PathData; pub fn try_boolean(.., precision: f64) -> Result; // errors on NaN/inf pub fn boolean_n(paths: &[(&PathData, FillRule)], pred: impl Fn(&[bool]) -> bool) -> PathData; pub fn unite_all(paths: &[(&PathData, FillRule)]) -> PathData; // fast many-shape union pub fn normalize(path: &PathData, rule: FillRule) -> PathData; // resolve self-overlaps pub fn area(path: &PathData, rule: FillRule) -> f64; // exact filled area ``` The engine is `linesweeper` (a robust sweep line that works directly on cubic Béziers, so there's no polygon flattening). The sweep splits curves at intersections and y-extrema. Afterwards: - pieces that lie on one input cubic are rebuilt exactly as a single sub-curve of that cubic; - other smooth joints (for example, in stroker output) are refitted by least squares within `DEFAULT_PRECISION` (0.01 pt); - collinear line pieces are merged; - the inputs' own anchors are always kept; - slivers narrower than the precision are dropped. Output contours are simple and consistently oriented: outer contours and holes wind in opposite directions, so the result fills the same under either fill rule. Open subpaths are closed implicitly because they're treated as filled. ## Pathfinder / Shape Builder ```rust pub struct Shape { pub path: PathData, pub rule: FillRule, pub key: u64 } // key = paint identity pub enum PathfinderOp { Unite, MinusFront, Intersect, Exclude, Divide, Trim, Merge, Crop, Outline, MinusBack } pub fn pathfinder(op: PathfinderOp, shapes: &[Shape] /* back → front */) -> Vec; pub struct Region { pub path: PathData, pub sources: Vec } pub fn regions(shapes: &[Shape]) -> Vec; // all faces of the arrangement pub fn region_at(shapes: &[Shape], p: Point) -> Option; pub fn merge_regions(regions: &[&Region]) -> PathData; // Shape Builder merge = union ``` Each result takes its key as follows: | Operation | Result | |---|---| | Unite, Intersect, Exclude, Minus Back | Takes the front-most key | | Minus Front | Takes the back-most key | | Divide | One shape per face, keyed by the front-most shape that covers the face | | Trim | One shape for each object that is still visible | | Merge | Joins touching visible parts that share a key; each connected component becomes one shape | | Crop | Keeps the visible parts of the lower objects inside the front-most object | | Outline | Returns open edge paths, split at every junction; coincident edges are de-duplicated, and each keeps the front-most key | ## Offset / stroke ```rust pub use kurbo::{Cap, Join}; pub fn offset_path(path: &PathData, delta: f64, join: Join, miter_limit: f64) -> PathData; // delta < 0 insets pub fn outline_stroke(path: &PathData, width: f64, cap: Cap, join: Join, miter_limit: f64) -> PathData; ``` `outline_stroke` passes the path to `kurbo::stroke` and then normalizes the result, which removes the overlaps at joins. `offset_path` computes `fill ∪ stroke(2|d|)` for a positive offset and `fill − stroke(2|d|)` for a negative one. ## Path editing ```rust pub fn simplify(path: &PathData, tolerance: f64) -> PathData; pub fn simplify_with(path: &PathData, opts: &SimplifyOptions) -> PathData; // tolerance, corner_angle_deg, straight_lines pub fn smooth(path: &PathData, amount: f64) -> PathData; pub fn remove_redundant_points(path: &PathData, tolerance: f64) -> PathData; pub fn add_anchor_points(path: &PathData) -> PathData; pub fn average(path: &PathData, selection: &[(usize, usize)], axis: AverageAxis) -> PathData; pub fn join(paths: &[PathData], tolerance: f64) -> PathData; pub fn split_into_grid(rect: Rect, rows: usize, cols: usize, gutter: f64) -> Vec; ``` `simplify` fits least-squares cubics with fixed end tangents, then reparameterizes with Newton steps and splits at the point of worst error. Any anchor that turns more than the corner threshold (30° by default) stays a corner. ## Caveats - linesweeper is still in beta. When two identical curves overlap, the sweep can leave a tiny lens between them; the sliver filter removes it. - The Outline op finds split points by snapping to arrangement junctions with a small tolerance. - The quality of offsets depends on kurbo's parallel-curve stroker. Its output is refitted, so offset results have more anchors than boolean results.