1use crate::{
11 LineGeometry, PathFillRule, Point, Rect, Stroke, StrokeCap, StrokeJoin, VectorPath,
12 vector_path::{flatten_cubic_into, quad_as_cubic},
13};
14
15#[derive(Clone, Debug, Default, PartialEq)]
18pub struct PathContour {
19 pub points: Vec<Point>,
21 pub closed: bool,
23}
24
25impl PathContour {
26 const INITIAL_POINTS: usize = 16;
30
31 fn starting_at(point: Point) -> Self {
34 let mut points =
35 crate::thread_state::with(|state| state.path_buffers.borrow_mut().points());
36 points.push(point);
37 Self {
38 points,
39 closed: false,
40 }
41 }
42}
43
44pub(crate) struct PathBuffers {
48 points: Vec<Vec<Point>>,
49 kept_points: usize,
51 contours: Vec<Vec<PathContour>>,
52}
53
54impl PathBuffers {
55 const KEPT: usize = 32;
57 const POINT_BUDGET: usize = 4096;
60
61 pub(crate) const fn new() -> Self {
62 Self {
63 points: Vec::new(),
64 kept_points: 0,
65 contours: Vec::new(),
66 }
67 }
68
69 fn points(&mut self) -> Vec<Point> {
70 match self.points.pop() {
71 Some(points) => {
72 self.kept_points -= points.capacity();
73 points
74 }
75 None => Vec::with_capacity(PathContour::INITIAL_POINTS),
76 }
77 }
78
79 fn contours(&mut self) -> Vec<PathContour> {
80 self.contours.pop().unwrap_or_default()
81 }
82
83 fn keep_points(&mut self, contours: &mut Vec<PathContour>) {
85 for PathContour { mut points, .. } in contours.drain(..) {
86 let room = points.capacity();
87 if self.kept_points + room <= Self::POINT_BUDGET {
88 points.clear();
89 self.kept_points += room;
90 self.points.push(points);
91 }
92 }
93 }
94
95 fn keep(&mut self, mut contours: Vec<PathContour>) {
97 self.keep_points(&mut contours);
98 if self.contours.len() < Self::KEPT {
99 self.contours.push(contours);
100 }
101 }
102}
103
104#[derive(Clone, Debug, Default, PartialEq)]
118pub struct Path {
119 contours: Vec<PathContour>,
120 pen: Point,
121}
122
123impl Path {
124 pub fn new() -> Self {
126 Self::default()
127 }
128
129 pub fn move_to(&mut self, point: Point) {
131 self.push_contour(point);
132 self.pen = point;
133 }
134
135 fn push_contour(&mut self, start: Point) {
136 if self.contours.capacity() == 0 {
137 self.contours =
138 crate::thread_state::with(|state| state.path_buffers.borrow_mut().contours());
139 }
140 self.contours.push(PathContour::starting_at(start));
141 }
142
143 #[inline]
145 pub fn line_to(&mut self, point: Point) {
146 match self.contours.last_mut() {
147 Some(contour) if !contour.closed => contour.points.push(point),
148 _ => self.line_to_new_contour(point),
149 }
150 self.pen = point;
151 }
152
153 #[cold]
156 #[inline(never)]
157 fn line_to_new_contour(&mut self, point: Point) {
158 self.open_contour().push(point);
159 }
160
161 pub fn quadratic_to(&mut self, control: Point, end: Point) {
164 let (first, second) = quad_as_cubic(self.pen, control, end);
165 self.cubic_to(first, second, end);
166 }
167
168 pub fn cubic_to(&mut self, first: Point, second: Point, end: Point) {
171 let start = self.pen;
172 flatten_cubic_into(self.open_contour(), start, first, second, end);
173 self.pen = end;
174 }
175
176 pub fn close(&mut self) {
179 if let Some(contour) = self.contours.last_mut().filter(|contour| !contour.closed) {
180 contour.closed = true;
181 if let Some(&first) = contour.points.first() {
182 self.pen = first;
183 }
184 }
185 }
186
187 pub fn reset(&mut self) {
189 if !self.contours.is_empty() {
190 crate::thread_state::with(|state| {
191 state
192 .path_buffers
193 .borrow_mut()
194 .keep_points(&mut self.contours);
195 });
196 }
197 self.pen = Point::ZERO;
198 }
199
200 pub fn contours(&self) -> &[PathContour] {
202 &self.contours
203 }
204
205 pub fn is_empty(&self) -> bool {
207 !self
208 .contours
209 .iter()
210 .any(|contour| contour.points.len() >= 2)
211 }
212
213 pub fn bounds(&self) -> Rect {
216 let mut points = self.contours.iter().flat_map(|contour| &contour.points);
217 let Some(&first) = points.next() else {
218 return Rect::EMPTY;
219 };
220 let (min, max) = points.fold((first, first), |(min, max), point| {
221 (
222 Point::new(min.x.min(point.x), min.y.min(point.y)),
223 Point::new(max.x.max(point.x), max.y.max(point.y)),
224 )
225 });
226 Rect {
227 x: min.x,
228 y: min.y,
229 width: max.x - min.x,
230 height: max.y - min.y,
231 }
232 }
233
234 pub fn to_vector_path(&self, fill_rule: PathFillRule) -> VectorPath {
236 contours_vector_path(&self.contours, fill_rule)
237 }
238
239 fn open_contour(&mut self) -> &mut Vec<Point> {
242 if self.contours.last().is_none_or(|contour| contour.closed) {
243 self.push_contour(self.pen);
244 }
245 let last = self.contours.len() - 1;
246 &mut self.contours[last].points
247 }
248}
249
250pub(crate) fn contours_vector_path(
252 contours: &[PathContour],
253 fill_rule: PathFillRule,
254) -> VectorPath {
255 VectorPath::from_subpaths(
256 contours
257 .iter()
258 .filter(|contour| contour.points.len() >= 2)
259 .map(|contour| contour.points.clone())
260 .collect(),
261 fill_rule,
262 )
263}
264
265impl Drop for Path {
266 fn drop(&mut self) {
267 if self.contours.capacity() == 0 {
268 return;
269 }
270 let contours = std::mem::take(&mut self.contours);
271 crate::thread_state::with(|state| state.path_buffers.borrow_mut().keep(contours));
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq)]
281pub struct DashPathEffect {
282 intervals: [f32; Self::MAX_INTERVALS],
283 count: usize,
284 phase: f32,
285}
286
287impl DashPathEffect {
288 pub const MAX_INTERVALS: usize = 8;
290
291 pub fn new(intervals: &[f32], phase: f32) -> Self {
294 let mut stored = [0.0; Self::MAX_INTERVALS];
295 let count = intervals.len().min(Self::MAX_INTERVALS);
296 stored[..count].copy_from_slice(&intervals[..count]);
297 Self {
298 intervals: stored,
299 count,
300 phase,
301 }
302 }
303
304 pub fn intervals(&self) -> &[f32] {
306 &self.intervals[..self.count]
307 }
308
309 fn period(&self) -> Option<f32> {
310 let period: f32 = self
311 .intervals()
312 .iter()
313 .map(|interval| interval.max(0.0))
314 .sum();
315 (self.count >= 2 && self.count.is_multiple_of(2) && period > 0.0 && period.is_finite())
316 .then_some(period)
317 }
318
319 pub fn for_each_dash(&self, points: &[Point], mut dash: impl FnMut(&[Point])) {
323 let Some(period) = self.period() else {
324 dash(points);
325 return;
326 };
327 let interval = |index: usize| self.intervals[index].max(0.0);
328 let mut current: Vec<Point> = Vec::new();
329 let mut into = self.phase.rem_euclid(period);
331 let mut index = 0;
332 while into >= interval(index) {
333 into -= interval(index);
334 index = (index + 1) % self.count;
335 }
336 for pair in points.windows(2) {
337 let (start, end) = (pair[0], pair[1]);
338 let length = ((end.x - start.x).powi(2) + (end.y - start.y).powi(2)).sqrt();
339 let at = |distance: f32| {
340 let t = if length > 0.0 { distance / length } else { 0.0 };
341 Point::new(
342 start.x + (end.x - start.x) * t,
343 start.y + (end.y - start.y) * t,
344 )
345 };
346 let mut walked = 0.0;
347 while walked < length {
348 let on = index.is_multiple_of(2);
349 let step = (interval(index) - into).min(length - walked);
350 if on {
351 if current.is_empty() {
352 current.push(at(walked));
353 }
354 current.push(at(walked + step));
355 }
356 walked += step;
357 into += step;
358 if into >= interval(index) {
359 into = 0.0;
360 if on && current.len() >= 2 {
361 dash(¤t);
362 }
363 current.clear();
364 index = (index + 1) % self.count;
365 }
366 }
367 }
368 if current.len() >= 2 {
369 dash(¤t);
370 }
371 }
372}
373
374#[derive(Clone, Copy, Debug, PartialEq)]
377pub enum DrawStyle {
378 Fill,
380 Stroke(Stroke),
382 DashedStroke(Stroke, DashPathEffect),
385}
386
387pub fn for_each_stroke_line(
396 points: &[Point],
397 closed: bool,
398 stroke: Stroke,
399 mut line: impl FnMut(LineGeometry),
400) {
401 let corner = match stroke.join {
402 StrokeJoin::Miter => StrokeCap::Square,
403 StrokeJoin::Round | StrokeJoin::Bevel => StrokeCap::Round,
404 };
405 let closing = closed
406 .then(|| points.first().zip(points.last()))
407 .flatten()
408 .filter(|(first, last)| first != last)
409 .map(|(&first, &last)| (last, first));
410 let mut edges = points
411 .windows(2)
412 .map(|pair| (pair[0], pair[1]))
413 .chain(closing)
414 .filter(|(start, end)| start != end)
415 .peekable();
416 let mut first = true;
417 while let Some((start, end)) = edges.next() {
418 let start_cap = if closed || !first { corner } else { stroke.cap };
419 first = false;
420 let end_cap = if closed || edges.peek().is_some() {
421 corner
422 } else {
423 stroke.cap
424 };
425 let with_cap = |cap| Stroke { cap, ..stroke };
426 if start_cap == end_cap {
427 line(LineGeometry::new(start, end, with_cap(start_cap)));
428 continue;
429 }
430 let half_width = stroke.half_width();
431 let length = ((end.x - start.x).powi(2) + (end.y - start.y).powi(2)).sqrt();
432 let grow = |cap: StrokeCap| {
433 if cap == StrokeCap::Square {
434 half_width / length
435 } else {
436 0.0
437 }
438 };
439 let (back, ahead) = (grow(start_cap), grow(end_cap));
440 let (dx, dy) = (end.x - start.x, end.y - start.y);
441 line(LineGeometry::new(
442 Point::new(start.x - dx * back, start.y - dy * back),
443 Point::new(end.x + dx * ahead, end.y + dy * ahead),
444 with_cap(StrokeCap::Butt),
445 ));
446 for (point, cap) in [(start, start_cap), (end, end_cap)] {
447 if cap == StrokeCap::Round {
448 line(LineGeometry::new(point, point, with_cap(StrokeCap::Round)));
449 }
450 }
451 }
452}
453
454#[cfg(test)]
455#[path = "tests/path_tests.rs"]
456mod tests;