pub use kurbo;
use kurbo::{BezPath, ParamCurve, PathEl, Point, Rect};
#[derive(Debug, Copy, Clone)]
pub enum CubicApprox {
Linear,
Flatten(f64),
Midpoint,
Lyon(f64),
}
pub fn break_path(orig: &BezPath, cubic_approx: CubicApprox) -> BezPath {
let mut initial = Point::ORIGIN;
let mut p0 = Point::ORIGIN;
let mut path = BezPath::new();
fn add_quad(path: &mut BezPath, p0: Point, p1: Point, p2: Point) {
fn split(t: f64, p0: Point, p1: Point, p2: Point) -> [Point; 3] {
let pa = p0.lerp(p1, t);
let pc = p1.lerp(p2, t);
let pb = pa.lerp(pc, t);
[pa, pb, pc]
}
let aabb = Rect::from_points(p0, p1);
let tx = if aabb.min_x() > p1.x || p1.x > aabb.max_x() {
Some((p0.x - p1.x) / (p0.x - 2.0 * p1.x + p2.x))
} else {
None
};
let ty = if aabb.min_y() > p1.y || p1.y > aabb.max_y() {
Some((p0.y - p1.y) / (p0.y - 2.0 * p1.y + p2.y))
} else {
None
};
match (tx, ty) {
(Some(tx), Some(ty)) => {
let t0 = tx.min(ty);
let t1 = (tx.max(ty) - t0) / (1.0 - t0);
let [pa0, pb0, pc0] = split(t0, p0, p1, p2);
let [pa1, pb1, pc1] = split(t1, pb0, pc0, p2);
path.quad_to(pa0, pb0);
path.quad_to(pa1, pb1);
path.quad_to(pc1, p2);
}
(Some(t), None) | (None, Some(t)) => {
let [pa, pb, pc] = split(t, p0, p1, p2);
path.quad_to(pa, pb);
path.quad_to(pc, p2);
}
(None, None) => {
path.quad_to(p1, p2);
}
}
}
for elem in orig {
match elem {
PathEl::MoveTo(p) => {
path.move_to(p);
p0 = p;
initial = p;
}
PathEl::LineTo(p) => {
path.line_to(p);
p0 = p;
}
PathEl::QuadTo(p1, p2) => {
add_quad(&mut path, p0, p1, p2);
p0 = p2;
}
PathEl::CurveTo(p1, p2, p3) => {
match cubic_approx {
CubicApprox::Linear => {
path.line_to(p3);
}
CubicApprox::Flatten(tolerance) => {
let mut subpath = BezPath::new();
subpath.move_to(p0);
subpath.curve_to(p1, p2, p3);
kurbo::flatten(subpath, tolerance, |el| match el {
PathEl::MoveTo(_) => {}
PathEl::LineTo(p) => {
path.line_to(p);
p0 = p;
}
_ => unreachable!(),
});
}
CubicApprox::Midpoint => {
if p0 == p1 {
add_quad(&mut path, p0, p2, p3);
} else if p2 == p3 {
add_quad(&mut path, p0, p1, p3);
} else {
let p_ca = p0.lerp(p1, 0.75);
let p_cb = p3.lerp(p2, 0.75);
let p_m = p_ca.midpoint(p_cb);
add_quad(&mut path, p0, p_ca, p_m);
add_quad(&mut path, p_m, p_cb, p3);
}
}
CubicApprox::Lyon(tolerance) => {
use lyon_geom::{
cubic_bezier::CubicBezierSegment,
cubic_to_quadratic::cubic_to_quadratics,
};
cubic_to_quadratics(
&CubicBezierSegment {
from: [p0.x, p0.y].into(),
ctrl1: [p1.x, p1.y].into(),
ctrl2: [p2.x, p2.y].into(),
to: [p3.x, p3.y].into(),
},
tolerance,
&mut |segment| {
add_quad(
&mut path,
Point::new(segment.from.x, segment.from.y),
Point::new(segment.ctrl.x, segment.ctrl.y),
Point::new(segment.to.x, segment.to.y),
);
},
);
}
}
p0 = p3;
}
PathEl::ClosePath => {
path.close_path();
p0 = initial;
}
}
}
path
}
pub fn monotonize_quads(orig: &BezPath) -> BezPath {
let mut initial = Point::ORIGIN;
let mut p0 = Point::ORIGIN;
let mut path = BezPath::new();
fn split_quad(path: &mut BezPath, p0: Point, p1: Point, p2: Point) {
let min = Point::new(p0.x.min(p2.x), p0.y.min(p2.y));
let max = Point::new(p0.x.max(p2.x), p0.y.max(p2.y));
let tx = if p1.x < min.x || max.x < p1.x {
Some((p0.x - p1.x) / (p0.x - 2.0 * p1.x + p2.x))
} else {
None
};
let ty = if p1.y < min.y || max.y < p1.y {
Some((p0.y - p1.y) / (p0.y - 2.0 * p1.y + p2.y))
} else {
None
};
match (tx, ty) {
(Some(tx), Some(ty)) => {
let (t0, t1) = (tx.min(ty), tx.max(ty));
let t = t0;
let c2 = kurbo::QuadBez::new(p0, p1, p2).eval(t);
let c1 = kurbo::Line::new(p0, p1).eval(t);
let c3 = kurbo::Line::new(p1, p2).eval(t);
let t = (t1 - t0) / (1.0 - t1);
let c5 = kurbo::QuadBez::new(c2, c3, p2).eval(t);
let c4 = kurbo::Line::new(c2, c3).eval(t);
let c6 = kurbo::Line::new(c3, p2).eval(t);
path.quad_to(c1, c2);
path.quad_to(c4, c5);
path.quad_to(c6, p2);
}
(Some(t), None) | (None, Some(t)) => {
let p = kurbo::QuadBez::new(p0, p1, p2).eval(t);
let p10 = kurbo::Line::new(p0, p1).eval(t);
let p11 = kurbo::Line::new(p1, p2).eval(t);
path.quad_to(p10, p);
path.quad_to(p11, p2);
}
(None, None) => path.quad_to(p1, p2),
}
}
for elem in orig {
match elem {
PathEl::MoveTo(p) => {
path.move_to(p);
p0 = p;
initial = p;
}
PathEl::LineTo(p) => {
path.line_to(p);
p0 = p;
}
PathEl::QuadTo(p1, p2) => {
split_quad(&mut path, p0, p1, p2);
p0 = p2;
}
PathEl::CurveTo(p1, p2, p3) => {
path.curve_to(p1, p2, p3);
p0 = p3;
}
PathEl::ClosePath => {
path.close_path();
p0 = initial;
}
}
}
path
}