1use crate::flatten_simd::{Callback, LinePathEl};
7use crate::kurbo::{self, Affine, PathEl, Stroke, StrokeCtx, StrokeOpts};
8use alloc::vec::Vec;
9use fearless_simd::{Level, Simd, dispatch};
10use log::warn;
11
12pub use crate::flatten_simd::FlattenCtx;
13
14const TOL: f64 = 0.25;
16pub(crate) const TOL_2: f64 = TOL * TOL;
17
18#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct Point {
21 pub x: f32,
23 pub y: f32,
25}
26
27impl Point {
28 pub const ZERO: Self = Self::new(0., 0.);
30
31 pub const fn new(x: f32, y: f32) -> Self {
33 Self { x, y }
34 }
35}
36
37impl core::ops::Add for Point {
38 type Output = Self;
39
40 fn add(self, rhs: Self) -> Self {
41 Self::new(self.x + rhs.x, self.y + rhs.y)
42 }
43}
44
45impl core::ops::Sub for Point {
46 type Output = Self;
47
48 fn sub(self, rhs: Self) -> Self {
49 Self::new(self.x - rhs.x, self.y - rhs.y)
50 }
51}
52
53impl core::ops::Mul<f32> for Point {
54 type Output = Self;
55
56 fn mul(self, rhs: f32) -> Self {
57 Self::new(self.x * rhs, self.y * rhs)
58 }
59}
60
61#[derive(Clone, Copy, Debug)]
63pub struct Line {
64 pub p0: Point,
66 pub p1: Point,
68}
69
70impl Line {
71 pub fn new(p0: Point, p1: Point) -> Self {
73 Self { p0, p1 }
74 }
75}
76
77pub fn fill(
79 level: Level,
80 path: impl IntoIterator<Item = PathEl>,
81 affine: Affine,
82 line_buf: &mut Vec<Line>,
83 ctx: &mut FlattenCtx,
84) {
85 dispatch!(level, simd => fill_impl(simd, path, affine, line_buf, ctx));
86}
87
88#[inline(always)]
90pub fn fill_impl<S: Simd>(
91 simd: S,
92 path: impl IntoIterator<Item = PathEl>,
93 affine: Affine,
94 line_buf: &mut Vec<Line>,
95 flatten_ctx: &mut FlattenCtx,
96) {
97 line_buf.clear();
98 let iter = path.into_iter().map(
99 #[inline(always)]
100 |el| affine * el,
101 );
102
103 let mut lb = FlattenerCallback {
104 line_buf,
105 start: Point::ZERO,
106 p0: Point::ZERO,
107 is_nan: false,
108 };
109
110 crate::flatten_simd::flatten(simd, iter, TOL, &mut lb, flatten_ctx);
111
112 if lb.is_nan {
114 warn!("A path contains NaN, ignoring it.");
115
116 line_buf.clear();
117 }
118}
119pub fn stroke(
121 level: Level,
122 path: impl IntoIterator<Item = PathEl>,
123 style: &Stroke,
124 affine: Affine,
125 line_buf: &mut Vec<Line>,
126 flatten_ctx: &mut FlattenCtx,
127 stroke_ctx: &mut StrokeCtx,
128) {
129 let tolerance = TOL
131 / affine.as_coeffs()[0]
132 .abs()
133 .max(affine.as_coeffs()[3].abs())
134 .max(1.);
135
136 expand_stroke(path, style, tolerance, stroke_ctx);
137 fill(level, stroke_ctx.output(), affine, line_buf, flatten_ctx);
138}
139
140pub fn expand_stroke(
142 path: impl IntoIterator<Item = PathEl>,
143 style: &Stroke,
144 tolerance: f64,
145 stroke_ctx: &mut StrokeCtx,
146) {
147 kurbo::stroke_with(path, style, &StrokeOpts::default(), tolerance, stroke_ctx);
148}
149
150struct FlattenerCallback<'a> {
151 line_buf: &'a mut Vec<Line>,
152 start: Point,
153 p0: Point,
154 is_nan: bool,
155}
156
157impl Callback for FlattenerCallback<'_> {
158 #[inline(always)]
159 fn callback(&mut self, el: LinePathEl) {
160 match el {
161 LinePathEl::MoveTo(p) => {
162 self.is_nan |= p.is_nan();
163
164 self.start = Point::new(p.x as f32, p.y as f32);
165 self.p0 = self.start;
166 }
167 LinePathEl::LineTo(p) => {
168 self.is_nan |= p.is_nan();
169
170 let p = Point::new(p.x as f32, p.y as f32);
171 self.line_buf.push(Line::new(self.p0, p));
172 self.p0 = p;
173 }
174 }
175 }
176}