1use core::fmt;
6use std::cell::RefCell;
7use std::sync::Arc;
8
9use crate::geometry::{Point, Rect, Transform};
10
11pub(crate) mod stroke;
12
13#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
15pub enum FillRule {
16 #[default]
18 NonZero,
19 EvenOdd,
21}
22
23#[derive(Copy, Clone, Debug, PartialEq)]
25pub enum PathSegment {
26 MoveTo(Point),
27 LineTo(Point),
28 QuadTo(Point, Point),
30 CubicTo(Point, Point, Point),
32 Close,
33}
34
35#[derive(Default)]
44pub struct Path {
45 pub(crate) segments: Vec<PathSegment>,
46 bounds: Option<Rect>,
47 flatten_cache: RefCell<Option<FlattenCache>>,
50}
51
52struct FlattenCache {
54 transform: Transform,
55 tolerance: f32,
56 contours: Arc<Vec<Contour>>,
57}
58
59impl Clone for Path {
60 fn clone(&self) -> Self {
61 Path { segments: self.segments.clone(), bounds: self.bounds, flatten_cache: RefCell::new(None) }
64 }
65}
66
67impl PartialEq for Path {
68 fn eq(&self, other: &Self) -> bool {
69 self.segments == other.segments && self.bounds == other.bounds
72 }
73}
74
75impl fmt::Debug for Path {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.debug_struct("Path")
78 .field("segments", &self.segments)
79 .field("bounds", &self.bounds)
80 .finish()
81 }
82}
83
84#[derive(Clone, Debug)]
86pub(crate) struct Contour {
87 pub points: Vec<Point>,
88 pub closed: bool,
89}
90
91impl Path {
92 pub fn segments(&self) -> &[PathSegment] {
94 &self.segments
95 }
96
97 pub fn is_empty(&self) -> bool {
99 self.segments.is_empty()
100 }
101
102 pub fn bounds(&self) -> Option<Rect> {
104 self.bounds
105 }
106
107 pub(crate) fn to_contours(&self, transform: Transform, tolerance: f32) -> Arc<Vec<Contour>> {
116 let tol = tolerance.max(1e-3);
117 if let Some(cache) = self.flatten_cache.borrow().as_ref() {
118 if cache.transform == transform && cache.tolerance == tol {
119 return cache.contours.clone();
120 }
121 }
122 let contours = Arc::new(self.flatten(transform, tol));
123 *self.flatten_cache.borrow_mut() =
124 Some(FlattenCache { transform, tolerance: tol, contours: contours.clone() });
125 contours
126 }
127
128 fn flatten(&self, transform: Transform, tol: f32) -> Vec<Contour> {
131 let mut contours: Vec<Contour> = Vec::new();
132 let mut current: Vec<Point> = Vec::new();
133 let mut start = Point::ZERO;
134 let mut pen = Point::ZERO;
135
136 let map = |p: Point| transform.map_point(p);
137
138 let flush = |contours: &mut Vec<Contour>, pts: &mut Vec<Point>, closed: bool| {
139 if pts.len() >= 2 {
140 contours.push(Contour { points: std::mem::take(pts), closed });
141 } else {
142 pts.clear();
143 }
144 };
145
146 for seg in &self.segments {
147 match *seg {
148 PathSegment::MoveTo(p) => {
149 flush(&mut contours, &mut current, false);
150 let p = map(p);
151 start = p;
152 pen = p;
153 current.push(p);
154 }
155 PathSegment::LineTo(p) => {
156 let p = map(p);
157 if current.is_empty() {
158 current.push(pen);
159 }
160 current.push(p);
161 pen = p;
162 }
163 PathSegment::QuadTo(c, p) => {
164 let c = map(c);
165 let p = map(p);
166 if current.is_empty() {
167 current.push(pen);
168 }
169 flatten_quad(pen, c, p, tol, 0, &mut current);
170 pen = p;
171 }
172 PathSegment::CubicTo(c1, c2, p) => {
173 let c1 = map(c1);
174 let c2 = map(c2);
175 let p = map(p);
176 if current.is_empty() {
177 current.push(pen);
178 }
179 flatten_cubic(pen, c1, c2, p, tol, 0, &mut current);
180 pen = p;
181 }
182 PathSegment::Close => {
183 flush(&mut contours, &mut current, true);
184 pen = start;
185 }
186 }
187 }
188 flush(&mut contours, &mut current, false);
189 contours
190 }
191}
192
193const MAX_FLATTEN_DEPTH: u8 = 16;
194
195#[inline]
197fn dist_to_line(p: Point, a: Point, b: Point) -> f32 {
198 let ab = b - a;
199 let len = ab.length();
200 if len < 1e-6 {
201 (p - a).length()
202 } else {
203 ((p - a).cross(ab)).abs() / len
204 }
205}
206
207fn flatten_quad(p0: Point, p1: Point, p2: Point, tol: f32, depth: u8, out: &mut Vec<Point>) {
208 if depth >= MAX_FLATTEN_DEPTH || dist_to_line(p1, p0, p2) <= tol {
209 out.push(p2);
210 return;
211 }
212 let p01 = p0.lerp(p1, 0.5);
214 let p12 = p1.lerp(p2, 0.5);
215 let mid = p01.lerp(p12, 0.5);
216 flatten_quad(p0, p01, mid, tol, depth + 1, out);
217 flatten_quad(mid, p12, p2, tol, depth + 1, out);
218}
219
220fn flatten_cubic(
221 p0: Point,
222 p1: Point,
223 p2: Point,
224 p3: Point,
225 tol: f32,
226 depth: u8,
227 out: &mut Vec<Point>,
228) {
229 let d = dist_to_line(p1, p0, p3).max(dist_to_line(p2, p0, p3));
230 if depth >= MAX_FLATTEN_DEPTH || d <= tol {
231 out.push(p3);
232 return;
233 }
234 let p01 = p0.lerp(p1, 0.5);
235 let p12 = p1.lerp(p2, 0.5);
236 let p23 = p2.lerp(p3, 0.5);
237 let p012 = p01.lerp(p12, 0.5);
238 let p123 = p12.lerp(p23, 0.5);
239 let mid = p012.lerp(p123, 0.5);
240 flatten_cubic(p0, p01, p012, mid, tol, depth + 1, out);
241 flatten_cubic(mid, p123, p23, p3, tol, depth + 1, out);
242}
243
244#[derive(Clone, Debug, Default)]
246pub struct PathBuilder {
247 segments: Vec<PathSegment>,
248 start: Option<Point>,
249 pen: Option<Point>,
250 min: Option<Point>,
251 max: Option<Point>,
252}
253
254impl PathBuilder {
255 pub fn new() -> Self {
256 PathBuilder::default()
257 }
258
259 fn track(&mut self, p: Point) {
260 self.min = Some(match self.min {
261 Some(m) => Point::new(m.x.min(p.x), m.y.min(p.y)),
262 None => p,
263 });
264 self.max = Some(match self.max {
265 Some(m) => Point::new(m.x.max(p.x), m.y.max(p.y)),
266 None => p,
267 });
268 }
269
270 pub fn move_to(&mut self, x: f32, y: f32) -> &mut Self {
272 let p = Point::new(x, y);
273 self.track(p);
274 self.start = Some(p);
275 self.pen = Some(p);
276 self.segments.push(PathSegment::MoveTo(p));
277 self
278 }
279
280 pub fn line_to(&mut self, x: f32, y: f32) -> &mut Self {
282 let p = Point::new(x, y);
283 if self.pen.is_none() {
284 return self.move_to(x, y);
285 }
286 self.track(p);
287 self.pen = Some(p);
288 self.segments.push(PathSegment::LineTo(p));
289 self
290 }
291
292 pub fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) -> &mut Self {
294 if self.pen.is_none() {
295 self.move_to(cx, cy);
296 }
297 let c = Point::new(cx, cy);
298 let p = Point::new(x, y);
299 self.track(c);
300 self.track(p);
301 self.pen = Some(p);
302 self.segments.push(PathSegment::QuadTo(c, p));
303 self
304 }
305
306 pub fn cubic_to(&mut self, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) -> &mut Self {
308 if self.pen.is_none() {
309 self.move_to(c1x, c1y);
310 }
311 let c1 = Point::new(c1x, c1y);
312 let c2 = Point::new(c2x, c2y);
313 let p = Point::new(x, y);
314 self.track(c1);
315 self.track(c2);
316 self.track(p);
317 self.pen = Some(p);
318 self.segments.push(PathSegment::CubicTo(c1, c2, p));
319 self
320 }
321
322 pub fn close(&mut self) -> &mut Self {
324 if !self.segments.is_empty() {
325 self.segments.push(PathSegment::Close);
326 self.pen = self.start;
327 }
328 self
329 }
330
331 pub fn push_rect(&mut self, rect: Rect) -> &mut Self {
333 self.move_to(rect.left, rect.top)
334 .line_to(rect.right, rect.top)
335 .line_to(rect.right, rect.bottom)
336 .line_to(rect.left, rect.bottom)
337 .close()
338 }
339
340 pub fn push_round_rect(&mut self, rect: Rect, radius: f32) -> &mut Self {
345 let r = radius.min(rect.width() * 0.5).min(rect.height() * 0.5);
346 if !r.is_finite() || r <= 0.0 {
347 return self.push_rect(rect);
348 }
349 const K: f32 = 0.552_284_8;
351 let kr = r * K;
352 let (l, t, rt, b) = (rect.left, rect.top, rect.right, rect.bottom);
353 self.move_to(l + r, t)
354 .line_to(rt - r, t)
355 .cubic_to(rt - r + kr, t, rt, t + r - kr, rt, t + r)
356 .line_to(rt, b - r)
357 .cubic_to(rt, b - r + kr, rt - r + kr, b, rt - r, b)
358 .line_to(l + r, b)
359 .cubic_to(l + r - kr, b, l, b - r + kr, l, b - r)
360 .line_to(l, t + r)
361 .cubic_to(l, t + r - kr, l + r - kr, t, l + r, t)
362 .close()
363 }
364
365 pub fn push_oval(&mut self, rect: Rect) -> &mut Self {
367 const K: f32 = 0.552_284_8; let cx = (rect.left + rect.right) * 0.5;
369 let cy = (rect.top + rect.bottom) * 0.5;
370 let rx = rect.width() * 0.5;
371 let ry = rect.height() * 0.5;
372 let ox = rx * K;
373 let oy = ry * K;
374 self.move_to(cx, rect.top)
375 .cubic_to(cx + ox, rect.top, rect.right, cy - oy, rect.right, cy)
376 .cubic_to(rect.right, cy + oy, cx + ox, rect.bottom, cx, rect.bottom)
377 .cubic_to(cx - ox, rect.bottom, rect.left, cy + oy, rect.left, cy)
378 .cubic_to(rect.left, cy - oy, cx - ox, rect.top, cx, rect.top)
379 .close()
380 }
381
382 pub fn push_circle(&mut self, cx: f32, cy: f32, r: f32) -> &mut Self {
384 if let Some(rect) = Rect::from_ltrb(cx - r, cy - r, cx + r, cy + r) {
385 self.push_oval(rect);
386 }
387 self
388 }
389
390 pub fn finish(self) -> Option<Path> {
392 if self.segments.is_empty() {
393 return None;
394 }
395 let bounds = match (self.min, self.max) {
396 (Some(min), Some(max)) => Rect::from_ltrb(min.x, min.y, max.x, max.y),
397 _ => None,
398 };
399 Some(Path { segments: self.segments, bounds, flatten_cache: RefCell::new(None) })
400 }
401
402 pub(crate) fn push_path_transformed(
409 &mut self,
410 segments: &[PathSegment],
411 transform: &Transform,
412 ) -> &mut Self {
413 for seg in segments {
414 match *seg {
415 PathSegment::MoveTo(p) => {
416 let p = transform.map_point(p);
417 self.move_to(p.x, p.y);
418 }
419 PathSegment::LineTo(p) => {
420 let p = transform.map_point(p);
421 self.line_to(p.x, p.y);
422 }
423 PathSegment::QuadTo(c, p) => {
424 let c = transform.map_point(c);
425 let p = transform.map_point(p);
426 self.quad_to(c.x, c.y, p.x, p.y);
427 }
428 PathSegment::CubicTo(c1, c2, p) => {
429 let c1 = transform.map_point(c1);
430 let c2 = transform.map_point(c2);
431 let p = transform.map_point(p);
432 self.cubic_to(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
433 }
434 PathSegment::Close => {
435 self.close();
436 }
437 }
438 }
439 self
440 }
441
442 pub fn from_rect(rect: Rect) -> Path {
444 let mut b = PathBuilder::new();
445 b.push_rect(rect);
446 b.finish().unwrap()
447 }
448
449 pub fn from_circle(cx: f32, cy: f32, r: f32) -> Option<Path> {
451 let mut b = PathBuilder::new();
452 b.push_circle(cx, cy, r);
453 b.finish()
454 }
455
456 pub fn from_round_rect(rect: Rect, radius: f32) -> Path {
458 let mut b = PathBuilder::new();
459 b.push_round_rect(rect, radius);
460 b.finish().expect("a non-empty rounded rectangle contour")
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn builds_rect_contour() {
470 let path = PathBuilder::from_rect(Rect::from_xywh(1.0, 2.0, 10.0, 4.0).unwrap());
471 let contours = path.to_contours(Transform::identity(), 0.1);
472 assert_eq!(contours.len(), 1);
473 assert!(contours[0].closed);
474 assert_eq!(contours[0].points.len(), 4);
476 }
477
478 #[test]
479 fn round_rect_zero_radius_is_plain_rect() {
480 let rect = Rect::from_xywh(0.0, 0.0, 10.0, 10.0).unwrap();
481 let plain = PathBuilder::from_rect(rect);
482 let rounded = PathBuilder::from_round_rect(rect, 0.0);
483 assert_eq!(plain.segments(), rounded.segments());
484 }
485
486 #[test]
487 fn round_rect_radius_clamped_to_half() {
488 let rect = Rect::from_xywh(0.0, 0.0, 10.0, 10.0).unwrap();
490 let path = PathBuilder::from_round_rect(rect, 100.0);
491 let contours = path.to_contours(Transform::identity(), 0.1);
492 assert_eq!(contours.len(), 1);
493 assert!(contours[0].closed);
494 }
495
496 #[test]
499 fn flatten_cache_reuses_and_invalidates() {
500 let mut b = PathBuilder::new();
501 b.move_to(0.0, 0.0).cubic_to(0.0, 40.0, 40.0, 40.0, 40.0, 0.0);
502 let path = b.finish().unwrap();
503
504 let a = path.to_contours(Transform::identity(), 0.1);
505 let again = path.to_contours(Transform::identity(), 0.1);
506 assert!(Arc::ptr_eq(&a, &again));
508
509 let scaled = path.to_contours(Transform::from_scale(4.0, 4.0), 0.1);
511 assert!(!Arc::ptr_eq(&a, &scaled));
512 assert!(scaled[0].points.len() >= a[0].points.len());
514
515 let clone = path.clone();
517 let from_clone = clone.to_contours(Transform::identity(), 0.1);
518 assert!(!Arc::ptr_eq(&a, &from_clone));
519 assert_eq!(from_clone[0].points.len(), a[0].points.len());
520 }
521
522 #[test]
523 fn flattens_curve_into_many_points() {
524 let mut b = PathBuilder::new();
525 b.move_to(0.0, 0.0).cubic_to(0.0, 100.0, 100.0, 100.0, 100.0, 0.0);
526 let path = b.finish().unwrap();
527 let contours = path.to_contours(Transform::identity(), 0.1);
528 assert!(contours[0].points.len() > 5);
529 }
530}