1use crate::flatten_simd::Callback;
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
88pub fn fill_impl<S: Simd>(
90 simd: S,
91 path: impl IntoIterator<Item = PathEl>,
92 affine: Affine,
93 line_buf: &mut Vec<Line>,
94 flatten_ctx: &mut FlattenCtx,
95) {
96 line_buf.clear();
97 let iter = path.into_iter().map(|el| affine * el);
98
99 let mut lb = FlattenerCallback {
100 line_buf,
101 start: kurbo::Point::default(),
102 p0: kurbo::Point::default(),
103 is_nan: false,
104 closed: false,
105 };
106
107 crate::flatten_simd::flatten(simd, iter, TOL, &mut lb, flatten_ctx);
108
109 if !lb.closed {
110 close_path(lb.start, lb.p0, lb.line_buf);
111 }
112
113 if lb.is_nan {
115 warn!("A path contains NaN, ignoring it.");
116
117 line_buf.clear();
118 }
119}
120pub fn stroke(
122 level: Level,
123 path: impl IntoIterator<Item = PathEl>,
124 style: &Stroke,
125 affine: Affine,
126 line_buf: &mut Vec<Line>,
127 flatten_ctx: &mut FlattenCtx,
128 stroke_ctx: &mut StrokeCtx,
129) {
130 let tolerance = TOL
132 / affine.as_coeffs()[0]
133 .abs()
134 .max(affine.as_coeffs()[3].abs())
135 .max(1.);
136
137 expand_stroke(path, style, tolerance, stroke_ctx);
138 fill(level, stroke_ctx.output(), affine, line_buf, flatten_ctx);
139}
140
141pub fn expand_stroke(
143 path: impl IntoIterator<Item = PathEl>,
144 style: &Stroke,
145 tolerance: f64,
146 stroke_ctx: &mut StrokeCtx,
147) {
148 kurbo::stroke_with(path, style, &StrokeOpts::default(), tolerance, stroke_ctx);
149}
150
151struct FlattenerCallback<'a> {
152 line_buf: &'a mut Vec<Line>,
153 start: kurbo::Point,
154 p0: kurbo::Point,
155 is_nan: bool,
156 closed: bool,
157}
158
159impl Callback for FlattenerCallback<'_> {
160 #[inline(always)]
161 fn callback(&mut self, el: PathEl) {
162 self.is_nan |= el.is_nan();
163
164 match el {
165 kurbo::PathEl::MoveTo(p) => {
166 if !self.closed && self.p0 != self.start {
167 close_path(self.start, self.p0, self.line_buf);
168 }
169
170 self.closed = false;
171 self.start = p;
172 self.p0 = p;
173 }
174 kurbo::PathEl::LineTo(p) => {
175 let pt0 = Point::new(self.p0.x as f32, self.p0.y as f32);
176 let pt1 = Point::new(p.x as f32, p.y as f32);
177 self.line_buf.push(Line::new(pt0, pt1));
178 self.p0 = p;
179 }
180 el @ (kurbo::PathEl::QuadTo(_, _) | kurbo::PathEl::CurveTo(_, _, _)) => {
181 unreachable!("Path has been flattened, so shouldn't contain {el:?}.")
182 }
183 kurbo::PathEl::ClosePath => {
184 self.closed = true;
185
186 close_path(self.start, self.p0, self.line_buf);
187 }
188 }
189 }
190}
191
192fn close_path(start: kurbo::Point, p0: kurbo::Point, line_buf: &mut Vec<Line>) {
193 let pt0 = Point::new(p0.x as f32, p0.y as f32);
194 let pt1 = Point::new(start.x as f32, start.y as f32);
195
196 if pt0 != pt1 {
197 line_buf.push(Line::new(pt0, pt1));
198 }
199}