use crate::types::point::Point;
use itertools::Itertools;
use std::f32::consts::PI;
use std::ops::{Add, Deref, Mul, Sub};
#[derive(Debug, Clone)]
pub(crate) enum SVGPathCommand {
Move(Move),
Line(Line<f32>),
QuadCurve(QuadCurve),
Curve(Curve),
End(End),
}
macro_rules! format_float {
($num:expr) => {
format!("{:.2}", $num).deref()
};
}
pub(crate) trait SvgCommand {
fn append_to_string(&self, offset: &Point<f32>, string: &mut String);
fn length_estimation(&self) -> usize;
}
impl SVGPathCommand {
pub(crate) fn append_to_string(&self, offset: &Point<f32>, string: &mut String) {
match self {
SVGPathCommand::Move(x) => x.append_to_string(offset, string),
SVGPathCommand::Line(x) => x.append_to_string(offset, string),
SVGPathCommand::QuadCurve(x) => x.append_to_string(offset, string),
SVGPathCommand::Curve(x) => x.append_to_string(offset, string),
SVGPathCommand::End(x) => x.append_to_string(offset, string),
}
}
pub(crate) fn length_estimation(&self) -> usize {
match self {
SVGPathCommand::Move(x) => x.length_estimation(),
SVGPathCommand::Line(x) => x.length_estimation(),
SVGPathCommand::QuadCurve(x) => x.length_estimation(),
SVGPathCommand::Curve(x) => x.length_estimation(),
SVGPathCommand::End(x) => x.length_estimation(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Line<T> {
pub(crate) start: Point<T>,
pub(crate) end: Point<T>,
}
impl<T> From<(Point<T>, Point<T>)> for Line<T> {
fn from(value: (Point<T>, Point<T>)) -> Self {
Line {
start: value.0,
end: value.1,
}
}
}
impl<T> From<(&Point<T>, &Point<T>)> for Line<T>
where
T: Copy,
{
fn from(value: (&Point<T>, &Point<T>)) -> Self {
Line {
start: *value.0,
end: *value.1,
}
}
}
#[allow(dead_code)]
pub(crate) struct Parallelogram<T> {
p1: Point<T>,
p2: Point<T>,
p3: Point<T>,
p4: Point<T>,
}
#[allow(dead_code)]
impl<T> Parallelogram<T> {
pub(crate) fn lines(&self) -> Vec<Line<T>>
where
T: Copy,
{
vec![
Line {
start: self.p1,
end: self.p2,
},
Line {
start: self.p2,
end: self.p3,
},
Line {
start: self.p3,
end: self.p4,
},
Line {
start: self.p4,
end: self.p1,
},
]
}
}
#[derive(Debug, Clone)]
pub(crate) struct Move {
pub(crate) position: Point<f32>,
}
#[derive(Debug, Clone)]
pub(crate) struct QuadCurve {
pub(crate) c1: Point<f32>,
pub(crate) e: Point<f32>,
pub(crate) s: Point<f32>,
}
#[derive(Debug, Clone)]
pub(crate) struct Curve {
pub(crate) c2: Point<f32>,
pub(crate) c1: Point<f32>,
pub(crate) e: Point<f32>,
pub(crate) s: Point<f32>,
}
#[derive(Debug, Clone)]
pub(crate) struct End {}
#[allow(dead_code)]
impl<T> Line<T>
where
T: Copy + Sub<Output = T> + Add<Output = T>,
{
fn rotate_point(point: Point<T>, angle: T) -> Point<T>
where
T: num_traits::float::Float,
{
Point {
x: point.x * angle.cos() - point.y * angle.sin(),
y: point.y * angle.cos() + point.x * angle.sin(),
}
}
pub(crate) fn thicken(&self, width: T) -> Parallelogram<T>
where
T: num_traits::float::Float + From<f32>,
{
let half_width = width / <T as From<f32>>::from(2.0);
let height = (self.start.y - self.end.y).abs();
let width = (self.start.x - self.end.x).abs();
let deg = if height == T::zero() {
<T as From<f32>>::from(PI) / <T as From<f32>>::from(2.0)
} else if width == T::zero() {
T::zero()
} else {
let dy = self.start.y - self.end.y;
let dx = self.start.x - self.end.x;
(dy / dx).atan()
};
let start_rot = Line::rotate_point(self.start, deg);
let end_rot = Line::rotate_point(self.end, deg);
let vec = Point {
x: half_width,
y: T::zero(),
};
let (p1, p2, p3, p4) = (
start_rot - vec,
start_rot + vec,
end_rot - vec,
end_rot + vec,
);
Parallelogram {
p1: Line::rotate_point(p1, -deg),
p2: Line::rotate_point(p2, -deg),
p3: Line::rotate_point(p3, -deg),
p4: Line::rotate_point(p4, -deg),
}
}
}
impl<T> Line<T>
where
T: Copy + Sub<Output = T> + Mul<Output = T> + PartialOrd,
{
pub(crate) fn intersects(&self, other: &Line<T>) -> bool {
fn ccw<T>(a: Point<T>, b: Point<T>, c: Point<T>) -> bool
where
T: Copy + Sub<Output = T> + Mul<Output = T> + PartialOrd,
{
(c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x)
}
fn i<T>(a: Point<T>, b: Point<T>, c: Point<T>, d: Point<T>) -> bool
where
T: Copy + Sub<Output = T> + Mul<Output = T> + PartialOrd,
{
ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d)
}
i(self.start, self.end, other.start, other.end)
}
}
impl SvgCommand for Line<f32> {
fn append_to_string(&self, offset: &Point<f32>, string: &mut String) {
string.push_str("L ");
string.push_str(format_float!(self.end.x + offset.x));
string.push(' ');
string.push_str(format_float!(self.end.y + offset.y))
}
fn length_estimation(&self) -> usize {
3 + 7 + 7
}
}
impl SvgCommand for Move {
fn append_to_string(&self, offset: &Point<f32>, string: &mut String) {
string.push_str("M ");
string.push_str(format_float!(self.position.x + offset.x));
string.push(' ');
string.push_str(format_float!(self.position.y + offset.y))
}
fn length_estimation(&self) -> usize {
3 + 7 + 7
}
}
impl SvgCommand for QuadCurve {
fn append_to_string(&self, offset: &Point<f32>, string: &mut String) {
string.push_str("Q ");
string.push_str(format_float!(self.c1.x + offset.x));
string.push(' ');
string.push_str(format_float!(self.c1.y + offset.y));
string.push(',');
string.push_str(format_float!(self.e.x + offset.x));
string.push(' ');
string.push_str(format_float!(self.e.y + offset.y))
}
fn length_estimation(&self) -> usize {
3 + 7 + 7 + 3 + 7 + 7
}
}
impl SvgCommand for Curve {
fn append_to_string(&self, offset: &Point<f32>, string: &mut String) {
string.push_str("C ");
string.push_str(format_float!(self.c2.x + offset.x));
string.push(' ');
string.push_str(format_float!(self.c2.y + offset.y));
string.push(',');
string.push_str(format_float!(self.c1.x + offset.x));
string.push(' ');
string.push_str(format_float!(self.c1.y + offset.y));
string.push(',');
string.push_str(format_float!(self.e.x + offset.x));
string.push(' ');
string.push_str(format_float!(self.e.y + offset.y));
string.push(',');
}
fn length_estimation(&self) -> usize {
2 + 6 + 7 + 7 + 7 + 7 + 7 + 7
}
}
impl SvgCommand for End {
fn append_to_string(&self, _offset: &Point<f32>, string: &mut String) {
string.push('Z');
}
fn length_estimation(&self) -> usize {
1
}
}
impl QuadCurve {
fn divide_quad(&self, center_points: usize) -> Vec<Point<f32>> {
let points = center_points + 2;
(0..points)
.map(|i| (1. / (points - 1) as f32) * i as f32)
.map(|x| self.get_point_on_curve(x))
.collect()
}
pub(crate) fn approximate(&self) -> Vec<Line<f32>> {
let approx = self.divide_quad(1);
approx
.iter()
.tuple_windows()
.map(|(s, e)| Line { start: *s, end: *e })
.collect()
}
fn get_point_on_curve(&self, t: f32) -> Point<f32> {
let d = self.s + (self.c1 - self.s) * t;
let e = self.c1 + (self.e - self.c1) * t;
d + (e - d) * t
}
}
impl Curve {
fn divide_curve(&self, center_points: usize) -> Vec<Point<f32>> {
let points = center_points + 2;
(0..points)
.map(|i| (1. / (points - 1) as f32) * i as f32)
.map(|x| self.get_point_on_curve(x))
.collect()
}
pub(crate) fn approximate(&self) -> Vec<Line<f32>> {
let approx = self.divide_curve(2);
approx
.iter()
.tuple_windows()
.map(|(s, e)| Line { start: *s, end: *e })
.collect()
}
fn get_point_on_curve(&self, t: f32) -> Point<f32> {
let inv_t = 1. - t;
let p5 = self.s * inv_t + self.c1 * t;
let p6 = self.c1 * inv_t + self.c2 * t;
let p7 = self.c2 * inv_t + self.e * t;
let p8 = p5 * inv_t + p6 * t;
let p9 = p6 * inv_t + p7 * t;
p8 * inv_t + p9 * t
}
}