1use crate::point::PointF;
6use crate::rect::Rect;
7
8#[derive(Debug, Clone, PartialEq)]
10pub enum PathCommand {
11 MoveTo(PointF),
13 LineTo(PointF),
15 QuadTo {
17 control: PointF,
19 end: PointF,
21 },
22 CubicTo {
24 control1: PointF,
26 control2: PointF,
28 end: PointF,
30 },
31 ArcTo {
33 oval: Rect,
35 start_angle: f32,
37 sweep_angle: f32,
39 force_move_to: bool,
41 },
42 Close,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum PathFillType {
49 Winding,
51 EvenOdd,
53}
54
55impl Default for PathFillType {
56 fn default() -> Self {
57 PathFillType::Winding
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum PathDirection {
64 CW,
66 CCW,
68}
69
70#[derive(Debug, Clone)]
74pub struct Path {
75 commands: Vec<PathCommand>,
77 fill_type: PathFillType,
79 current_point: Option<PointF>,
81 last_move_to: Option<PointF>,
83}
84
85impl Path {
86 pub fn new() -> Self {
88 Self {
89 commands: Vec::new(),
90 fill_type: PathFillType::default(),
91 current_point: None,
92 last_move_to: None,
93 }
94 }
95
96 pub fn move_to(&mut self, point: impl Into<PointF>) {
98 let point = point.into();
99 self.commands.push(PathCommand::MoveTo(point));
100 self.current_point = Some(point);
101 self.last_move_to = Some(point);
102 }
103
104 pub fn line_to(&mut self, point: impl Into<PointF>) {
106 let point = point.into();
107 self.commands.push(PathCommand::LineTo(point));
108 self.current_point = Some(point);
109 }
110
111 pub fn quad_to(&mut self, control: impl Into<PointF>, end: impl Into<PointF>) {
113 let control = control.into();
114 let end = end.into();
115 self.commands.push(PathCommand::QuadTo { control, end });
116 self.current_point = Some(end);
117 }
118
119 pub fn cubic_to(
121 &mut self,
122 control1: impl Into<PointF>,
123 control2: impl Into<PointF>,
124 end: impl Into<PointF>,
125 ) {
126 let control1 = control1.into();
127 let control2 = control2.into();
128 let end = end.into();
129 self.commands.push(PathCommand::CubicTo {
130 control1,
131 control2,
132 end,
133 });
134 self.current_point = Some(end);
135 }
136
137 pub fn arc_to(&mut self, oval: Rect, start_angle: f32, sweep_angle: f32, force_move_to: bool) {
139 self.commands.push(PathCommand::ArcTo {
140 oval,
141 start_angle,
142 sweep_angle,
143 force_move_to,
144 });
145 }
146
147 pub fn close(&mut self) {
149 if self.last_move_to.is_some() {
150 self.commands.push(PathCommand::Close);
151 self.current_point = self.last_move_to;
152 }
153 }
154
155 pub fn add_rect(&mut self, rect: Rect, dir: PathDirection) {
157 let (x, y, w, h) = (
158 rect.x as f32,
159 rect.y as f32,
160 rect.width as f32,
161 rect.height as f32,
162 );
163
164 match dir {
165 PathDirection::CW => {
166 self.move_to(PointF::new(x, y));
167 self.line_to(PointF::new(x + w, y));
168 self.line_to(PointF::new(x + w, y + h));
169 self.line_to(PointF::new(x, y + h));
170 }
171 PathDirection::CCW => {
172 self.move_to(PointF::new(x, y));
173 self.line_to(PointF::new(x, y + h));
174 self.line_to(PointF::new(x + w, y + h));
175 self.line_to(PointF::new(x + w, y));
176 }
177 }
178 self.close();
179 }
180
181 pub fn add_circle(&mut self, center: impl Into<PointF>, radius: f32, dir: PathDirection) {
183 let center = center.into();
184 let rect = Rect::new(
185 (center.x - radius) as i32,
186 (center.y - radius) as i32,
187 (radius * 2.0) as u32,
188 (radius * 2.0) as u32,
189 );
190 self.add_oval(rect, dir);
191 }
192
193 pub fn add_oval(&mut self, oval: Rect, dir: PathDirection) {
195 let (cx, cy) = (
196 oval.x as f32 + oval.width as f32 / 2.0,
197 oval.y as f32 + oval.height as f32 / 2.0,
198 );
199 let (rx, ry) = (oval.width as f32 / 2.0, oval.height as f32 / 2.0);
200
201 let kappa = 0.5522847498; let ox = rx * kappa;
204 let oy = ry * kappa;
205
206 match dir {
207 PathDirection::CW => {
208 self.move_to(PointF::new(cx - rx, cy));
209 self.cubic_to(
210 PointF::new(cx - rx, cy - oy),
211 PointF::new(cx - ox, cy - ry),
212 PointF::new(cx, cy - ry),
213 );
214 self.cubic_to(
215 PointF::new(cx + ox, cy - ry),
216 PointF::new(cx + rx, cy - oy),
217 PointF::new(cx + rx, cy),
218 );
219 self.cubic_to(
220 PointF::new(cx + rx, cy + oy),
221 PointF::new(cx + ox, cy + ry),
222 PointF::new(cx, cy + ry),
223 );
224 self.cubic_to(
225 PointF::new(cx - ox, cy + ry),
226 PointF::new(cx - rx, cy + oy),
227 PointF::new(cx - rx, cy),
228 );
229 }
230 PathDirection::CCW => {
231 self.move_to(PointF::new(cx - rx, cy));
232 self.cubic_to(
233 PointF::new(cx - rx, cy + oy),
234 PointF::new(cx - ox, cy + ry),
235 PointF::new(cx, cy + ry),
236 );
237 self.cubic_to(
238 PointF::new(cx + ox, cy + ry),
239 PointF::new(cx + rx, cy + oy),
240 PointF::new(cx + rx, cy),
241 );
242 self.cubic_to(
243 PointF::new(cx + rx, cy - oy),
244 PointF::new(cx + ox, cy - ry),
245 PointF::new(cx, cy - ry),
246 );
247 self.cubic_to(
248 PointF::new(cx - ox, cy - ry),
249 PointF::new(cx - rx, cy - oy),
250 PointF::new(cx - rx, cy),
251 );
252 }
253 }
254 self.close();
255 }
256
257 pub fn add_rounded_rect(&mut self, rect: Rect, radius: f32, dir: PathDirection) {
259 let (x, y, w, h) = (
260 rect.x as f32,
261 rect.y as f32,
262 rect.width as f32,
263 rect.height as f32,
264 );
265 let r = radius.min(w / 2.0).min(h / 2.0);
266
267 if r <= 0.0 {
268 self.add_rect(rect, dir);
269 return;
270 }
271
272 let kappa = 0.5522847498;
273 let o = r * kappa;
274
275 match dir {
276 PathDirection::CW => {
277 self.move_to(PointF::new(x + r, y));
278 self.line_to(PointF::new(x + w - r, y));
279 self.cubic_to(
280 PointF::new(x + w - r + o, y),
281 PointF::new(x + w, y + r - o),
282 PointF::new(x + w, y + r),
283 );
284 self.line_to(PointF::new(x + w, y + h - r));
285 self.cubic_to(
286 PointF::new(x + w, y + h - r + o),
287 PointF::new(x + w - r + o, y + h),
288 PointF::new(x + w - r, y + h),
289 );
290 self.line_to(PointF::new(x + r, y + h));
291 self.cubic_to(
292 PointF::new(x + r - o, y + h),
293 PointF::new(x, y + h - r + o),
294 PointF::new(x, y + h - r),
295 );
296 self.line_to(PointF::new(x, y + r));
297 self.cubic_to(
298 PointF::new(x, y + r - o),
299 PointF::new(x + r - o, y),
300 PointF::new(x + r, y),
301 );
302 }
303 PathDirection::CCW => {
304 self.move_to(PointF::new(x + r, y));
305 self.cubic_to(
306 PointF::new(x + r - o, y),
307 PointF::new(x, y + r - o),
308 PointF::new(x, y + r),
309 );
310 self.line_to(PointF::new(x, y + h - r));
311 self.cubic_to(
312 PointF::new(x, y + h - r + o),
313 PointF::new(x + r - o, y + h),
314 PointF::new(x + r, y + h),
315 );
316 self.line_to(PointF::new(x + w - r, y + h));
317 self.cubic_to(
318 PointF::new(x + w - r + o, y + h),
319 PointF::new(x + w, y + h - r + o),
320 PointF::new(x + w, y + h - r),
321 );
322 self.line_to(PointF::new(x + w, y + r));
323 self.cubic_to(
324 PointF::new(x + w, y + r - o),
325 PointF::new(x + w - r + o, y),
326 PointF::new(x + w - r, y),
327 );
328 }
329 }
330 self.close();
331 }
332
333 pub fn reset(&mut self) {
335 self.commands.clear();
336 self.current_point = None;
337 self.last_move_to = None;
338 }
339
340 pub fn set_fill_type(&mut self, fill_type: PathFillType) {
342 self.fill_type = fill_type;
343 }
344
345 pub fn fill_type(&self) -> PathFillType {
347 self.fill_type
348 }
349
350 pub fn commands(&self) -> &[PathCommand] {
352 &self.commands
353 }
354
355 pub fn is_empty(&self) -> bool {
357 self.commands.is_empty()
358 }
359
360 pub fn count_points(&self) -> usize {
362 self.commands.len()
363 }
364
365 pub fn current_point(&self) -> Option<PointF> {
367 self.current_point
368 }
369
370 pub fn bounds(&self) -> Option<Rect> {
372 if self.commands.is_empty() {
373 return None;
374 }
375
376 let mut min_x = f32::MAX;
377 let mut min_y = f32::MAX;
378 let mut max_x = f32::MIN;
379 let mut max_y = f32::MIN;
380
381 for cmd in &self.commands {
382 match cmd {
383 PathCommand::MoveTo(p) | PathCommand::LineTo(p) => {
384 min_x = min_x.min(p.x);
385 min_y = min_y.min(p.y);
386 max_x = max_x.max(p.x);
387 max_y = max_y.max(p.y);
388 }
389 PathCommand::QuadTo { control, end } => {
390 min_x = min_x.min(control.x).min(end.x);
391 min_y = min_y.min(control.y).min(end.y);
392 max_x = max_x.max(control.x).max(end.x);
393 max_y = max_y.max(control.y).max(end.y);
394 }
395 PathCommand::CubicTo {
396 control1,
397 control2,
398 end,
399 } => {
400 min_x = min_x.min(control1.x).min(control2.x).min(end.x);
401 min_y = min_y.min(control1.y).min(control2.y).min(end.y);
402 max_x = max_x.max(control1.x).max(control2.x).max(end.x);
403 max_y = max_y.max(control1.y).max(control2.y).max(end.y);
404 }
405 PathCommand::ArcTo { oval, .. } => {
406 min_x = min_x.min(oval.x as f32);
407 min_y = min_y.min(oval.y as f32);
408 max_x = max_x.max((oval.x + oval.width as i32) as f32);
409 max_y = max_y.max((oval.y + oval.height as i32) as f32);
410 }
411 PathCommand::Close => {}
412 }
413 }
414
415 if min_x <= max_x && min_y <= max_y {
416 Some(Rect::new(
417 min_x as i32,
418 min_y as i32,
419 (max_x - min_x) as u32,
420 (max_y - min_y) as u32,
421 ))
422 } else {
423 None
424 }
425 }
426
427 pub fn transform(&self, matrix: &crate::matrix::Matrix) -> Path {
429 let mut new_path = Path {
430 commands: Vec::new(),
431 fill_type: self.fill_type,
432 current_point: None,
433 last_move_to: None,
434 };
435
436 for cmd in &self.commands {
437 match cmd {
438 PathCommand::MoveTo(p) => {
439 let (x, y) = matrix.map_point(p.x, p.y);
440 let new_point = PointF { x, y };
441 new_path.commands.push(PathCommand::MoveTo(new_point));
442 new_path.current_point = Some(new_point);
443 new_path.last_move_to = Some(new_point);
444 }
445 PathCommand::LineTo(p) => {
446 let (x, y) = matrix.map_point(p.x, p.y);
447 let new_point = PointF { x, y };
448 new_path.commands.push(PathCommand::LineTo(new_point));
449 new_path.current_point = Some(new_point);
450 }
451 PathCommand::QuadTo { control, end } => {
452 let (cx, cy) = matrix.map_point(control.x, control.y);
453 let (ex, ey) = matrix.map_point(end.x, end.y);
454 new_path.commands.push(PathCommand::QuadTo {
455 control: PointF { x: cx, y: cy },
456 end: PointF { x: ex, y: ey },
457 });
458 new_path.current_point = Some(PointF { x: ex, y: ey });
459 }
460 PathCommand::CubicTo {
461 control1,
462 control2,
463 end,
464 } => {
465 let (c1x, c1y) = matrix.map_point(control1.x, control1.y);
466 let (c2x, c2y) = matrix.map_point(control2.x, control2.y);
467 let (ex, ey) = matrix.map_point(end.x, end.y);
468 new_path.commands.push(PathCommand::CubicTo {
469 control1: PointF { x: c1x, y: c1y },
470 control2: PointF { x: c2x, y: c2y },
471 end: PointF { x: ex, y: ey },
472 });
473 new_path.current_point = Some(PointF { x: ex, y: ey });
474 }
475 PathCommand::ArcTo {
476 oval,
477 start_angle,
478 sweep_angle,
479 force_move_to,
480 } => {
481 let (x1, y1) = matrix.map_point(oval.x as f32, oval.y as f32);
483 let (x2, y2) = matrix.map_point(
484 (oval.x + oval.width as i32) as f32,
485 (oval.y + oval.height as i32) as f32,
486 );
487 let new_oval = crate::rect::Rect::new(
488 x1 as i32,
489 y1 as i32,
490 (x2 - x1).abs() as u32,
491 (y2 - y1).abs() as u32,
492 );
493 new_path.commands.push(PathCommand::ArcTo {
494 oval: new_oval,
495 start_angle: *start_angle,
496 sweep_angle: *sweep_angle,
497 force_move_to: *force_move_to,
498 });
499 }
500 PathCommand::Close => {
501 new_path.commands.push(PathCommand::Close);
502 new_path.current_point = new_path.last_move_to;
503 }
504 }
505 }
506
507 new_path
508 }
509
510 pub fn contains(&self, x: f32, y: f32) -> bool {
512 let mut crossings = 0;
514 let mut current_point = PointF { x: 0.0, y: 0.0 };
515 let mut start_point = PointF { x: 0.0, y: 0.0 };
516
517 for cmd in &self.commands {
518 match cmd {
519 PathCommand::MoveTo(p) => {
520 current_point = *p;
521 start_point = *p;
522 }
523 PathCommand::LineTo(p) => {
524 if self.line_crosses_ray(current_point, *p, x, y) {
525 crossings += 1;
526 }
527 current_point = *p;
528 }
529 PathCommand::Close => {
530 if self.line_crosses_ray(current_point, start_point, x, y) {
531 crossings += 1;
532 }
533 current_point = start_point;
534 }
535 _ => {
536 }
539 }
540 }
541
542 match self.fill_type {
544 PathFillType::Winding => crossings != 0,
545 PathFillType::EvenOdd => crossings % 2 != 0,
546 }
547 }
548
549 fn line_crosses_ray(&self, p1: PointF, p2: PointF, x: f32, y: f32) -> bool {
551 if (p1.y > y && p2.y > y) || (p1.y < y && p2.y < y) {
553 return false;
554 }
555
556 if (p1.y - p2.y).abs() < 1e-6 {
558 return false;
559 }
560
561 let t = (y - p1.y) / (p2.y - p1.y);
563 let intersection_x = p1.x + t * (p2.x - p1.x);
564
565 intersection_x > x
567 }
568}
569
570impl Default for Path {
571 fn default() -> Self {
572 Self::new()
573 }
574}
575
576#[cfg(test)]
579mod tests {
580 use super::*;
581
582 #[test]
583 fn test_path_creation() {
584 let path = Path::new();
585 assert!(path.is_empty());
586 assert_eq!(path.count_points(), 0);
587 }
588
589 #[test]
590 fn test_path_move_to() {
591 let mut path = Path::new();
592 path.move_to(PointF::new(10.0, 20.0));
593 assert_eq!(path.count_points(), 1);
594 assert_eq!(path.current_point(), Some(PointF::new(10.0, 20.0)));
595 }
596
597 #[test]
598 fn test_path_line_to() {
599 let mut path = Path::new();
600 path.move_to(PointF::new(0.0, 0.0));
601 path.line_to(PointF::new(10.0, 10.0));
602 assert_eq!(path.count_points(), 2);
603 }
604
605 #[test]
606 fn test_path_rect() {
607 let mut path = Path::new();
608 path.add_rect(Rect::new(10, 10, 100, 50), PathDirection::CW);
609 assert!(!path.is_empty());
610 }
611
612 #[test]
613 fn test_path_circle() {
614 let mut path = Path::new();
615 path.add_circle(PointF::new(50.0, 50.0), 25.0, PathDirection::CW);
616 assert!(!path.is_empty());
617 }
618
619 #[test]
620 fn test_path_bounds() {
621 let mut path = Path::new();
622 path.move_to(PointF::new(10.0, 20.0));
623 path.line_to(PointF::new(100.0, 80.0));
624 let bounds = path.bounds().unwrap();
625 assert_eq!(bounds.x, 10);
626 assert_eq!(bounds.y, 20);
627 }
628}