#![allow(clippy::many_single_char_names)]
use core::iter::{Extend, FromIterator};
use core::mem;
use core::ops::{Mul, Range};
use alloc::vec::Vec;
use arrayvec::ArrayVec;
use crate::MAX_EXTREMA;
use crate::common::{solve_cubic, solve_quadratic};
use crate::{
Affine, CubicBez, Line, Nearest, ParamCurve, ParamCurveArclen, ParamCurveArea,
ParamCurveExtrema, ParamCurveNearest, Point, QuadBez, Rect, Shape, TranslateScale, Vec2,
};
#[cfg(not(feature = "std"))]
use crate::common::FloatFuncs;
#[derive(Clone, Default, Debug, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BezPath(Vec<PathEl>);
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PathEl {
MoveTo(Point),
LineTo(Point),
QuadTo(Point, Point),
CurveTo(Point, Point, Point),
ClosePath,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PathSeg {
Line(Line),
Quad(QuadBez),
Cubic(CubicBez),
}
#[derive(Debug, Clone, Copy)]
pub struct LineIntersection {
pub line_t: f64,
pub segment_t: f64,
}
pub struct MinDistance {
pub distance: f64,
pub t1: f64,
pub t2: f64,
}
impl BezPath {
#[inline(always)]
pub fn new() -> BezPath {
BezPath::default()
}
pub fn with_capacity(capacity: usize) -> BezPath {
BezPath(Vec::with_capacity(capacity))
}
pub fn from_vec(v: Vec<PathEl>) -> BezPath {
debug_assert!(
v.is_empty() || matches!(v.first(), Some(PathEl::MoveTo(_))),
"BezPath must begin with MoveTo"
);
BezPath(v)
}
pub fn pop(&mut self) -> Option<PathEl> {
self.0.pop()
}
pub fn push(&mut self, el: PathEl) {
self.0.push(el);
debug_assert!(
matches!(self.0.first(), Some(PathEl::MoveTo(_))),
"BezPath must begin with MoveTo"
);
}
pub fn move_to<P: Into<Point>>(&mut self, p: P) {
self.push(PathEl::MoveTo(p.into()));
}
pub fn line_to<P: Into<Point>>(&mut self, p: P) {
debug_assert!(!self.0.is_empty(), "uninitialized subpath (missing MoveTo)");
self.push(PathEl::LineTo(p.into()));
}
pub fn quad_to<P: Into<Point>>(&mut self, p1: P, p2: P) {
debug_assert!(!self.0.is_empty(), "uninitialized subpath (missing MoveTo)");
self.push(PathEl::QuadTo(p1.into(), p2.into()));
}
pub fn curve_to<P: Into<Point>>(&mut self, p1: P, p2: P, p3: P) {
debug_assert!(!self.0.is_empty(), "uninitialized subpath (missing MoveTo)");
self.push(PathEl::CurveTo(p1.into(), p2.into(), p3.into()));
}
pub fn close_path(&mut self) {
debug_assert!(!self.0.is_empty(), "uninitialized subpath (missing MoveTo)");
self.push(PathEl::ClosePath);
}
#[inline(always)]
pub fn into_elements(self) -> Vec<PathEl> {
self.0
}
#[inline(always)]
pub fn elements(&self) -> &[PathEl] {
&self.0
}
#[inline(always)]
pub fn elements_mut(&mut self) -> &mut [PathEl] {
&mut self.0
}
pub fn iter(&self) -> impl Iterator<Item = PathEl> + Clone + '_ {
self.0.iter().copied()
}
pub fn segments(&self) -> impl Iterator<Item = PathSeg> + Clone + '_ {
segments(self.iter())
}
pub fn truncate(&mut self, len: usize) {
self.0.truncate(len);
}
pub fn get_seg(&self, ix: usize) -> Option<PathSeg> {
if ix == 0 || ix >= self.0.len() {
return None;
}
let last = match self.0[ix - 1] {
PathEl::MoveTo(p) => p,
PathEl::LineTo(p) => p,
PathEl::QuadTo(_, p2) => p2,
PathEl::CurveTo(_, _, p3) => p3,
PathEl::ClosePath => return None,
};
match self.0[ix] {
PathEl::LineTo(p) => Some(PathSeg::Line(Line::new(last, p))),
PathEl::QuadTo(p1, p2) => Some(PathSeg::Quad(QuadBez::new(last, p1, p2))),
PathEl::CurveTo(p1, p2, p3) => Some(PathSeg::Cubic(CubicBez::new(last, p1, p2, p3))),
PathEl::ClosePath => self.0[..ix].iter().rev().find_map(|el| match *el {
PathEl::MoveTo(start) if start != last => {
Some(PathSeg::Line(Line::new(last, start)))
}
_ => None,
}),
PathEl::MoveTo(_) => None,
}
}
pub fn is_empty(&self) -> bool {
self.0
.iter()
.all(|el| matches!(el, PathEl::MoveTo(..) | PathEl::ClosePath))
}
pub fn apply_affine(&mut self, affine: Affine) {
for el in self.0.iter_mut() {
*el = affine * (*el);
}
}
#[inline]
pub fn is_finite(&self) -> bool {
self.0.iter().all(|v| v.is_finite())
}
#[inline]
pub fn is_nan(&self) -> bool {
self.0.iter().any(|v| v.is_nan())
}
pub fn control_box(&self) -> Rect {
let mut cbox: Option<Rect> = None;
let mut add_pts = |pts: &[Point]| {
for pt in pts {
cbox = match cbox {
Some(cbox) => Some(cbox.union_pt(*pt)),
_ => Some(Rect::from_points(*pt, *pt)),
};
}
};
for &el in self.elements() {
match el {
PathEl::MoveTo(p0) | PathEl::LineTo(p0) => add_pts(&[p0]),
PathEl::QuadTo(p0, p1) => add_pts(&[p0, p1]),
PathEl::CurveTo(p0, p1, p2) => add_pts(&[p0, p1, p2]),
PathEl::ClosePath => {}
}
}
cbox.unwrap_or_default()
}
pub fn current_position(&self) -> Option<Point> {
match self.0.last()? {
PathEl::MoveTo(p) => Some(*p),
PathEl::LineTo(p1) => Some(*p1),
PathEl::QuadTo(_, p2) => Some(*p2),
PathEl::CurveTo(_, _, p3) => Some(*p3),
PathEl::ClosePath => self
.elements()
.iter()
.rev()
.skip(1)
.take_while(|el| !matches!(el, PathEl::ClosePath))
.last()
.and_then(|el| el.end_point()),
}
}
pub fn subpaths(&self) -> impl Iterator<Item = &[PathEl]> {
let elements = self.elements();
let mut i = 0;
core::iter::from_fn(move || {
if i >= elements.len() {
return None;
}
let start = i;
i += 1;
while i < elements.len() && !matches!(elements[i], PathEl::MoveTo(_)) {
i += 1;
}
Some(&elements[start..i])
})
}
pub fn reverse_subpaths(&self) -> BezPath {
let elements = self.elements();
let mut start_ix = 1;
let mut start_pt = Point::default();
let mut reversed = BezPath(Vec::with_capacity(elements.len()));
let mut pending_move = false;
for (ix, el) in elements.iter().enumerate() {
match el {
PathEl::MoveTo(pt) => {
if pending_move {
reversed.push(PathEl::MoveTo(start_pt));
}
if start_ix < ix {
reverse_subpath(start_pt, &elements[start_ix..ix], &mut reversed);
}
pending_move = true;
start_pt = *pt;
start_ix = ix + 1;
}
PathEl::ClosePath => {
if start_ix <= ix {
reverse_subpath(start_pt, &elements[start_ix..ix], &mut reversed);
}
reversed.push(PathEl::ClosePath);
start_ix = ix + 1;
pending_move = false;
}
_ => {
pending_move = false;
}
}
}
if start_ix < elements.len() {
reverse_subpath(start_pt, &elements[start_ix..], &mut reversed);
} else if pending_move {
reversed.push(PathEl::MoveTo(start_pt));
}
reversed
}
}
fn reverse_subpath(start_pt: Point, els: &[PathEl], reversed: &mut BezPath) {
let end_pt = els.last().and_then(|el| el.end_point()).unwrap_or(start_pt);
reversed.push(PathEl::MoveTo(end_pt));
for (ix, el) in els.iter().enumerate().rev() {
let end_pt = if ix > 0 {
els[ix - 1].end_point().unwrap()
} else {
start_pt
};
match el {
PathEl::LineTo(_) => reversed.push(PathEl::LineTo(end_pt)),
PathEl::QuadTo(c0, _) => reversed.push(PathEl::QuadTo(*c0, end_pt)),
PathEl::CurveTo(c0, c1, _) => reversed.push(PathEl::CurveTo(*c1, *c0, end_pt)),
_ => panic!("reverse_subpath expects MoveTo and ClosePath to be removed"),
}
}
}
impl FromIterator<PathEl> for BezPath {
fn from_iter<T: IntoIterator<Item = PathEl>>(iter: T) -> Self {
let el_vec: Vec<_> = iter.into_iter().collect();
BezPath::from_vec(el_vec)
}
}
impl<'a> IntoIterator for &'a BezPath {
type Item = PathEl;
type IntoIter = core::iter::Cloned<core::slice::Iter<'a, PathEl>>;
fn into_iter(self) -> Self::IntoIter {
self.elements().iter().cloned()
}
}
impl IntoIterator for BezPath {
type Item = PathEl;
type IntoIter = alloc::vec::IntoIter<PathEl>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Extend<PathEl> for BezPath {
fn extend<I: IntoIterator<Item = PathEl>>(&mut self, iter: I) {
self.0.extend(iter);
}
}
const TO_QUAD_TOL: f64 = 0.1;
pub fn flatten(
path: impl IntoIterator<Item = PathEl>,
tolerance: f64,
mut callback: impl FnMut(PathEl),
) {
let sqrt_tol = tolerance.sqrt();
let mut last_pt = None;
let mut quad_buf = Vec::new();
for el in path {
match el {
PathEl::MoveTo(p) => {
last_pt = Some(p);
callback(PathEl::MoveTo(p));
}
PathEl::LineTo(p) => {
last_pt = Some(p);
callback(PathEl::LineTo(p));
}
PathEl::QuadTo(p1, p2) => {
if let Some(p0) = last_pt {
let q = QuadBez::new(p0, p1, p2);
let params = q.estimate_subdiv(sqrt_tol);
let n = ((0.5 * params.val / sqrt_tol).ceil() as usize).max(1);
let step = 1.0 / (n as f64);
for i in 1..n {
let u = (i as f64) * step;
let t = q.determine_subdiv_t(¶ms, u);
let p = q.eval(t);
callback(PathEl::LineTo(p));
}
callback(PathEl::LineTo(p2));
}
last_pt = Some(p2);
}
PathEl::CurveTo(p1, p2, p3) => {
if let Some(p0) = last_pt {
let c = CubicBez::new(p0, p1, p2, p3);
let iter = c.to_quads(tolerance * TO_QUAD_TOL);
quad_buf.clear();
quad_buf.reserve(iter.size_hint().0);
let sqrt_remain_tol = sqrt_tol * (1.0 - TO_QUAD_TOL).sqrt();
let mut sum = 0.0;
for (_, _, q) in iter {
let params = q.estimate_subdiv(sqrt_remain_tol);
sum += params.val;
quad_buf.push((q, params));
}
let n = ((0.5 * sum / sqrt_remain_tol).ceil() as usize).max(1);
let step = sum / (n as f64);
let mut i = 1;
let mut val_sum = 0.0;
for (q, params) in &quad_buf {
let mut target = (i as f64) * step;
let recip_val = params.val.recip();
while target < val_sum + params.val {
let u = (target - val_sum) * recip_val;
let t = q.determine_subdiv_t(params, u);
let p = q.eval(t);
callback(PathEl::LineTo(p));
i += 1;
if i == n + 1 {
break;
}
target = (i as f64) * step;
}
val_sum += params.val;
}
callback(PathEl::LineTo(p3));
}
last_pt = Some(p3);
}
PathEl::ClosePath => {
last_pt = None;
callback(PathEl::ClosePath);
}
}
}
}
impl Mul<PathEl> for Affine {
type Output = PathEl;
#[inline(always)]
fn mul(self, other: PathEl) -> PathEl {
match other {
PathEl::MoveTo(p) => PathEl::MoveTo(self * p),
PathEl::LineTo(p) => PathEl::LineTo(self * p),
PathEl::QuadTo(p1, p2) => PathEl::QuadTo(self * p1, self * p2),
PathEl::CurveTo(p1, p2, p3) => PathEl::CurveTo(self * p1, self * p2, self * p3),
PathEl::ClosePath => PathEl::ClosePath,
}
}
}
impl Mul<PathSeg> for Affine {
type Output = PathSeg;
fn mul(self, other: PathSeg) -> PathSeg {
match other {
PathSeg::Line(line) => PathSeg::Line(self * line),
PathSeg::Quad(quad) => PathSeg::Quad(self * quad),
PathSeg::Cubic(cubic) => PathSeg::Cubic(self * cubic),
}
}
}
impl Mul<BezPath> for Affine {
type Output = BezPath;
fn mul(self, other: BezPath) -> BezPath {
BezPath(other.0.iter().map(|&el| self * el).collect())
}
}
impl Mul<&BezPath> for Affine {
type Output = BezPath;
fn mul(self, other: &BezPath) -> BezPath {
BezPath(other.0.iter().map(|&el| self * el).collect())
}
}
impl Mul<PathEl> for TranslateScale {
type Output = PathEl;
fn mul(self, other: PathEl) -> PathEl {
match other {
PathEl::MoveTo(p) => PathEl::MoveTo(self * p),
PathEl::LineTo(p) => PathEl::LineTo(self * p),
PathEl::QuadTo(p1, p2) => PathEl::QuadTo(self * p1, self * p2),
PathEl::CurveTo(p1, p2, p3) => PathEl::CurveTo(self * p1, self * p2, self * p3),
PathEl::ClosePath => PathEl::ClosePath,
}
}
}
impl Mul<PathSeg> for TranslateScale {
type Output = PathSeg;
fn mul(self, other: PathSeg) -> PathSeg {
match other {
PathSeg::Line(line) => PathSeg::Line(self * line),
PathSeg::Quad(quad) => PathSeg::Quad(self * quad),
PathSeg::Cubic(cubic) => PathSeg::Cubic(self * cubic),
}
}
}
impl Mul<BezPath> for TranslateScale {
type Output = BezPath;
fn mul(self, other: BezPath) -> BezPath {
BezPath(other.0.iter().map(|&el| self * el).collect())
}
}
impl Mul<&BezPath> for TranslateScale {
type Output = BezPath;
fn mul(self, other: &BezPath) -> BezPath {
BezPath(other.0.iter().map(|&el| self * el).collect())
}
}
pub(crate) fn close_subpaths<I>(elements: I) -> CloseSubpaths<I::IntoIter>
where
I: IntoIterator<Item = PathEl>,
{
CloseSubpaths {
elements: elements.into_iter(),
state: CloseSubpathState::Start,
}
}
#[derive(Clone)]
pub(crate) struct CloseSubpaths<I: Iterator<Item = PathEl>> {
elements: I,
state: CloseSubpathState,
}
#[derive(Clone)]
enum CloseSubpathState {
Start,
InSubpath,
ClosedLast,
PendingMoveTo(Point),
}
impl<I: Iterator<Item = PathEl>> Iterator for CloseSubpaths<I> {
type Item = PathEl;
fn next(&mut self) -> Option<PathEl> {
match self.state {
CloseSubpathState::Start => {
let el = self.elements.next();
if !matches!(el, Some(PathEl::ClosePath) | None) {
self.state = CloseSubpathState::InSubpath;
}
el
}
CloseSubpathState::InSubpath => {
let el = self.elements.next();
match el {
None => {
self.state = CloseSubpathState::ClosedLast;
Some(PathEl::ClosePath)
}
Some(PathEl::MoveTo(point)) => {
self.state = CloseSubpathState::PendingMoveTo(point);
Some(PathEl::ClosePath)
}
Some(PathEl::ClosePath) => {
self.state = CloseSubpathState::Start;
el
}
_ => el,
}
}
CloseSubpathState::ClosedLast => None,
CloseSubpathState::PendingMoveTo(point) => {
self.state = CloseSubpathState::Start;
Some(PathEl::MoveTo(point))
}
}
}
}
pub fn segments<I>(elements: I) -> Segments<I::IntoIter>
where
I: IntoIterator<Item = PathEl>,
{
Segments {
elements: elements.into_iter(),
start_last: None,
}
}
#[derive(Clone)]
pub struct Segments<I: Iterator<Item = PathEl>> {
elements: I,
start_last: Option<(Point, Point)>,
}
impl<I: Iterator<Item = PathEl>> Iterator for Segments<I> {
type Item = PathSeg;
#[inline]
fn next(&mut self) -> Option<PathSeg> {
for el in &mut self.elements {
let (start, last) = self.start_last.get_or_insert_with(|| {
let point = match el {
PathEl::MoveTo(p) => p,
PathEl::LineTo(p) => p,
PathEl::QuadTo(_, p2) => p2,
PathEl::CurveTo(_, _, p3) => p3,
PathEl::ClosePath => panic!("Can't start a segment on a ClosePath"),
};
(point, point)
});
return Some(match el {
PathEl::MoveTo(p) => {
*start = p;
*last = p;
continue;
}
PathEl::LineTo(p) => PathSeg::Line(Line::new(mem::replace(last, p), p)),
PathEl::QuadTo(p1, p2) => {
PathSeg::Quad(QuadBez::new(mem::replace(last, p2), p1, p2))
}
PathEl::CurveTo(p1, p2, p3) => {
PathSeg::Cubic(CubicBez::new(mem::replace(last, p3), p1, p2, p3))
}
PathEl::ClosePath => {
if *last != *start {
PathSeg::Line(Line::new(mem::replace(last, *start), *start))
} else {
continue;
}
}
});
}
None
}
}
impl<I: Iterator<Item = PathEl>> Segments<I> {
pub(crate) fn perimeter(self, accuracy: f64) -> f64 {
self.map(|seg| seg.arclen(accuracy)).sum()
}
pub(crate) fn area(self) -> f64 {
self.map(|seg| seg.signed_area()).sum()
}
pub(crate) fn winding(self, p: Point) -> i32 {
self.map(|seg| seg.winding(p)).sum()
}
pub(crate) fn bounding_box(self) -> Rect {
let mut bbox: Option<Rect> = None;
for seg in self {
let seg_bb = ParamCurveExtrema::bounding_box(&seg);
if let Some(bb) = bbox {
bbox = Some(bb.union(seg_bb));
} else {
bbox = Some(seg_bb);
}
}
bbox.unwrap_or_default()
}
}
impl ParamCurve for PathSeg {
fn eval(&self, t: f64) -> Point {
match *self {
PathSeg::Line(line) => line.eval(t),
PathSeg::Quad(quad) => quad.eval(t),
PathSeg::Cubic(cubic) => cubic.eval(t),
}
}
fn subsegment(&self, range: Range<f64>) -> PathSeg {
match *self {
PathSeg::Line(line) => PathSeg::Line(line.subsegment(range)),
PathSeg::Quad(quad) => PathSeg::Quad(quad.subsegment(range)),
PathSeg::Cubic(cubic) => PathSeg::Cubic(cubic.subsegment(range)),
}
}
fn start(&self) -> Point {
match *self {
PathSeg::Line(line) => line.start(),
PathSeg::Quad(quad) => quad.start(),
PathSeg::Cubic(cubic) => cubic.start(),
}
}
fn end(&self) -> Point {
match *self {
PathSeg::Line(line) => line.end(),
PathSeg::Quad(quad) => quad.end(),
PathSeg::Cubic(cubic) => cubic.end(),
}
}
}
impl ParamCurveArclen for PathSeg {
fn arclen(&self, accuracy: f64) -> f64 {
match *self {
PathSeg::Line(line) => line.arclen(accuracy),
PathSeg::Quad(quad) => quad.arclen(accuracy),
PathSeg::Cubic(cubic) => cubic.arclen(accuracy),
}
}
fn inv_arclen(&self, arclen: f64, accuracy: f64) -> f64 {
match *self {
PathSeg::Line(line) => line.inv_arclen(arclen, accuracy),
PathSeg::Quad(quad) => quad.inv_arclen(arclen, accuracy),
PathSeg::Cubic(cubic) => cubic.inv_arclen(arclen, accuracy),
}
}
}
impl ParamCurveArea for PathSeg {
fn signed_area(&self) -> f64 {
match *self {
PathSeg::Line(line) => line.signed_area(),
PathSeg::Quad(quad) => quad.signed_area(),
PathSeg::Cubic(cubic) => cubic.signed_area(),
}
}
}
impl ParamCurveNearest for PathSeg {
fn nearest(&self, p: Point, accuracy: f64) -> Nearest {
match *self {
PathSeg::Line(line) => line.nearest(p, accuracy),
PathSeg::Quad(quad) => quad.nearest(p, accuracy),
PathSeg::Cubic(cubic) => cubic.nearest(p, accuracy),
}
}
}
impl ParamCurveExtrema for PathSeg {
fn extrema(&self) -> ArrayVec<f64, MAX_EXTREMA> {
match *self {
PathSeg::Line(line) => line.extrema(),
PathSeg::Quad(quad) => quad.extrema(),
PathSeg::Cubic(cubic) => cubic.extrema(),
}
}
}
impl PathSeg {
pub fn as_path_el(&self) -> PathEl {
match self {
PathSeg::Line(line) => PathEl::LineTo(line.p1),
PathSeg::Quad(q) => PathEl::QuadTo(q.p1, q.p2),
PathSeg::Cubic(c) => PathEl::CurveTo(c.p1, c.p2, c.p3),
}
}
pub fn reverse(&self) -> PathSeg {
match self {
PathSeg::Line(Line { p0, p1 }) => PathSeg::Line(Line::new(*p1, *p0)),
PathSeg::Quad(q) => PathSeg::Quad(QuadBez::new(q.p2, q.p1, q.p0)),
PathSeg::Cubic(c) => PathSeg::Cubic(CubicBez::new(c.p3, c.p2, c.p1, c.p0)),
}
}
pub fn to_cubic(&self) -> CubicBez {
match *self {
PathSeg::Line(Line { p0, p1 }) => CubicBez::new(p0, p0, p1, p1),
PathSeg::Cubic(c) => c,
PathSeg::Quad(q) => q.raise(),
}
}
fn winding_inner(&self, p: Point) -> i32 {
let start = self.start();
let end = self.end();
let sign = if end.y > start.y {
if p.y < start.y || p.y >= end.y {
return 0;
}
-1
} else if end.y < start.y {
if p.y < end.y || p.y >= start.y {
return 0;
}
1
} else {
return 0;
};
match *self {
PathSeg::Line(_line) => {
if p.x < start.x.min(end.x) {
return 0;
}
if p.x >= start.x.max(end.x) {
return sign;
}
let a = end.y - start.y;
let b = start.x - end.x;
let c = a * start.x + b * start.y;
if (a * p.x + b * p.y - c) * (sign as f64) <= 0.0 {
sign
} else {
0
}
}
PathSeg::Quad(quad) => {
let p1 = quad.p1;
if p.x < start.x.min(end.x).min(p1.x) {
return 0;
}
if p.x >= start.x.max(end.x).max(p1.x) {
return sign;
}
let t = quad.solve_monotonic_for_y(p.y);
let x = quad.eval(t).x;
if p.x >= x { sign } else { 0 }
}
PathSeg::Cubic(cubic) => {
let p1 = cubic.p1;
let p2 = cubic.p2;
if p.x < start.x.min(end.x).min(p1.x).min(p2.x) {
return 0;
}
if p.x >= start.x.max(end.x).max(p1.x).max(p2.x) {
return sign;
}
let t = cubic.solve_monotonic_for_y(p.y);
let x = cubic.eval(t).x;
if p.x >= x { sign } else { 0 }
}
}
}
fn winding(&self, p: Point) -> i32 {
self.extrema_ranges()
.into_iter()
.map(|range| self.subsegment(range).winding_inner(p))
.sum()
}
pub fn intersect_line(&self, line: Line) -> ArrayVec<LineIntersection, 3> {
const EPSILON: f64 = 1e-9;
let p0 = line.p0;
let p1 = line.p1;
let dx = p1.x - p0.x;
let dy = p1.y - p0.y;
let mut result = ArrayVec::new();
match self {
PathSeg::Line(l) => {
let det = dx * (l.p1.y - l.p0.y) - dy * (l.p1.x - l.p0.x);
if det.abs() < EPSILON {
return result;
}
let t = dx * (p0.y - l.p0.y) - dy * (p0.x - l.p0.x);
let t = t / det;
if (-EPSILON..=(1.0 + EPSILON)).contains(&t) {
let u =
(l.p0.x - p0.x) * (l.p1.y - l.p0.y) - (l.p0.y - p0.y) * (l.p1.x - l.p0.x);
let u = u / det;
if (0.0..=1.0).contains(&u) {
result.push(LineIntersection::new(u, t));
}
}
}
PathSeg::Quad(q) => {
let (px0, px1, px2) = quadratic_bez_coefs(q.p0.x, q.p1.x, q.p2.x);
let (py0, py1, py2) = quadratic_bez_coefs(q.p0.y, q.p1.y, q.p2.y);
let c0 = dy * (px0 - p0.x) - dx * (py0 - p0.y);
let c1 = dy * px1 - dx * py1;
let c2 = dy * px2 - dx * py2;
let invlen2 = (dx * dx + dy * dy).recip();
for t in solve_quadratic(c0, c1, c2) {
if (-EPSILON..=(1.0 + EPSILON)).contains(&t) {
let x = px0 + t * px1 + t * t * px2;
let y = py0 + t * py1 + t * t * py2;
let u = ((x - p0.x) * dx + (y - p0.y) * dy) * invlen2;
if (0.0..=1.0).contains(&u) {
result.push(LineIntersection::new(u, t));
}
}
}
}
PathSeg::Cubic(c) => {
let (px0, px1, px2, px3) = cubic_bez_coefs(c.p0.x, c.p1.x, c.p2.x, c.p3.x);
let (py0, py1, py2, py3) = cubic_bez_coefs(c.p0.y, c.p1.y, c.p2.y, c.p3.y);
let c0 = dy * (px0 - p0.x) - dx * (py0 - p0.y);
let c1 = dy * px1 - dx * py1;
let c2 = dy * px2 - dx * py2;
let c3 = dy * px3 - dx * py3;
let invlen2 = (dx * dx + dy * dy).recip();
for t in solve_cubic(c0, c1, c2, c3) {
if (-EPSILON..=(1.0 + EPSILON)).contains(&t) {
let x = px0 + t * px1 + t * t * px2 + t * t * t * px3;
let y = py0 + t * py1 + t * t * py2 + t * t * t * py3;
let u = ((x - p0.x) * dx + (y - p0.y) * dy) * invlen2;
if (0.0..=1.0).contains(&u) {
result.push(LineIntersection::new(u, t));
}
}
}
}
}
result
}
#[inline]
pub fn is_finite(&self) -> bool {
match self {
PathSeg::Line(line) => line.is_finite(),
PathSeg::Quad(quad_bez) => quad_bez.is_finite(),
PathSeg::Cubic(cubic_bez) => cubic_bez.is_finite(),
}
}
#[inline]
pub fn is_nan(&self) -> bool {
match self {
PathSeg::Line(line) => line.is_nan(),
PathSeg::Quad(quad_bez) => quad_bez.is_nan(),
PathSeg::Cubic(cubic_bez) => cubic_bez.is_nan(),
}
}
#[inline]
fn as_vec2_vec(&self) -> ArrayVec<Vec2, 4> {
let mut a = ArrayVec::new();
match self {
PathSeg::Line(l) => {
a.push(l.p0.to_vec2());
a.push(l.p1.to_vec2());
}
PathSeg::Quad(q) => {
a.push(q.p0.to_vec2());
a.push(q.p1.to_vec2());
a.push(q.p2.to_vec2());
}
PathSeg::Cubic(c) => {
a.push(c.p0.to_vec2());
a.push(c.p1.to_vec2());
a.push(c.p2.to_vec2());
a.push(c.p3.to_vec2());
}
}
a
}
pub fn min_dist(&self, other: PathSeg, accuracy: f64) -> MinDistance {
let (distance, t1, t2) = crate::mindist::min_dist_param(
&self.as_vec2_vec(),
&other.as_vec2_vec(),
(0.0, 1.0),
(0.0, 1.0),
accuracy,
None,
);
MinDistance {
distance: distance.sqrt(),
t1,
t2,
}
}
pub(crate) fn tangents(&self) -> (Vec2, Vec2) {
const EPS: f64 = 1e-12;
match self {
PathSeg::Line(l) => {
let d = l.p1 - l.p0;
(d, d)
}
PathSeg::Quad(q) => {
let d01 = q.p1 - q.p0;
let d0 = if d01.hypot2() > EPS { d01 } else { q.p2 - q.p0 };
let d12 = q.p2 - q.p1;
let d1 = if d12.hypot2() > EPS { d12 } else { q.p2 - q.p0 };
(d0, d1)
}
PathSeg::Cubic(c) => {
let d01 = c.p1 - c.p0;
let d0 = if d01.hypot2() > EPS {
d01
} else {
let d02 = c.p2 - c.p0;
if d02.hypot2() > EPS { d02 } else { c.p3 - c.p0 }
};
let d23 = c.p3 - c.p2;
let d1 = if d23.hypot2() > EPS {
d23
} else {
let d13 = c.p3 - c.p1;
if d13.hypot2() > EPS { d13 } else { c.p3 - c.p0 }
};
(d0, d1)
}
}
}
}
impl LineIntersection {
#[inline(always)]
fn new(line_t: f64, segment_t: f64) -> Self {
LineIntersection { line_t, segment_t }
}
#[inline]
pub fn is_finite(self) -> bool {
self.line_t.is_finite() && self.segment_t.is_finite()
}
#[inline]
pub fn is_nan(self) -> bool {
self.line_t.is_nan() || self.segment_t.is_nan()
}
}
fn quadratic_bez_coefs(x0: f64, x1: f64, x2: f64) -> (f64, f64, f64) {
let p0 = x0;
let p1 = 2.0 * x1 - 2.0 * x0;
let p2 = x2 - 2.0 * x1 + x0;
(p0, p1, p2)
}
fn cubic_bez_coefs(x0: f64, x1: f64, x2: f64, x3: f64) -> (f64, f64, f64, f64) {
let p0 = x0;
let p1 = 3.0 * x1 - 3.0 * x0;
let p2 = 3.0 * x2 - 6.0 * x1 + 3.0 * x0;
let p3 = x3 - 3.0 * x2 + 3.0 * x1 - x0;
(p0, p1, p2, p3)
}
impl From<CubicBez> for PathSeg {
#[inline(always)]
fn from(cubic_bez: CubicBez) -> PathSeg {
PathSeg::Cubic(cubic_bez)
}
}
impl From<Line> for PathSeg {
#[inline(always)]
fn from(line: Line) -> PathSeg {
PathSeg::Line(line)
}
}
impl From<QuadBez> for PathSeg {
#[inline(always)]
fn from(quad_bez: QuadBez) -> PathSeg {
PathSeg::Quad(quad_bez)
}
}
impl Shape for BezPath {
type PathElementsIter<'iter> = core::iter::Copied<core::slice::Iter<'iter, PathEl>>;
fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter<'_> {
self.0.iter().copied()
}
fn to_path(&self, _tolerance: f64) -> BezPath {
self.clone()
}
#[inline(always)]
fn into_path(self, _tolerance: f64) -> BezPath {
self
}
fn area(&self) -> f64 {
self.elements().area()
}
fn perimeter(&self, accuracy: f64) -> f64 {
self.elements().perimeter(accuracy)
}
fn winding(&self, pt: Point) -> i32 {
self.elements().winding(pt)
}
fn bounding_box(&self) -> Rect {
self.elements().bounding_box()
}
#[inline(always)]
fn as_path_slice(&self) -> Option<&[PathEl]> {
Some(&self.0)
}
}
impl PathEl {
#[inline]
pub fn is_finite(&self) -> bool {
match self {
PathEl::MoveTo(p) => p.is_finite(),
PathEl::LineTo(p) => p.is_finite(),
PathEl::QuadTo(p, p2) => p.is_finite() && p2.is_finite(),
PathEl::CurveTo(p, p2, p3) => p.is_finite() && p2.is_finite() && p3.is_finite(),
PathEl::ClosePath => true,
}
}
#[inline]
pub fn is_nan(&self) -> bool {
match self {
PathEl::MoveTo(p) => p.is_nan(),
PathEl::LineTo(p) => p.is_nan(),
PathEl::QuadTo(p, p2) => p.is_nan() || p2.is_nan(),
PathEl::CurveTo(p, p2, p3) => p.is_nan() || p2.is_nan() || p3.is_nan(),
PathEl::ClosePath => false,
}
}
pub fn end_point(&self) -> Option<Point> {
match self {
PathEl::MoveTo(p) => Some(*p),
PathEl::LineTo(p1) => Some(*p1),
PathEl::QuadTo(_, p2) => Some(*p2),
PathEl::CurveTo(_, _, p3) => Some(*p3),
PathEl::ClosePath => None,
}
}
}
impl<'a> Shape for &'a [PathEl] {
type PathElementsIter<'iter>
= core::iter::Copied<core::slice::Iter<'a, PathEl>>
where
'a: 'iter;
#[inline]
fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter<'_> {
self.iter().copied()
}
fn to_path(&self, _tolerance: f64) -> BezPath {
BezPath::from_vec(self.to_vec())
}
fn area(&self) -> f64 {
segments(self.iter().copied()).area()
}
fn perimeter(&self, accuracy: f64) -> f64 {
segments(self.iter().copied()).perimeter(accuracy)
}
fn winding(&self, pt: Point) -> i32 {
segments(self.iter().copied()).winding(pt)
}
fn bounding_box(&self) -> Rect {
segments(self.iter().copied()).bounding_box()
}
#[inline(always)]
fn as_path_slice(&self) -> Option<&[PathEl]> {
Some(self)
}
}
impl<const N: usize> Shape for [PathEl; N] {
type PathElementsIter<'iter> = core::iter::Copied<core::slice::Iter<'iter, PathEl>>;
#[inline]
fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter<'_> {
self.iter().copied()
}
fn to_path(&self, _tolerance: f64) -> BezPath {
BezPath::from_vec(self.to_vec())
}
fn area(&self) -> f64 {
segments(self.iter().copied()).area()
}
fn perimeter(&self, accuracy: f64) -> f64 {
segments(self.iter().copied()).perimeter(accuracy)
}
fn winding(&self, pt: Point) -> i32 {
segments(self.iter().copied()).winding(pt)
}
fn bounding_box(&self) -> Rect {
segments(self.iter().copied()).bounding_box()
}
#[inline(always)]
fn as_path_slice(&self) -> Option<&[PathEl]> {
Some(self)
}
}
pub struct PathSegIter {
seg: PathSeg,
ix: usize,
}
impl Shape for PathSeg {
type PathElementsIter<'iter> = PathSegIter;
#[inline(always)]
fn path_elements(&self, _tolerance: f64) -> PathSegIter {
PathSegIter { seg: *self, ix: 0 }
}
fn area(&self) -> f64 {
self.signed_area()
}
#[inline]
fn perimeter(&self, accuracy: f64) -> f64 {
self.arclen(accuracy)
}
#[inline(always)]
fn winding(&self, _pt: Point) -> i32 {
0
}
#[inline]
fn bounding_box(&self) -> Rect {
ParamCurveExtrema::bounding_box(self)
}
fn as_line(&self) -> Option<Line> {
if let PathSeg::Line(line) = self {
Some(*line)
} else {
None
}
}
}
impl Iterator for PathSegIter {
type Item = PathEl;
fn next(&mut self) -> Option<PathEl> {
self.ix += 1;
match (self.ix, self.seg) {
(1, PathSeg::Line(seg)) => Some(PathEl::MoveTo(seg.p0)),
(1, PathSeg::Quad(seg)) => Some(PathEl::MoveTo(seg.p0)),
(1, PathSeg::Cubic(seg)) => Some(PathEl::MoveTo(seg.p0)),
(2, PathSeg::Line(seg)) => Some(PathEl::LineTo(seg.p1)),
(2, PathSeg::Quad(seg)) => Some(PathEl::QuadTo(seg.p1, seg.p2)),
(2, PathSeg::Cubic(seg)) => Some(PathEl::CurveTo(seg.p1, seg.p2, seg.p3)),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::{Circle, DEFAULT_ACCURACY};
use super::*;
fn assert_approx_eq(x: f64, y: f64) {
assert!((x - y).abs() < 1e-8, "{x} != {y}");
}
#[test]
#[should_panic(expected = "uninitialized subpath")]
fn test_elements_to_segments_starts_on_closepath() {
let mut path = BezPath::new();
path.close_path();
path.segments().next();
}
#[test]
fn test_elements_to_segments_closepath_refers_to_last_moveto() {
let mut path = BezPath::new();
path.move_to((5.0, 5.0));
path.line_to((15.0, 15.0));
path.move_to((10.0, 10.0));
path.line_to((15.0, 15.0));
path.close_path();
assert_eq!(
path.segments().collect::<Vec<_>>().last(),
Some(&Line::new((15.0, 15.0), (10.0, 10.0)).into()),
);
}
#[test]
#[should_panic(expected = "uninitialized subpath")]
fn test_must_not_start_on_quad() {
let mut path = BezPath::new();
path.quad_to((5.0, 5.0), (10.0, 10.0));
path.line_to((15.0, 15.0));
path.close_path();
}
#[test]
fn test_intersect_line() {
let h_line = Line::new((0.0, 0.0), (100.0, 0.0));
let v_line = Line::new((10.0, -10.0), (10.0, 10.0));
let intersection = PathSeg::Line(h_line).intersect_line(v_line)[0];
assert_approx_eq(intersection.segment_t, 0.1);
assert_approx_eq(intersection.line_t, 0.5);
let v_line = Line::new((-10.0, -10.0), (-10.0, 10.0));
assert!(PathSeg::Line(h_line).intersect_line(v_line).is_empty());
let v_line = Line::new((10.0, 10.0), (10.0, 20.0));
assert!(PathSeg::Line(h_line).intersect_line(v_line).is_empty());
}
#[test]
fn test_intersect_qad() {
let q = QuadBez::new((0.0, -10.0), (10.0, 20.0), (20.0, -10.0));
let v_line = Line::new((10.0, -10.0), (10.0, 10.0));
assert_eq!(PathSeg::Quad(q).intersect_line(v_line).len(), 1);
let intersection = PathSeg::Quad(q).intersect_line(v_line)[0];
assert_approx_eq(intersection.segment_t, 0.5);
assert_approx_eq(intersection.line_t, 0.75);
let h_line = Line::new((0.0, 0.0), (100.0, 0.0));
assert_eq!(PathSeg::Quad(q).intersect_line(h_line).len(), 2);
}
#[test]
fn test_intersect_cubic() {
let c = CubicBez::new((0.0, -10.0), (10.0, 20.0), (20.0, -20.0), (30.0, 10.0));
let v_line = Line::new((10.0, -10.0), (10.0, 10.0));
assert_eq!(PathSeg::Cubic(c).intersect_line(v_line).len(), 1);
let intersection = PathSeg::Cubic(c).intersect_line(v_line)[0];
assert_approx_eq(intersection.segment_t, 0.333333333);
assert_approx_eq(intersection.line_t, 0.592592592);
let h_line = Line::new((0.0, 0.0), (100.0, 0.0));
assert_eq!(PathSeg::Cubic(c).intersect_line(h_line).len(), 3);
}
#[test]
fn test_contains() {
let mut path = BezPath::new();
path.move_to((0.0, 0.0));
path.line_to((1.0, 1.0));
path.line_to((2.0, 0.0));
path.close_path();
assert_eq!(path.winding(Point::new(1.0, 0.5)), -1);
assert!(path.contains(Point::new(1.0, 0.5)));
}
#[test]
fn test_get_seg() {
let circle = Circle::new((10.0, 10.0), 2.0).to_path(DEFAULT_ACCURACY);
let segments = circle.path_segments(DEFAULT_ACCURACY).collect::<Vec<_>>();
let get_segs = (1..usize::MAX)
.map_while(|i| circle.get_seg(i))
.collect::<Vec<_>>();
assert_eq!(segments, get_segs);
}
#[test]
fn test_control_box() {
let path = BezPath::from_svg("M200,300 C50,50 350,50 200,300").unwrap();
assert_eq!(Rect::new(50.0, 50.0, 350.0, 300.0), path.control_box());
assert!(path.control_box().area() > path.bounding_box().area());
}
#[test]
fn test_subpaths() {
let path = BezPath::from_svg("M10,10 L0,10 L0,0 L10,0 Z M100,100 M30,0 Q35,10,40,0 L30,0")
.unwrap();
assert_eq!(
vec![
BezPath::from_svg("M10,10 L0,10 L0,0 L10,0 Z").unwrap(),
BezPath::from_svg("M100,100").unwrap(),
BezPath::from_svg("M30,0 Q35,10,40,0 L30,0").unwrap(),
],
path.subpaths()
.map(|sp| BezPath::from_vec(sp.to_vec()))
.collect::<Vec<_>>()
);
}
#[test]
fn test_reverse_unclosed() {
let path = BezPath::from_svg("M10,10 Q40,40 60,10 L100,10 C125,10 150,50 125,60").unwrap();
let reversed = path.reverse_subpaths();
assert_eq!(
"M125,60 C150,50 125,10 100,10 L60,10 Q40,40 10,10",
reversed.to_svg()
);
}
#[test]
fn test_reverse_closed_triangle() {
let path = BezPath::from_svg("M100,100 L150,200 L50,200 Z").unwrap();
let reversed = path.reverse_subpaths();
assert_eq!("M50,200 L150,200 L100,100 Z", reversed.to_svg());
}
#[test]
fn test_reverse_closed_shape() {
let path = BezPath::from_svg(
"M125,100 Q200,150 175,300 C150,150 50,150 25,300 Q0,150 75,100 L100,50 Z",
)
.unwrap();
let reversed = path.reverse_subpaths();
assert_eq!(
"M100,50 L75,100 Q0,150 25,300 C50,150 150,150 175,300 Q200,150 125,100 Z",
reversed.to_svg()
);
}
#[test]
fn test_reverse_multiple_subpaths() {
let svg = "M10,10 Q40,40 60,10 L100,10 C125,10 150,50 125,60 M100,100 L150,200 L50,200 Z M125,100 Q200,150 175,300 C150,150 50,150 25,300 Q0,150 75,100 L100,50 Z";
let expected_svg = "M125,60 C150,50 125,10 100,10 L60,10 Q40,40 10,10 M50,200 L150,200 L100,100 Z M100,50 L75,100 Q0,150 25,300 C50,150 150,150 175,300 Q200,150 125,100 Z";
let path = BezPath::from_svg(svg).unwrap();
let reversed = path.reverse_subpaths();
assert_eq!(expected_svg, reversed.to_svg());
}
#[test]
fn test_reverse_lines() {
let mut path = BezPath::new();
path.move_to((0.0, 0.0));
path.line_to((1.0, 1.0));
path.line_to((2.0, 2.0));
path.line_to((3.0, 3.0));
path.close_path();
let rev = path.reverse_subpaths();
assert_eq!("M3,3 L2,2 L1,1 L0,0 Z", rev.to_svg());
}
#[test]
fn test_reverse_multiple_moves() {
reverse_test_helper(
vec![
PathEl::MoveTo((2.0, 2.0).into()),
PathEl::MoveTo((3.0, 3.0).into()),
PathEl::ClosePath,
PathEl::MoveTo((4.0, 4.0).into()),
],
vec![
PathEl::MoveTo((2.0, 2.0).into()),
PathEl::MoveTo((3.0, 3.0).into()),
PathEl::ClosePath,
PathEl::MoveTo((4.0, 4.0).into()),
],
);
}
#[test]
fn test_reverse_closed_last_line_not_on_move() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
PathEl::LineTo((2.0, 2.0).into()),
PathEl::LineTo((3.0, 3.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((3.0, 3.0).into()),
PathEl::LineTo((2.0, 2.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
PathEl::LineTo((0.0, 0.0).into()), PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_last_line_overlaps_move() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
PathEl::LineTo((2.0, 2.0).into()),
PathEl::LineTo((0.0, 0.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((2.0, 2.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
PathEl::LineTo((0.0, 0.0).into()), PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_duplicate_line_following_move() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((0.0, 0.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
PathEl::LineTo((2.0, 2.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((2.0, 2.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
PathEl::LineTo((0.0, 0.0).into()), PathEl::LineTo((0.0, 0.0).into()),
PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_two_lines() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((1.0, 1.0).into()),
PathEl::LineTo((0.0, 0.0).into()), PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_last_curve_overlaps_move() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::CurveTo((1.0, 1.0).into(), (2.0, 2.0).into(), (3.0, 3.0).into()),
PathEl::CurveTo((4.0, 4.0).into(), (5.0, 5.0).into(), (0.0, 0.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((0.0, 0.0).into()), PathEl::CurveTo((5.0, 5.0).into(), (4.0, 4.0).into(), (3.0, 3.0).into()),
PathEl::CurveTo((2.0, 2.0).into(), (1.0, 1.0).into(), (0.0, 0.0).into()),
PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_last_curve_not_on_move() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::CurveTo((1.0, 1.0).into(), (2.0, 2.0).into(), (3.0, 3.0).into()),
PathEl::CurveTo((4.0, 4.0).into(), (5.0, 5.0).into(), (6.0, 6.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((6.0, 6.0).into()), PathEl::CurveTo((5.0, 5.0).into(), (4.0, 4.0).into(), (3.0, 3.0).into()),
PathEl::CurveTo((2.0, 2.0).into(), (1.0, 1.0).into(), (0.0, 0.0).into()),
PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_line_curve_line() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((1.0, 1.0).into()), PathEl::CurveTo((2.0, 2.0).into(), (3.0, 3.0).into(), (4.0, 4.0).into()),
PathEl::CurveTo((5.0, 5.0).into(), (6.0, 6.0).into(), (7.0, 7.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((7.0, 7.0).into()),
PathEl::CurveTo((6.0, 6.0).into(), (5.0, 5.0).into(), (4.0, 4.0).into()),
PathEl::CurveTo((3.0, 3.0).into(), (2.0, 2.0).into(), (1.0, 1.0).into()),
PathEl::LineTo((0.0, 0.0).into()), PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_last_quad_overlaps_move() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::QuadTo((1.0, 1.0).into(), (2.0, 2.0).into()),
PathEl::QuadTo((3.0, 3.0).into(), (0.0, 0.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((0.0, 0.0).into()), PathEl::QuadTo((3.0, 3.0).into(), (2.0, 2.0).into()),
PathEl::QuadTo((1.0, 1.0).into(), (0.0, 0.0).into()),
PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_last_quad_not_on_move() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::QuadTo((1.0, 1.0).into(), (2.0, 2.0).into()),
PathEl::QuadTo((3.0, 3.0).into(), (4.0, 4.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((4.0, 4.0).into()), PathEl::QuadTo((3.0, 3.0).into(), (2.0, 2.0).into()),
PathEl::QuadTo((1.0, 1.0).into(), (0.0, 0.0).into()),
PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_closed_line_quad_line() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((1.0, 1.0).into()), PathEl::QuadTo((2.0, 2.0).into(), (3.0, 3.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((3.0, 3.0).into()),
PathEl::QuadTo((2.0, 2.0).into(), (1.0, 1.0).into()),
PathEl::LineTo((0.0, 0.0).into()), PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_empty() {
reverse_test_helper(vec![], vec![]);
}
#[test]
fn test_reverse_single_point() {
reverse_test_helper(
vec![PathEl::MoveTo((0.0, 0.0).into())],
vec![PathEl::MoveTo((0.0, 0.0).into())],
);
}
#[test]
fn test_reverse_single_point_closed() {
reverse_test_helper(
vec![PathEl::MoveTo((0.0, 0.0).into()), PathEl::ClosePath],
vec![PathEl::MoveTo((0.0, 0.0).into()), PathEl::ClosePath],
);
}
#[test]
fn test_reverse_single_line_open() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
],
vec![
PathEl::MoveTo((1.0, 1.0).into()),
PathEl::LineTo((0.0, 0.0).into()),
],
);
}
#[test]
fn test_reverse_single_curve_open() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::CurveTo((1.0, 1.0).into(), (2.0, 2.0).into(), (3.0, 3.0).into()),
],
vec![
PathEl::MoveTo((3.0, 3.0).into()),
PathEl::CurveTo((2.0, 2.0).into(), (1.0, 1.0).into(), (0.0, 0.0).into()),
],
);
}
#[test]
fn test_reverse_curve_line_open() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::CurveTo((1.0, 1.0).into(), (2.0, 2.0).into(), (3.0, 3.0).into()),
PathEl::LineTo((4.0, 4.0).into()),
],
vec![
PathEl::MoveTo((4.0, 4.0).into()),
PathEl::LineTo((3.0, 3.0).into()),
PathEl::CurveTo((2.0, 2.0).into(), (1.0, 1.0).into(), (0.0, 0.0).into()),
],
);
}
#[test]
fn test_reverse_line_curve_open() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((1.0, 1.0).into()),
PathEl::CurveTo((2.0, 2.0).into(), (3.0, 3.0).into(), (4.0, 4.0).into()),
],
vec![
PathEl::MoveTo((4.0, 4.0).into()),
PathEl::CurveTo((3.0, 3.0).into(), (2.0, 2.0).into(), (1.0, 1.0).into()),
PathEl::LineTo((0.0, 0.0).into()),
],
);
}
#[test]
fn test_reverse_duplicate_point_after_move() {
reverse_test_helper(
vec![
PathEl::MoveTo((848.0, 348.0).into()),
PathEl::LineTo((848.0, 348.0).into()),
PathEl::QuadTo((848.0, 526.0).into(), (449.0, 704.0).into()),
PathEl::QuadTo((848.0, 171.0).into(), (848.0, 348.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((848.0, 348.0).into()),
PathEl::QuadTo((848.0, 171.0).into(), (449.0, 704.0).into()),
PathEl::QuadTo((848.0, 526.0).into(), (848.0, 348.0).into()),
PathEl::LineTo((848.0, 348.0).into()),
PathEl::ClosePath,
],
);
}
#[test]
fn test_reverse_duplicate_point_at_end() {
reverse_test_helper(
vec![
PathEl::MoveTo((0.0, 651.0).into()),
PathEl::LineTo((0.0, 101.0).into()),
PathEl::LineTo((0.0, 101.0).into()),
PathEl::LineTo((0.0, 651.0).into()),
PathEl::LineTo((0.0, 651.0).into()),
PathEl::ClosePath,
],
vec![
PathEl::MoveTo((0.0, 651.0).into()),
PathEl::LineTo((0.0, 651.0).into()),
PathEl::LineTo((0.0, 101.0).into()),
PathEl::LineTo((0.0, 101.0).into()),
PathEl::LineTo((0.0, 651.0).into()),
PathEl::ClosePath,
],
);
}
fn reverse_test_helper(contour: Vec<PathEl>, expected: Vec<PathEl>) {
assert_eq!(BezPath(contour).reverse_subpaths().0, expected);
}
#[test]
fn test_rect_segments() {
let x0 = 25.189500810000002;
let x1 = 568.18950081;
let y0 = -105.0;
let y1 = 176.0;
let r = Rect::from_points((x0, y0), (x1, y1));
let path0 = r.into_path(0.0);
assert!(
path0
.elements()
.iter()
.skip(1)
.all(|el| !matches!(el, PathEl::MoveTo(_)))
);
let path1 = BezPath::from_path_segments(path0.segments());
assert!(
path1
.elements()
.iter()
.skip(1)
.all(|el| !matches!(el, PathEl::MoveTo(_)))
);
}
#[test]
fn test_current_position() {
let mut path = BezPath::new();
assert_eq!(path.current_position(), None);
path.move_to((0., 0.));
assert_eq!(path.current_position(), Some(Point::new(0., 0.)));
path.line_to((10., 10.));
assert_eq!(path.current_position(), Some(Point::new(10., 10.)));
path.line_to((10., 0.));
assert_eq!(path.current_position(), Some(Point::new(10., 0.)));
path.close_path();
assert_eq!(path.current_position(), Some(Point::new(0., 0.)));
path.close_path();
assert_eq!(path.current_position(), None);
path.move_to((0., 10.));
assert_eq!(path.current_position(), Some(Point::new(0., 10.)));
path.close_path();
assert_eq!(path.current_position(), Some(Point::new(0., 10.)));
path.close_path();
assert_eq!(path.current_position(), None);
}
#[test]
fn winding_endpoints() {
let bez = BezPath::from_vec(vec![
PathEl::MoveTo((200.0, 410.0).into()),
PathEl::CurveTo(
(139.0, 410.0).into(),
(90.0, 360.8772277832031).into(),
(90.0, 300.0).into(),
),
PathEl::CurveTo(
(90.0, 239.0).into(),
(139.0, 190.0).into(),
(200.0, 190.0).into(),
),
PathEl::CurveTo(
(150.0, 210.0).into(),
(110.0, 250.0).into(),
(110.0, 300.0).into(),
),
PathEl::CurveTo(
(110.0, 349.0).into(),
(150.0, 390.0).into(),
(200.0, 390.0).into(),
),
PathEl::ClosePath,
]);
assert!(bez.contains((100.0, 300.1).into()));
assert!(bez.contains((100.0, 299.9).into()));
assert!(bez.contains((100.0, 300.0).into()));
}
#[test]
fn check_close_subpaths() {
let mut bez = BezPath::new();
bez.move_to((10.0, 10.0));
bez.line_to((100.0, 20.0));
bez.line_to((60.0, 100.0));
let elements = close_subpaths(&bez).collect::<Vec<_>>();
bez.close_path();
assert_eq!(&elements, bez.elements());
let mut bez2 = BezPath::new();
bez2.move_to((10.0, 10.0));
bez2.line_to((100.0, 20.0));
bez2.line_to((60.0, 100.0));
bez2.move_to((110.0, 10.0));
bez2.line_to((200.0, 20.0));
bez2.line_to((160.0, 100.0));
let elements2 = close_subpaths(&bez2).collect::<Vec<_>>();
let mut bez3 = BezPath::new();
bez3.move_to((10.0, 10.0));
bez3.line_to((100.0, 20.0));
bez3.line_to((60.0, 100.0));
bez3.close_path();
bez3.move_to((110.0, 10.0));
bez3.line_to((200.0, 20.0));
bez3.line_to((160.0, 100.0));
bez3.close_path();
assert_eq!(&elements2, bez3.elements());
}
#[test]
fn close_subpaths_does_not_duplicate_existing_closepath() {
let path = BezPath::from_vec(vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((10.0, 0.0).into()),
PathEl::LineTo((10.0, 10.0).into()),
PathEl::ClosePath,
PathEl::MoveTo((20.0, 0.0).into()),
PathEl::LineTo((30.0, 0.0).into()),
]);
let closed = close_subpaths(path.iter()).collect::<Vec<_>>();
assert_eq!(
closed,
vec![
PathEl::MoveTo((0.0, 0.0).into()),
PathEl::LineTo((10.0, 0.0).into()),
PathEl::LineTo((10.0, 10.0).into()),
PathEl::ClosePath,
PathEl::MoveTo((20.0, 0.0).into()),
PathEl::LineTo((30.0, 0.0).into()),
PathEl::ClosePath,
]
);
}
}