use crate::flatten_simd::{Callback, LinePathEl};
use crate::geometry::RectU16;
use crate::kurbo::{self, Affine, PathEl, Stroke, StrokeCtx, StrokeOpts};
use alloc::vec::Vec;
use fearless_simd::{Level, Simd, dispatch};
use log::warn;
pub use crate::flatten_simd::FlattenCtx;
pub(crate) const SQRT_TOL: f64 = 0.5;
pub(crate) const TOL: f64 = SQRT_TOL * SQRT_TOL;
pub(crate) const TOL_2: f64 = TOL * TOL;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point {
pub x: f32,
pub y: f32,
}
impl Point {
pub const ZERO: Self = Self::new(0., 0.);
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
impl From<kurbo::Point> for Point {
#[inline(always)]
fn from(value: kurbo::Point) -> Self {
Self {
x: value.x as f32,
y: value.y as f32,
}
}
}
impl core::ops::Add for Point {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(self.x + rhs.x, self.y + rhs.y)
}
}
impl core::ops::Sub for Point {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(self.x - rhs.x, self.y - rhs.y)
}
}
impl core::ops::Mul<f32> for Point {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self::new(self.x * rhs, self.y * rhs)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Line {
pub p0: Point,
pub p1: Point,
}
impl Line {
pub fn new(p0: Point, p1: Point) -> Self {
Self { p0, p1 }
}
}
pub fn fill(
level: Level,
path: impl IntoIterator<Item = PathEl>,
affine: Affine,
line_buf: &mut Vec<Line>,
ctx: &mut FlattenCtx,
cull_bbox: RectU16,
) {
dispatch!(level, simd => fill_impl(simd, path, affine, line_buf, ctx, cull_bbox));
}
#[inline(always)]
pub fn fill_impl<S: Simd>(
simd: S,
path: impl IntoIterator<Item = PathEl>,
affine: Affine,
line_buf: &mut Vec<Line>,
flatten_ctx: &mut FlattenCtx,
cull_bbox: RectU16,
) {
line_buf.clear();
let mut lb = FlattenerCallback {
line_buf,
start: Point::ZERO,
p0: Point::ZERO,
is_nan: false,
};
crate::flatten_simd::flatten(simd, path, affine, &mut lb, flatten_ctx, cull_bbox);
if lb.is_nan {
warn!("A path contains NaN, ignoring it.");
line_buf.clear();
}
}
pub fn stroke(
level: Level,
path: impl IntoIterator<Item = PathEl>,
style: &Stroke,
affine: Affine,
line_buf: &mut Vec<Line>,
flatten_ctx: &mut FlattenCtx,
stroke_ctx: &mut StrokeCtx,
cull_bbox: RectU16,
) {
let tolerance = TOL
/ affine.as_coeffs()[0]
.abs()
.max(affine.as_coeffs()[3].abs())
.max(1.);
expand_stroke(path, style, tolerance, stroke_ctx);
fill(
level,
stroke_ctx.output(),
affine,
line_buf,
flatten_ctx,
cull_bbox,
);
}
pub fn expand_stroke(
path: impl IntoIterator<Item = PathEl>,
style: &Stroke,
tolerance: f64,
stroke_ctx: &mut StrokeCtx,
) {
kurbo::stroke_with(path, style, &StrokeOpts::default(), tolerance, stroke_ctx);
}
struct FlattenerCallback<'a> {
line_buf: &'a mut Vec<Line>,
start: Point,
p0: Point,
is_nan: bool,
}
impl Callback for FlattenerCallback<'_> {
#[inline(always)]
fn callback(&mut self, el: LinePathEl) {
match el {
LinePathEl::MoveTo(p) => {
self.is_nan |= p.is_nan();
let p = p.into();
self.start = p;
self.p0 = p;
}
LinePathEl::LineTo(p) => {
self.is_nan |= p.is_nan();
let p = p.into();
self.line_buf.push(Line::new(self.p0, p));
self.p0 = p;
}
}
}
}