use core::ops::{Mul, Range};
use arrayvec::ArrayVec;
use crate::common::{solve_cubic, solve_quadratic};
use crate::MAX_EXTREMA;
use crate::{
Affine, CubicBez, Line, ParamCurve, ParamCurveArclen, ParamCurveArea, ParamCurveExtrema,
ParamCurveNearest, Point, QuadBez, Rect, Shape, TranslateScale,
};
#[derive(Clone, Default, Debug)]
pub struct BezPath(ArrayVec::<PathElArray>);
pub type PathElArray = [PathEl; MAX_BEZ_PATH];
pub const MAX_BEZ_PATH: usize = 16;
#[derive(Clone, Copy, Debug)]
pub enum PathEl {
MoveTo(Point),
LineTo(Point),
QuadTo(Point, Point),
CurveTo(Point, Point, Point),
ClosePath,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PathSeg {
Line(Line),
Quad(QuadBez),
Cubic(CubicBez),
}
impl BezPath {
pub fn new() -> BezPath {
Default::default()
}
pub fn from_vec(v: ArrayVec::<PathElArray>) -> BezPath {
BezPath(v)
}
pub fn push(&mut self, el: PathEl) {
self.0.push(el)
}
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) {
self.push(PathEl::LineTo(p.into()));
}
pub fn quad_to<P: Into<Point>>(&mut self, p1: P, p2: P) {
self.push(PathEl::QuadTo(p1.into(), p2.into()));
}
pub fn curve_to<P: Into<Point>>(&mut self, p1: P, p2: P, p3: P) {
self.push(PathEl::CurveTo(p1.into(), p2.into(), p3.into()));
}
pub fn close_path(&mut self) {
self.push(PathEl::ClosePath);
}
pub fn elements(&self) -> &[PathEl] {
&self.0
}
pub fn segments<'a>(&'a self) -> impl Iterator<Item = PathSeg> + 'a {
BezPath::segments_of_slice(&self.0)
}
fn segments_of_slice<'a>(slice: &'a [PathEl]) -> BezPathSegs<'a> {
let first = match slice.get(0) {
Some(PathEl::MoveTo(ref p)) => *p,
Some(_) => panic!("First element has to be a PathEl::Moveto!"),
None => Default::default(),
};
BezPathSegs {
c: slice.iter(),
start: first,
last: first,
}
}
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,
_ => 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) => Some(PathSeg::Line(Line::new(last, start))),
_ => None,
}),
_ => None,
}
}
pub fn is_empty(&self) -> bool {
!self.0.iter().any(|el| match *el {
PathEl::LineTo(..) | PathEl::QuadTo(..) | PathEl::CurveTo(..) => true,
_ => false,
})
}
pub fn apply_affine(&mut self, affine: Affine) {
for el in self.0.iter_mut() {
*el = affine * (*el);
}
}
pub fn nearest(&self, p: Point, accuracy: f64) -> (usize, f64, f64) {
let mut best = None;
for (ix, seg) in self.segments().enumerate() {
let (t, r) = seg.nearest(p, accuracy);
if best.map(|(_, _, r_best)| r < r_best).unwrap_or(true) {
best = Some((ix, t, r));
}
}
best.unwrap()
}
}
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 Mul<PathEl> for Affine {
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<BezPath> for Affine {
type Output = BezPath;
fn mul(self, other: BezPath) -> BezPath {
BezPath(other.0.iter().map(|&el| self * el).collect())
}
}
impl<'a> Mul<&'a 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<BezPath> for TranslateScale {
type Output = BezPath;
fn mul(self, other: BezPath) -> BezPath {
BezPath(other.0.iter().map(|&el| self * el).collect())
}
}
impl<'a> Mul<&'a BezPath> for TranslateScale {
type Output = BezPath;
fn mul(self, other: &BezPath) -> BezPath {
BezPath(other.0.iter().map(|&el| self * el).collect())
}
}
struct BezPathSegs<'a> {
c: core::slice::Iter<'a, PathEl>,
start: Point,
last: Point,
}
impl<'a> Iterator for BezPathSegs<'a> {
type Item = PathSeg;
fn next(&mut self) -> Option<PathSeg> {
for el in &mut self.c {
let (ret, last) = match *el {
PathEl::MoveTo(p) => {
self.start = p;
self.last = p;
continue;
}
PathEl::LineTo(p) => (PathSeg::Line(Line::new(self.last, p)), p),
PathEl::QuadTo(p1, p2) => (PathSeg::Quad(QuadBez::new(self.last, p1, p2)), p2),
PathEl::CurveTo(p1, p2, p3) => {
(PathSeg::Cubic(CubicBez::new(self.last, p1, p2, p3)), p3)
}
PathEl::ClosePath => {
if self.last != self.start {
(PathSeg::Line(Line::new(self.last, self.start)), self.start)
} else {
continue;
}
}
};
self.last = last;
return Some(ret);
}
None
}
}
impl<'a> BezPathSegs<'a> {
fn arclen(self, accuracy: f64) -> f64 {
self.map(|seg| seg.arclen(accuracy)).sum()
}
fn area(self) -> f64 {
self.map(|seg| seg.signed_area()).sum()
}
fn winding(self, p: Point) -> i32 {
self.map(|seg| seg.winding(p)).sum()
}
fn bounding_box(self) -> Rect {
let mut bbox: Option<Rect> = None;
for seg in self {
let seg_bb = seg.bounding_box();
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)),
}
}
}
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),
}
}
}
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) -> (f64, f64) {
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 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 a = end.y - 2.0 * p1.y + start.y;
let b = 2.0 * (p1.y - start.y);
let c = start.y - p.y;
for t in solve_quadratic(c, b, a) {
if t >= 0.0 && t <= 1.0 {
let x = quad.eval(t).x;
if p.x >= x {
return sign;
} else {
return 0;
}
}
}
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 a = end.y - 3.0 * p2.y + 3.0 * p1.y - start.y;
let b = 3.0 * (p2.y - 2.0 * p1.y + start.y);
let c = 3.0 * (p1.y - start.y);
let d = start.y - p.y;
for t in solve_cubic(d, c, b, a) {
if t >= 0.0 && t <= 1.0 {
let x = cubic.eval(t).x;
if p.x >= x {
return sign;
} else {
return 0;
}
}
}
0
}
}
}
fn winding(&self, p: Point) -> i32 {
self.extrema_ranges()
.into_iter()
.map(|range| self.subsegment(range).winding_inner(p))
.sum()
}
}
impl From<CubicBez> for PathSeg {
fn from(cubic_bez: CubicBez) -> PathSeg {
PathSeg::Cubic(cubic_bez)
}
}
impl From<Line> for PathSeg {
fn from(line: Line) -> PathSeg {
PathSeg::Line(line)
}
}
impl From<QuadBez> for PathSeg {
fn from(quad_bez: QuadBez) -> PathSeg {
PathSeg::Quad(quad_bez)
}
}
impl Shape for BezPath {
type BezPathIter = arrayvec::IntoIter<PathElArray>;
fn to_bez_path(&self, _tolerance: f64) -> Self::BezPathIter {
self.clone().0.into_iter()
}
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()
}
fn as_path_slice(&self) -> Option<&[PathEl]> {
Some(&self.0)
}
}
impl<'a> Shape for &'a [PathEl] {
type BezPathIter = core::iter::Cloned<core::slice::Iter<'a, PathEl>>;
#[inline]
fn to_bez_path(&self, _tolerance: f64) -> Self::BezPathIter {
self.iter().cloned()
}
fn area(&self) -> f64 {
BezPath::segments_of_slice(self).area()
}
fn perimeter(&self, accuracy: f64) -> f64 {
BezPath::segments_of_slice(self).arclen(accuracy)
}
fn winding(&self, pt: Point) -> i32 {
BezPath::segments_of_slice(self).winding(pt)
}
fn bounding_box(&self) -> Rect {
BezPath::segments_of_slice(self).bounding_box()
}
#[inline]
fn as_path_slice(&self) -> Option<&[PathEl]> {
Some(self)
}
}