1use crate::geometry::{Ctm, Point};
2
3#[derive(Debug, Clone, PartialEq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum PathSegment {
7 MoveTo(Point),
9 LineTo(Point),
11 CurveTo {
13 cp1: Point,
15 cp2: Point,
17 end: Point,
19 },
20 ClosePath,
22}
23
24#[derive(Debug, Clone, PartialEq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct Path {
28 pub segments: Vec<PathSegment>,
30}
31
32#[derive(Debug, Clone)]
36pub struct PathBuilder {
37 segments: Vec<PathSegment>,
38 current_point: Option<Point>,
39 subpath_start: Option<Point>,
40 ctm: Ctm,
41}
42
43impl PathBuilder {
44 pub fn new(ctm: Ctm) -> Self {
46 Self {
47 segments: Vec::new(),
48 current_point: None,
49 subpath_start: None,
50 ctm,
51 }
52 }
53
54 pub fn set_ctm(&mut self, ctm: Ctm) {
56 self.ctm = ctm;
57 }
58
59 pub fn ctm(&self) -> &Ctm {
61 &self.ctm
62 }
63
64 pub fn move_to(&mut self, x: f64, y: f64) {
66 let p = self.ctm.transform_point(Point::new(x, y));
67 self.segments.push(PathSegment::MoveTo(p));
68 self.current_point = Some(p);
69 self.subpath_start = Some(p);
70 }
71
72 pub fn line_to(&mut self, x: f64, y: f64) {
74 let p = self.ctm.transform_point(Point::new(x, y));
75 self.segments.push(PathSegment::LineTo(p));
76 self.current_point = Some(p);
77 }
78
79 pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) {
81 let cp1 = self.ctm.transform_point(Point::new(x1, y1));
82 let cp2 = self.ctm.transform_point(Point::new(x2, y2));
83 let end = self.ctm.transform_point(Point::new(x3, y3));
84 self.segments.push(PathSegment::CurveTo { cp1, cp2, end });
85 self.current_point = Some(end);
86 }
87
88 pub fn curve_to_v(&mut self, x2: f64, y2: f64, x3: f64, y3: f64) {
90 let Some(cp1) = self.current_point else {
91 return;
92 };
93 let cp2 = self.ctm.transform_point(Point::new(x2, y2));
94 let end = self.ctm.transform_point(Point::new(x3, y3));
95 self.segments.push(PathSegment::CurveTo { cp1, cp2, end });
96 self.current_point = Some(end);
97 }
98
99 pub fn curve_to_y(&mut self, x1: f64, y1: f64, x3: f64, y3: f64) {
101 let cp1 = self.ctm.transform_point(Point::new(x1, y1));
102 let end = self.ctm.transform_point(Point::new(x3, y3));
103 self.segments
104 .push(PathSegment::CurveTo { cp1, cp2: end, end });
105 self.current_point = Some(end);
106 }
107
108 pub fn close_path(&mut self) {
110 self.segments.push(PathSegment::ClosePath);
111 if let Some(start) = self.subpath_start {
112 self.current_point = Some(start);
113 }
114 }
115
116 pub fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) {
118 self.move_to(x, y);
119 self.line_to(x + width, y);
120 self.line_to(x + width, y + height);
121 self.line_to(x, y + height);
122 self.close_path();
123 }
124
125 pub fn current_point(&self) -> Option<Point> {
127 self.current_point
128 }
129
130 pub fn build(self) -> Path {
132 Path {
133 segments: self.segments,
134 }
135 }
136
137 pub fn is_empty(&self) -> bool {
139 self.segments.is_empty()
140 }
141
142 pub fn take_and_reset(&mut self) -> Path {
147 let segments = std::mem::take(&mut self.segments);
148 self.current_point = None;
149 self.subpath_start = None;
150 Path { segments }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 fn assert_point_approx(p: Point, x: f64, y: f64) {
159 assert!((p.x - x).abs() < 1e-10, "x: expected {x}, got {}", p.x);
160 assert!((p.y - y).abs() < 1e-10, "y: expected {y}, got {}", p.y);
161 }
162
163 #[test]
166 fn test_new_builder_is_empty() {
167 let builder = PathBuilder::new(Ctm::identity());
168 assert!(builder.is_empty());
169 assert!(builder.current_point().is_none());
170 }
171
172 #[test]
175 fn test_move_to() {
176 let mut builder = PathBuilder::new(Ctm::identity());
177 builder.move_to(10.0, 20.0);
178
179 assert!(!builder.is_empty());
180 let cp = builder.current_point().unwrap();
181 assert_point_approx(cp, 10.0, 20.0);
182
183 let path = builder.build();
184 assert_eq!(path.segments.len(), 1);
185 assert_eq!(
186 path.segments[0],
187 PathSegment::MoveTo(Point::new(10.0, 20.0))
188 );
189 }
190
191 #[test]
192 fn test_move_to_updates_subpath_start() {
193 let mut builder = PathBuilder::new(Ctm::identity());
194 builder.move_to(10.0, 20.0);
195 builder.line_to(30.0, 40.0);
196 builder.close_path();
197
198 let cp = builder.current_point().unwrap();
200 assert_point_approx(cp, 10.0, 20.0);
201 }
202
203 #[test]
206 fn test_line_to() {
207 let mut builder = PathBuilder::new(Ctm::identity());
208 builder.move_to(0.0, 0.0);
209 builder.line_to(100.0, 50.0);
210
211 let cp = builder.current_point().unwrap();
212 assert_point_approx(cp, 100.0, 50.0);
213
214 let path = builder.build();
215 assert_eq!(path.segments.len(), 2);
216 assert_eq!(
217 path.segments[1],
218 PathSegment::LineTo(Point::new(100.0, 50.0))
219 );
220 }
221
222 #[test]
225 fn test_curve_to() {
226 let mut builder = PathBuilder::new(Ctm::identity());
227 builder.move_to(0.0, 0.0);
228 builder.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
229
230 let cp = builder.current_point().unwrap();
231 assert_point_approx(cp, 50.0, 60.0);
232
233 let path = builder.build();
234 assert_eq!(path.segments.len(), 2);
235 assert_eq!(
236 path.segments[1],
237 PathSegment::CurveTo {
238 cp1: Point::new(10.0, 20.0),
239 cp2: Point::new(30.0, 40.0),
240 end: Point::new(50.0, 60.0),
241 }
242 );
243 }
244
245 #[test]
248 fn test_curve_to_v() {
249 let mut builder = PathBuilder::new(Ctm::identity());
250 builder.move_to(5.0, 10.0);
251 builder.curve_to_v(30.0, 40.0, 50.0, 60.0);
252
253 let cp = builder.current_point().unwrap();
254 assert_point_approx(cp, 50.0, 60.0);
255
256 let path = builder.build();
257 assert_eq!(path.segments.len(), 2);
258 assert_eq!(
259 path.segments[1],
260 PathSegment::CurveTo {
261 cp1: Point::new(5.0, 10.0), cp2: Point::new(30.0, 40.0),
263 end: Point::new(50.0, 60.0),
264 }
265 );
266 }
267
268 #[test]
269 fn test_curve_to_v_without_current_point_is_noop() {
270 let mut builder = PathBuilder::new(Ctm::identity());
271 builder.curve_to_v(30.0, 40.0, 50.0, 60.0);
272
273 assert!(builder.is_empty());
274 assert!(builder.current_point().is_none());
275 }
276
277 #[test]
280 fn test_curve_to_y() {
281 let mut builder = PathBuilder::new(Ctm::identity());
282 builder.move_to(0.0, 0.0);
283 builder.curve_to_y(10.0, 20.0, 50.0, 60.0);
284
285 let cp = builder.current_point().unwrap();
286 assert_point_approx(cp, 50.0, 60.0);
287
288 let path = builder.build();
289 assert_eq!(path.segments.len(), 2);
290 assert_eq!(
291 path.segments[1],
292 PathSegment::CurveTo {
293 cp1: Point::new(10.0, 20.0),
294 cp2: Point::new(50.0, 60.0), end: Point::new(50.0, 60.0),
296 }
297 );
298 }
299
300 #[test]
303 fn test_close_path() {
304 let mut builder = PathBuilder::new(Ctm::identity());
305 builder.move_to(10.0, 20.0);
306 builder.line_to(30.0, 40.0);
307 builder.line_to(50.0, 20.0);
308 builder.close_path();
309
310 let cp = builder.current_point().unwrap();
312 assert_point_approx(cp, 10.0, 20.0);
313
314 let path = builder.build();
315 assert_eq!(path.segments.len(), 4);
316 assert_eq!(path.segments[3], PathSegment::ClosePath);
317 }
318
319 #[test]
322 fn test_rectangle() {
323 let mut builder = PathBuilder::new(Ctm::identity());
324 builder.rectangle(10.0, 20.0, 100.0, 50.0);
325
326 let path = builder.build();
327 assert_eq!(path.segments.len(), 5);
329 assert_eq!(
330 path.segments[0],
331 PathSegment::MoveTo(Point::new(10.0, 20.0))
332 );
333 assert_eq!(
334 path.segments[1],
335 PathSegment::LineTo(Point::new(110.0, 20.0))
336 );
337 assert_eq!(
338 path.segments[2],
339 PathSegment::LineTo(Point::new(110.0, 70.0))
340 );
341 assert_eq!(
342 path.segments[3],
343 PathSegment::LineTo(Point::new(10.0, 70.0))
344 );
345 assert_eq!(path.segments[4], PathSegment::ClosePath);
346 }
347
348 #[test]
349 fn test_rectangle_current_point_at_start() {
350 let mut builder = PathBuilder::new(Ctm::identity());
351 builder.rectangle(10.0, 20.0, 100.0, 50.0);
352
353 let cp = builder.current_point().unwrap();
355 assert_point_approx(cp, 10.0, 20.0);
356 }
357
358 #[test]
361 fn test_combined_path_triangle() {
362 let mut builder = PathBuilder::new(Ctm::identity());
363 builder.move_to(0.0, 0.0);
364 builder.line_to(100.0, 0.0);
365 builder.line_to(50.0, 80.0);
366 builder.close_path();
367
368 let path = builder.build();
369 assert_eq!(path.segments.len(), 4);
370 assert_eq!(path.segments[0], PathSegment::MoveTo(Point::new(0.0, 0.0)));
371 assert_eq!(
372 path.segments[1],
373 PathSegment::LineTo(Point::new(100.0, 0.0))
374 );
375 assert_eq!(
376 path.segments[2],
377 PathSegment::LineTo(Point::new(50.0, 80.0))
378 );
379 assert_eq!(path.segments[3], PathSegment::ClosePath);
380 }
381
382 #[test]
383 fn test_combined_path_with_curves() {
384 let mut builder = PathBuilder::new(Ctm::identity());
385 builder.move_to(0.0, 0.0);
386 builder.line_to(50.0, 0.0);
387 builder.curve_to(60.0, 0.0, 70.0, 10.0, 70.0, 20.0);
388 builder.line_to(70.0, 50.0);
389 builder.close_path();
390
391 let path = builder.build();
392 assert_eq!(path.segments.len(), 5);
393 }
394
395 #[test]
396 fn test_multiple_subpaths() {
397 let mut builder = PathBuilder::new(Ctm::identity());
398 builder.move_to(0.0, 0.0);
400 builder.line_to(100.0, 0.0);
401 builder.move_to(0.0, 50.0);
403 builder.line_to(100.0, 50.0);
404
405 let path = builder.build();
406 assert_eq!(path.segments.len(), 4);
407
408 }
410
411 #[test]
412 fn test_multiple_subpaths_close_returns_to_latest_start() {
413 let mut builder = PathBuilder::new(Ctm::identity());
414 builder.move_to(0.0, 0.0);
415 builder.line_to(100.0, 100.0);
416 builder.move_to(200.0, 200.0);
417 builder.line_to(300.0, 300.0);
418 builder.close_path();
419
420 let cp = builder.current_point().unwrap();
422 assert_point_approx(cp, 200.0, 200.0);
423 }
424
425 #[test]
428 fn test_ctm_translation_moveto() {
429 let ctm = Ctm::new(1.0, 0.0, 0.0, 1.0, 100.0, 200.0);
431 let mut builder = PathBuilder::new(ctm);
432 builder.move_to(10.0, 20.0);
433
434 let cp = builder.current_point().unwrap();
435 assert_point_approx(cp, 110.0, 220.0);
436 }
437
438 #[test]
439 fn test_ctm_scaling_lineto() {
440 let ctm = Ctm::new(2.0, 0.0, 0.0, 2.0, 0.0, 0.0);
442 let mut builder = PathBuilder::new(ctm);
443 builder.move_to(5.0, 10.0);
444 builder.line_to(15.0, 25.0);
445
446 let path = builder.build();
447 assert_eq!(
448 path.segments[0],
449 PathSegment::MoveTo(Point::new(10.0, 20.0))
450 );
451 assert_eq!(
452 path.segments[1],
453 PathSegment::LineTo(Point::new(30.0, 50.0))
454 );
455 }
456
457 #[test]
458 fn test_ctm_transformed_rectangle() {
459 let ctm = Ctm::new(2.0, 0.0, 0.0, 2.0, 10.0, 10.0);
461 let mut builder = PathBuilder::new(ctm);
462 builder.rectangle(0.0, 0.0, 50.0, 30.0);
463
464 let path = builder.build();
465 assert_eq!(
467 path.segments[0],
468 PathSegment::MoveTo(Point::new(10.0, 10.0))
469 );
470 assert_eq!(
472 path.segments[1],
473 PathSegment::LineTo(Point::new(110.0, 10.0))
474 );
475 assert_eq!(
477 path.segments[2],
478 PathSegment::LineTo(Point::new(110.0, 70.0))
479 );
480 assert_eq!(
482 path.segments[3],
483 PathSegment::LineTo(Point::new(10.0, 70.0))
484 );
485 assert_eq!(path.segments[4], PathSegment::ClosePath);
486 }
487
488 #[test]
489 fn test_ctm_transformed_curveto() {
490 let ctm = Ctm::new(2.0, 0.0, 0.0, 3.0, 0.0, 0.0);
492 let mut builder = PathBuilder::new(ctm);
493 builder.move_to(0.0, 0.0);
494 builder.curve_to(10.0, 10.0, 20.0, 20.0, 30.0, 30.0);
495
496 let path = builder.build();
497 assert_eq!(
498 path.segments[1],
499 PathSegment::CurveTo {
500 cp1: Point::new(20.0, 30.0),
501 cp2: Point::new(40.0, 60.0),
502 end: Point::new(60.0, 90.0),
503 }
504 );
505 }
506
507 #[test]
508 fn test_ctm_transformed_curve_to_v() {
509 let ctm = Ctm::new(1.0, 0.0, 0.0, 1.0, 100.0, 0.0);
511 let mut builder = PathBuilder::new(ctm);
512 builder.move_to(5.0, 10.0); builder.curve_to_v(30.0, 40.0, 50.0, 60.0);
514
515 let path = builder.build();
516 assert_eq!(
517 path.segments[1],
518 PathSegment::CurveTo {
519 cp1: Point::new(105.0, 10.0), cp2: Point::new(130.0, 40.0),
521 end: Point::new(150.0, 60.0),
522 }
523 );
524 }
525
526 #[test]
527 fn test_ctm_transformed_curve_to_y() {
528 let ctm = Ctm::new(0.5, 0.0, 0.0, 0.5, 0.0, 0.0);
530 let mut builder = PathBuilder::new(ctm);
531 builder.move_to(0.0, 0.0);
532 builder.curve_to_y(20.0, 40.0, 60.0, 80.0);
533
534 let path = builder.build();
535 assert_eq!(
536 path.segments[1],
537 PathSegment::CurveTo {
538 cp1: Point::new(10.0, 20.0),
539 cp2: Point::new(30.0, 40.0), end: Point::new(30.0, 40.0),
541 }
542 );
543 }
544
545 #[test]
546 fn test_ctm_close_path_returns_to_transformed_start() {
547 let ctm = Ctm::new(1.0, 0.0, 0.0, 1.0, 50.0, 50.0);
548 let mut builder = PathBuilder::new(ctm);
549 builder.move_to(10.0, 20.0); builder.line_to(100.0, 100.0);
551 builder.close_path();
552
553 let cp = builder.current_point().unwrap();
554 assert_point_approx(cp, 60.0, 70.0);
555 }
556
557 #[test]
558 fn test_set_ctm() {
559 let mut builder = PathBuilder::new(Ctm::identity());
560 builder.move_to(10.0, 20.0); builder.set_ctm(Ctm::new(1.0, 0.0, 0.0, 1.0, 100.0, 100.0));
564 builder.line_to(10.0, 20.0); let path = builder.build();
567 assert_eq!(
568 path.segments[0],
569 PathSegment::MoveTo(Point::new(10.0, 20.0))
570 );
571 assert_eq!(
572 path.segments[1],
573 PathSegment::LineTo(Point::new(110.0, 120.0))
574 );
575 }
576
577 #[test]
578 fn test_ctm_accessor() {
579 let ctm = Ctm::new(2.0, 0.0, 0.0, 3.0, 10.0, 20.0);
580 let builder = PathBuilder::new(ctm);
581 assert_eq!(*builder.ctm(), ctm);
582 }
583}