Prototype integration of linesweeper crate into graphite

This commit is contained in:
Dennis Kobert 2025-05-25 05:51:43 +02:00
parent 63cce26755
commit dd45af48f4
No known key found for this signature in database
GPG key ID: 5A4358CB9530F933
4 changed files with 62 additions and 3 deletions

View file

@ -25,6 +25,8 @@ default = ["parsing"]
[dependencies]
glam = "0.29.0"
kurbo.version = "=0.11"
linesweeper = "0.0.4"
regex = "1.10.6"
slotmap = "1.0.7"

View file

@ -16,6 +16,62 @@ mod path;
#[cfg(test)]
mod visual_tests;
pub fn linesweeper_bool(a: &Path, a_fill_rule: FillRule, b: &Path, b_fill_rule: FillRule, op: PathBooleanOperation) -> Result<Vec<Path>, BooleanError> {
let a = to_kurbo_path(a);
let b = to_kurbo_path(b);
let op = match op {
PathBooleanOperation::Union => linesweeper::BinaryOp::Union,
PathBooleanOperation::Difference => linesweeper::BinaryOp::Difference,
PathBooleanOperation::Intersection => linesweeper::BinaryOp::Intersection,
_ => panic!(),
};
let output = linesweeper::binary_op(&a, &b, linesweeper::FillRule::NonZero, op).unwrap();
Ok(from_kurbo_path(&output))
}
fn to_kurbo_path(a: &Vec<PathSegment>) -> kurbo::BezPath {
let mut a_new = kurbo::BezPath::new();
for segment in a {
let PathSegment::Cubic(p1, p2, p3, p4) = segment else { panic!("{:?}", segment) };
let k = |x: &glam::DVec2| kurbo::Point::new(x.x, x.y);
if a_new.is_empty() {
a_new.push(kurbo::PathEl::MoveTo(k(p1)));
}
a_new.push(kurbo::PathEl::CurveTo(k(p2), k(p3), k(p4)))
}
a_new
}
fn from_kurbo_path(a: &linesweeper::topology::Contours) -> Vec<Path> {
let mut output = vec![];
for contour in a.contours() {
let mut a_new = vec![];
let mut start = glam::DVec2::ZERO;
let mut last = glam::DVec2::ZERO;
for segment in &contour.path {
let k = |x: kurbo::Point| glam::DVec2::new(x.x, x.y);
match segment {
kurbo::PathEl::MoveTo(p1) => {
last = k(p1);
start = k(p1)
}
kurbo::PathEl::CurveTo(p2, p3, p4) => {
a_new.push(PathSegment::Cubic(last, k(p2), k(p3), k(p4)));
last = k(p4)
}
kurbo::PathEl::QuadTo(p2, p3) => {
a_new.push(PathSegment::Quadratic(last, k(p2), k(p3)));
last = k(p3)
}
kurbo::PathEl::LineTo(p) => a_new.push(PathSegment::Cubic(last, last, k(p), k(p))),
kurbo::PathEl::ClosePath => a_new.push(PathSegment::Cubic(last, last, start, start)),
_ => panic!("{:?}", segment),
};
}
output.push(a_new);
}
output
}
#[cfg(feature = "parsing")]
pub(crate) use parsing::*;
pub(crate) use path::*;

View file

@ -89,6 +89,7 @@ web-sys = { workspace = true, optional = true, features = [
# Optional dependencies
image-compare = { version = "0.4.1", optional = true }
ndarray = "0.16.1"
linesweeper = "0.0.4"
[dev-dependencies]
tokio = { workspace = true, features = ["macros"] }

View file

@ -233,14 +233,14 @@ fn to_path_segments(path: &mut Vec<path_bool::PathSegment>, subpath: &bezier_rs:
}
global_end = end;
let segment = match transformed.handles {
bezier_rs::BezierHandles::Linear => PathSegment::Line(start, end),
bezier_rs::BezierHandles::Linear => PathSegment::Cubic(start, start, end, end),
bezier_rs::BezierHandles::Quadratic { handle } => PathSegment::Quadratic(start, handle, end),
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => PathSegment::Cubic(start, handle_start, handle_end, end),
};
path.push(segment);
}
if let Some(start) = global_start {
path.push(PathSegment::Line(global_end, start));
path.push(PathSegment::Cubic(global_end, global_end, start, start));
}
}
@ -342,7 +342,7 @@ fn boolean_union(a: Path, b: Path) -> Vec<Path> {
}
fn path_bool(a: Path, b: Path, op: PathBooleanOperation) -> Vec<Path> {
match path_bool::path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, op) {
match path_bool::linesweeper_bool(&a, FillRule::NonZero, &b, FillRule::NonZero, op) {
Ok(results) => results,
Err(e) => {
let a_path = path_bool::path_to_path_data(&a, 0.001);