1use crate::{Error, Stream};
5use alloc::vec::Vec;
6
7#[allow(missing_docs)]
13#[derive(Clone, Copy, PartialEq, Debug)]
14pub enum PathSegment {
15 MoveTo {
16 abs: bool,
17 x: f64,
18 y: f64,
19 },
20 LineTo {
21 abs: bool,
22 x: f64,
23 y: f64,
24 },
25 HorizontalLineTo {
26 abs: bool,
27 x: f64,
28 },
29 VerticalLineTo {
30 abs: bool,
31 y: f64,
32 },
33 CurveTo {
34 abs: bool,
35 x1: f64,
36 y1: f64,
37 x2: f64,
38 y2: f64,
39 x: f64,
40 y: f64,
41 },
42 SmoothCurveTo {
43 abs: bool,
44 x2: f64,
45 y2: f64,
46 x: f64,
47 y: f64,
48 },
49 Quadratic {
50 abs: bool,
51 x1: f64,
52 y1: f64,
53 x: f64,
54 y: f64,
55 },
56 SmoothQuadratic {
57 abs: bool,
58 x: f64,
59 y: f64,
60 },
61 EllipticalArc {
62 abs: bool,
63 rx: f64,
64 ry: f64,
65 x_axis_rotation: f64,
66 large_arc: bool,
67 sweep: bool,
68 x: f64,
69 y: f64,
70 },
71 ClosePath {
72 abs: bool,
73 },
74}
75
76impl PathSegment {
77 pub fn is_abs(&self) -> bool {
96 match *self {
97 Self::MoveTo { abs, .. } => abs,
98 Self::LineTo { abs, .. } => abs,
99 Self::HorizontalLineTo { abs, .. } => abs,
100 Self::VerticalLineTo { abs, .. } => abs,
101 Self::CurveTo { abs, .. } => abs,
102 Self::SmoothCurveTo { abs, .. } => abs,
103 Self::Quadratic { abs, .. } => abs,
104 Self::SmoothQuadratic { abs, .. } => abs,
105 Self::EllipticalArc { abs, .. } => abs,
106 Self::ClosePath { abs } => abs,
107 }
108 }
109
110 pub fn command(&self) -> u8 {
129 match self.is_abs() {
130 true => match *self {
131 Self::MoveTo { .. } => b'M',
132 Self::LineTo { .. } => b'L',
133 Self::HorizontalLineTo { .. } => b'H',
134 Self::VerticalLineTo { .. } => b'V',
135 Self::CurveTo { .. } => b'C',
136 Self::SmoothCurveTo { .. } => b'S',
137 Self::Quadratic { .. } => b'Q',
138 Self::SmoothQuadratic { .. } => b'T',
139 Self::EllipticalArc { .. } => b'A',
140 Self::ClosePath { .. } => b'Z',
141 },
142 false => match *self {
143 Self::MoveTo { .. } => b'm',
144 Self::LineTo { .. } => b'l',
145 Self::HorizontalLineTo { .. } => b'h',
146 Self::VerticalLineTo { .. } => b'v',
147 Self::CurveTo { .. } => b'c',
148 Self::SmoothCurveTo { .. } => b's',
149 Self::Quadratic { .. } => b'q',
150 Self::SmoothQuadratic { .. } => b't',
151 Self::EllipticalArc { .. } => b'a',
152 Self::ClosePath { .. } => b'z',
153 },
154 }
155 }
156}
157
158#[derive(Clone, Copy, PartialEq, Eq, Debug)]
192pub struct PathParser<'a> {
193 stream: Stream<'a>,
194 prev_cmd: Option<u8>,
195}
196
197impl<'a> From<&'a str> for PathParser<'a> {
198 #[inline]
199 fn from(v: &'a str) -> Self {
200 PathParser {
201 stream: Stream::from(v),
202 prev_cmd: None,
203 }
204 }
205}
206
207impl Iterator for PathParser<'_> {
208 type Item = Result<PathSegment, Error>;
209
210 #[inline]
211 fn next(&mut self) -> Option<Self::Item> {
212 let s = &mut self.stream;
213
214 s.skip_spaces();
215
216 if s.at_end() {
217 return None;
218 }
219
220 let res = next_impl(s, &mut self.prev_cmd);
221 if res.is_err() {
222 s.jump_to_end();
223 }
224
225 Some(res)
226 }
227}
228
229fn next_impl(s: &mut Stream<'_>, prev_cmd: &mut Option<u8>) -> Result<PathSegment, Error> {
230 let start = s.pos();
231
232 let has_prev_cmd = prev_cmd.is_some();
233 let first_char = s.curr_byte_unchecked();
234
235 if !has_prev_cmd && !is_cmd(first_char) {
236 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
237 }
238
239 if !has_prev_cmd && !matches!(first_char, b'M' | b'm') {
240 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
242 }
243
244 let is_implicit_move_to;
246 let cmd: u8;
247 if is_cmd(first_char) {
248 is_implicit_move_to = false;
249 cmd = first_char;
250 s.advance(1);
251 } else if is_number_start(first_char) && has_prev_cmd {
252 let p_cmd = prev_cmd.unwrap();
254
255 if p_cmd == b'Z' || p_cmd == b'z' {
256 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
258 }
259
260 if p_cmd == b'M' || p_cmd == b'm' {
261 is_implicit_move_to = true;
265 cmd = if is_absolute(p_cmd) { b'L' } else { b'l' };
266 } else {
267 is_implicit_move_to = false;
268 cmd = p_cmd;
269 }
270 } else {
271 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
272 }
273
274 let cmdl = to_relative(cmd);
275 let absolute = is_absolute(cmd);
276 let token = match cmdl {
277 b'm' => PathSegment::MoveTo {
278 abs: absolute,
279 x: s.parse_list_number()?,
280 y: s.parse_list_number()?,
281 },
282 b'l' => PathSegment::LineTo {
283 abs: absolute,
284 x: s.parse_list_number()?,
285 y: s.parse_list_number()?,
286 },
287 b'h' => PathSegment::HorizontalLineTo {
288 abs: absolute,
289 x: s.parse_list_number()?,
290 },
291 b'v' => PathSegment::VerticalLineTo {
292 abs: absolute,
293 y: s.parse_list_number()?,
294 },
295 b'c' => PathSegment::CurveTo {
296 abs: absolute,
297 x1: s.parse_list_number()?,
298 y1: s.parse_list_number()?,
299 x2: s.parse_list_number()?,
300 y2: s.parse_list_number()?,
301 x: s.parse_list_number()?,
302 y: s.parse_list_number()?,
303 },
304 b's' => PathSegment::SmoothCurveTo {
305 abs: absolute,
306 x2: s.parse_list_number()?,
307 y2: s.parse_list_number()?,
308 x: s.parse_list_number()?,
309 y: s.parse_list_number()?,
310 },
311 b'q' => PathSegment::Quadratic {
312 abs: absolute,
313 x1: s.parse_list_number()?,
314 y1: s.parse_list_number()?,
315 x: s.parse_list_number()?,
316 y: s.parse_list_number()?,
317 },
318 b't' => PathSegment::SmoothQuadratic {
319 abs: absolute,
320 x: s.parse_list_number()?,
321 y: s.parse_list_number()?,
322 },
323 b'a' => {
324 PathSegment::EllipticalArc {
326 abs: absolute,
327 rx: s.parse_list_number()?,
328 ry: s.parse_list_number()?,
329 x_axis_rotation: s.parse_list_number()?,
330 large_arc: parse_flag(s)?,
331 sweep: parse_flag(s)?,
332 x: s.parse_list_number()?,
333 y: s.parse_list_number()?,
334 }
335 }
336 b'z' => PathSegment::ClosePath { abs: absolute },
337 _ => unreachable!(),
338 };
339
340 *prev_cmd = Some(if is_implicit_move_to {
341 if absolute { b'M' } else { b'm' }
342 } else {
343 cmd
344 });
345
346 Ok(token)
347}
348
349#[rustfmt::skip]
351#[inline]
352fn is_cmd(c: u8) -> bool {
353 matches!(c,
354 b'M' | b'm'
355 | b'Z' | b'z'
356 | b'L' | b'l'
357 | b'H' | b'h'
358 | b'V' | b'v'
359 | b'C' | b'c'
360 | b'S' | b's'
361 | b'Q' | b'q'
362 | b'T' | b't'
363 | b'A' | b'a')
364}
365
366#[inline]
368fn is_absolute(c: u8) -> bool {
369 debug_assert!(is_cmd(c));
370 matches!(
371 c,
372 b'M' | b'Z' | b'L' | b'H' | b'V' | b'C' | b'S' | b'Q' | b'T' | b'A'
373 )
374}
375
376#[inline]
378fn to_relative(c: u8) -> u8 {
379 debug_assert!(is_cmd(c));
380 match c {
381 b'M' => b'm',
382 b'Z' => b'z',
383 b'L' => b'l',
384 b'H' => b'h',
385 b'V' => b'v',
386 b'C' => b'c',
387 b'S' => b's',
388 b'Q' => b'q',
389 b'T' => b't',
390 b'A' => b'a',
391 _ => c,
392 }
393}
394
395#[inline]
396fn is_number_start(c: u8) -> bool {
397 matches!(c, b'0'..=b'9' | b'.' | b'-' | b'+')
398}
399
400fn parse_flag(s: &mut Stream<'_>) -> Result<bool, Error> {
403 s.skip_spaces();
404
405 let c = s.curr_byte()?;
406 match c {
407 b'0' | b'1' => {
408 s.advance(1);
409 if s.is_curr_byte_eq(b',') {
410 s.advance(1);
411 }
412 s.skip_spaces();
413
414 Ok(c == b'1')
415 }
416 _ => Err(Error::UnexpectedData(s.calc_char_pos_at(s.pos()))),
417 }
418}
419
420#[rustfmt::skip]
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 macro_rules! test {
426 ($name:ident, $text:expr, $( $seg:expr ),*) => (
427 #[test]
428 fn $name() {
429 let mut s = PathParser::from($text);
430 $(
431 assert_eq!(s.next().unwrap().unwrap(), $seg);
432 )*
433
434 if let Some(res) = s.next() {
435 assert!(res.is_err());
436 }
437 }
438 )
439 }
440
441 test!(null, "", );
442 test!(not_a_path, "q", );
443 test!(not_a_move_to, "L 20 30", );
444 test!(stop_on_err_1, "M 10 20 L 30 40 L 50",
445 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
446 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 }
447 );
448
449 test!(move_to_1, "M 10 20", PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 });
450 test!(move_to_2, "m 10 20", PathSegment::MoveTo { abs: false, x: 10.0, y: 20.0 });
451 test!(move_to_3, "M 10 20 30 40 50 60",
452 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
453 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
454 PathSegment::LineTo { abs: true, x: 50.0, y: 60.0 }
455 );
456 test!(move_to_4, "M 10 20 30 40 50 60 M 70 80 90 100 110 120",
457 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
458 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
459 PathSegment::LineTo { abs: true, x: 50.0, y: 60.0 },
460 PathSegment::MoveTo { abs: true, x: 70.0, y: 80.0 },
461 PathSegment::LineTo { abs: true, x: 90.0, y: 100.0 },
462 PathSegment::LineTo { abs: true, x: 110.0, y: 120.0 }
463 );
464
465 test!(arc_to_1, "M 10 20 A 5 5 30 1 1 20 20",
466 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
467 PathSegment::EllipticalArc {
468 abs: true,
469 rx: 5.0, ry: 5.0,
470 x_axis_rotation: 30.0,
471 large_arc: true, sweep: true,
472 x: 20.0, y: 20.0
473 }
474 );
475
476 test!(arc_to_2, "M 10 20 a 5 5 30 0 0 20 20",
477 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
478 PathSegment::EllipticalArc {
479 abs: false,
480 rx: 5.0, ry: 5.0,
481 x_axis_rotation: 30.0,
482 large_arc: false, sweep: false,
483 x: 20.0, y: 20.0
484 }
485 );
486
487 test!(arc_to_10, "M10-20A5.5.3-4 010-.1",
488 PathSegment::MoveTo { abs: true, x: 10.0, y: -20.0 },
489 PathSegment::EllipticalArc {
490 abs: true,
491 rx: 5.5, ry: 0.3,
492 x_axis_rotation: -4.0,
493 large_arc: false, sweep: true,
494 x: 0.0, y: -0.1
495 }
496 );
497
498 test!(separator_1, "M 10 20 L 5 15 C 10 20 30 40 50 60",
499 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
500 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
501 PathSegment::CurveTo {
502 abs: true,
503 x1: 10.0, y1: 20.0,
504 x2: 30.0, y2: 40.0,
505 x: 50.0, y: 60.0,
506 }
507 );
508
509 test!(separator_2, "M 10, 20 L 5, 15 C 10, 20 30, 40 50, 60",
510 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
511 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
512 PathSegment::CurveTo {
513 abs: true,
514 x1: 10.0, y1: 20.0,
515 x2: 30.0, y2: 40.0,
516 x: 50.0, y: 60.0,
517 }
518 );
519
520 test!(separator_3, "M 10,20 L 5,15 C 10,20 30,40 50,60",
521 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
522 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
523 PathSegment::CurveTo {
524 abs: true,
525 x1: 10.0, y1: 20.0,
526 x2: 30.0, y2: 40.0,
527 x: 50.0, y: 60.0,
528 }
529 );
530
531 test!(separator_4, "M10, 20 L5, 15 C10, 20 30 40 50 60",
532 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
533 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
534 PathSegment::CurveTo {
535 abs: true,
536 x1: 10.0, y1: 20.0,
537 x2: 30.0, y2: 40.0,
538 x: 50.0, y: 60.0,
539 }
540 );
541
542 test!(separator_5, "M10 20V30H40V50H60Z",
543 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
544 PathSegment::VerticalLineTo { abs: true, y: 30.0 },
545 PathSegment::HorizontalLineTo { abs: true, x: 40.0 },
546 PathSegment::VerticalLineTo { abs: true, y: 50.0 },
547 PathSegment::HorizontalLineTo { abs: true, x: 60.0 },
548 PathSegment::ClosePath { abs: true }
549 );
550
551 test!(all_segments_1, "M 10 20 L 30 40 H 50 V 60 C 70 80 90 100 110 120 S 130 140 150 160
552 Q 170 180 190 200 T 210 220 A 50 50 30 1 1 230 240 Z",
553 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
554 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
555 PathSegment::HorizontalLineTo { abs: true, x: 50.0 },
556 PathSegment::VerticalLineTo { abs: true, y: 60.0 },
557 PathSegment::CurveTo {
558 abs: true,
559 x1: 70.0, y1: 80.0,
560 x2: 90.0, y2: 100.0,
561 x: 110.0, y: 120.0,
562 },
563 PathSegment::SmoothCurveTo {
564 abs: true,
565 x2: 130.0, y2: 140.0,
566 x: 150.0, y: 160.0,
567 },
568 PathSegment::Quadratic {
569 abs: true,
570 x1: 170.0, y1: 180.0,
571 x: 190.0, y: 200.0,
572 },
573 PathSegment::SmoothQuadratic { abs: true, x: 210.0, y: 220.0 },
574 PathSegment::EllipticalArc {
575 abs: true,
576 rx: 50.0, ry: 50.0,
577 x_axis_rotation: 30.0,
578 large_arc: true, sweep: true,
579 x: 230.0, y: 240.0
580 },
581 PathSegment::ClosePath { abs: true }
582 );
583
584 test!(all_segments_2, "m 10 20 l 30 40 h 50 v 60 c 70 80 90 100 110 120 s 130 140 150 160
585 q 170 180 190 200 t 210 220 a 50 50 30 1 1 230 240 z",
586 PathSegment::MoveTo { abs: false, x: 10.0, y: 20.0 },
587 PathSegment::LineTo { abs: false, x: 30.0, y: 40.0 },
588 PathSegment::HorizontalLineTo { abs: false, x: 50.0 },
589 PathSegment::VerticalLineTo { abs: false, y: 60.0 },
590 PathSegment::CurveTo {
591 abs: false,
592 x1: 70.0, y1: 80.0,
593 x2: 90.0, y2: 100.0,
594 x: 110.0, y: 120.0,
595 },
596 PathSegment::SmoothCurveTo {
597 abs: false,
598 x2: 130.0, y2: 140.0,
599 x: 150.0, y: 160.0,
600 },
601 PathSegment::Quadratic {
602 abs: false,
603 x1: 170.0, y1: 180.0,
604 x: 190.0, y: 200.0,
605 },
606 PathSegment::SmoothQuadratic { abs: false, x: 210.0, y: 220.0 },
607 PathSegment::EllipticalArc {
608 abs: false,
609 rx: 50.0, ry: 50.0,
610 x_axis_rotation: 30.0,
611 large_arc: true, sweep: true,
612 x: 230.0, y: 240.0
613 },
614 PathSegment::ClosePath { abs: false }
615 );
616
617 test!(close_path_1, "M10 20 L 30 40 ZM 100 200 L 300 400",
618 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
619 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
620 PathSegment::ClosePath { abs: true },
621 PathSegment::MoveTo { abs: true, x: 100.0, y: 200.0 },
622 PathSegment::LineTo { abs: true, x: 300.0, y: 400.0 }
623 );
624
625 test!(close_path_2, "M10 20 L 30 40 zM 100 200 L 300 400",
626 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
627 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
628 PathSegment::ClosePath { abs: false },
629 PathSegment::MoveTo { abs: true, x: 100.0, y: 200.0 },
630 PathSegment::LineTo { abs: true, x: 300.0, y: 400.0 }
631 );
632
633 test!(close_path_3, "M10 20 L 30 40 Z Z Z",
634 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
635 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
636 PathSegment::ClosePath { abs: true },
637 PathSegment::ClosePath { abs: true },
638 PathSegment::ClosePath { abs: true }
639 );
640
641 test!(invalid_1, "M\t.", );
643
644 test!(invalid_2, "M 0 0 Z 2",
646 PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 },
647 PathSegment::ClosePath { abs: true }
648 );
649
650 test!(invalid_3, "M 0 0 Z H 10",
652 PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 },
653 PathSegment::ClosePath { abs: true },
654 PathSegment::HorizontalLineTo { abs: true, x: 10.0 }
655 );
656
657 #[test]
658 fn test_helper_functions(){
659 assert!(PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 }.is_abs());
660 assert!(!PathSegment::MoveTo { abs: false, x: 0.0, y: 0.0 }.is_abs()); assert_eq!(PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 }.command(), b'M');
662 }
663}
664
665#[allow(missing_docs)]
667#[derive(Clone, Copy, PartialEq, Debug)]
668pub enum SimplePathSegment {
669 MoveTo {
670 x: f64,
671 y: f64,
672 },
673 LineTo {
674 x: f64,
675 y: f64,
676 },
677 CurveTo {
678 x1: f64,
679 y1: f64,
680 x2: f64,
681 y2: f64,
682 x: f64,
683 y: f64,
684 },
685 Quadratic {
686 x1: f64,
687 y1: f64,
688 x: f64,
689 y: f64,
690 },
691 ClosePath,
692}
693
694#[derive(Clone, Debug)]
707pub struct SimplifyingPathParser<'a> {
708 parser: PathParser<'a>,
709
710 prev_mx: f64,
712 prev_my: f64,
713
714 prev_tx: f64,
716 prev_ty: f64,
717
718 prev_x: f64,
720 prev_y: f64,
721
722 prev_seg: PathSegment,
723 prev_simple_seg: Option<SimplePathSegment>,
724
725 buffer: Vec<SimplePathSegment>,
726}
727
728impl<'a> From<&'a str> for SimplifyingPathParser<'a> {
729 #[inline]
730 fn from(v: &'a str) -> Self {
731 SimplifyingPathParser {
732 parser: PathParser::from(v),
733 prev_mx: 0.0,
734 prev_my: 0.0,
735 prev_tx: 0.0,
736 prev_ty: 0.0,
737 prev_x: 0.0,
738 prev_y: 0.0,
739 prev_seg: PathSegment::MoveTo {
740 abs: true,
741 x: 0.0,
742 y: 0.0,
743 },
744 prev_simple_seg: None,
745 buffer: Vec::new(),
746 }
747 }
748}
749
750impl Iterator for SimplifyingPathParser<'_> {
751 type Item = Result<SimplePathSegment, Error>;
752
753 fn next(&mut self) -> Option<Self::Item> {
754 if !self.buffer.is_empty() {
755 return Some(Ok(self.buffer.remove(0)));
756 }
757
758 let segment = match self.parser.next()? {
759 Ok(v) => v,
760 Err(e) => return Some(Err(e)),
761 };
762
763 if let Some(SimplePathSegment::ClosePath) = self.prev_simple_seg {
766 match segment {
767 PathSegment::MoveTo { .. } | PathSegment::ClosePath { .. } => {}
768 _ => {
769 let new_seg = SimplePathSegment::MoveTo {
770 x: self.prev_mx,
771 y: self.prev_my,
772 };
773 self.buffer.push(new_seg);
774 self.prev_simple_seg = Some(new_seg);
775 }
776 }
777 }
778
779 match segment {
780 PathSegment::MoveTo { abs, mut x, mut y } => {
781 if !abs {
782 if let Some(SimplePathSegment::ClosePath) = self.prev_simple_seg {
785 x += self.prev_mx;
786 y += self.prev_my;
787 } else {
788 x += self.prev_x;
789 y += self.prev_y;
790 }
791 }
792
793 self.buffer.push(SimplePathSegment::MoveTo { x, y });
794 self.prev_seg = segment;
795 }
796 PathSegment::LineTo { abs, mut x, mut y } => {
797 if !abs {
798 x += self.prev_x;
799 y += self.prev_y;
800 }
801
802 self.buffer.push(SimplePathSegment::LineTo { x, y });
803 self.prev_seg = segment;
804 }
805 PathSegment::HorizontalLineTo { abs, mut x } => {
806 if !abs {
807 x += self.prev_x;
808 }
809
810 self.buffer
811 .push(SimplePathSegment::LineTo { x, y: self.prev_y });
812 self.prev_seg = segment;
813 }
814 PathSegment::VerticalLineTo { abs, mut y } => {
815 if !abs {
816 y += self.prev_y;
817 }
818
819 self.buffer
820 .push(SimplePathSegment::LineTo { x: self.prev_x, y });
821 self.prev_seg = segment;
822 }
823 PathSegment::CurveTo {
824 abs,
825 mut x1,
826 mut y1,
827 mut x2,
828 mut y2,
829 mut x,
830 mut y,
831 } => {
832 if !abs {
833 x1 += self.prev_x;
834 y1 += self.prev_y;
835 x2 += self.prev_x;
836 y2 += self.prev_y;
837 x += self.prev_x;
838 y += self.prev_y;
839 }
840
841 self.buffer.push(SimplePathSegment::CurveTo {
842 x1,
843 y1,
844 x2,
845 y2,
846 x,
847 y,
848 });
849
850 self.prev_seg = PathSegment::CurveTo {
852 abs: true,
853 x1,
854 y1,
855 x2,
856 y2,
857 x,
858 y,
859 };
860 }
861 PathSegment::SmoothCurveTo {
862 abs,
863 mut x2,
864 mut y2,
865 mut x,
866 mut y,
867 } => {
868 let (x1, y1) = match self.prev_seg {
874 PathSegment::CurveTo { x2, y2, x, y, .. }
875 | PathSegment::SmoothCurveTo { x2, y2, x, y, .. } => {
876 (x * 2.0 - x2, y * 2.0 - y2)
877 }
878 _ => (self.prev_x, self.prev_y),
879 };
880
881 if !abs {
882 x2 += self.prev_x;
883 y2 += self.prev_y;
884 x += self.prev_x;
885 y += self.prev_y;
886 }
887
888 self.buffer.push(SimplePathSegment::CurveTo {
889 x1,
890 y1,
891 x2,
892 y2,
893 x,
894 y,
895 });
896
897 self.prev_seg = PathSegment::SmoothCurveTo {
899 abs: true,
900 x2,
901 y2,
902 x,
903 y,
904 };
905 }
906 PathSegment::Quadratic {
907 abs,
908 mut x1,
909 mut y1,
910 mut x,
911 mut y,
912 } => {
913 if !abs {
914 x1 += self.prev_x;
915 y1 += self.prev_y;
916 x += self.prev_x;
917 y += self.prev_y;
918 }
919
920 self.buffer
921 .push(SimplePathSegment::Quadratic { x1, y1, x, y });
922
923 self.prev_seg = PathSegment::Quadratic {
925 abs: true,
926 x1,
927 y1,
928 x,
929 y,
930 };
931 }
932 PathSegment::SmoothQuadratic { abs, mut x, mut y } => {
933 let (x1, y1) = match self.prev_seg {
939 PathSegment::Quadratic { x1, y1, x, y, .. } => (x * 2.0 - x1, y * 2.0 - y1),
940 PathSegment::SmoothQuadratic { x, y, .. } => {
941 (x * 2.0 - self.prev_tx, y * 2.0 - self.prev_ty)
942 }
943 _ => (self.prev_x, self.prev_y),
944 };
945
946 self.prev_tx = x1;
947 self.prev_ty = y1;
948
949 if !abs {
950 x += self.prev_x;
951 y += self.prev_y;
952 }
953
954 self.buffer
955 .push(SimplePathSegment::Quadratic { x1, y1, x, y });
956
957 self.prev_seg = PathSegment::SmoothQuadratic { abs: true, x, y };
959 }
960 PathSegment::EllipticalArc {
961 abs,
962 rx,
963 ry,
964 x_axis_rotation,
965 large_arc,
966 sweep,
967 mut x,
968 mut y,
969 } => {
970 if !abs {
971 x += self.prev_x;
972 y += self.prev_y;
973 }
974
975 let svg_arc = kurbo::SvgArc {
976 from: kurbo::Point::new(self.prev_x, self.prev_y),
977 to: kurbo::Point::new(x, y),
978 radii: kurbo::Vec2::new(rx, ry),
979 x_rotation: x_axis_rotation.to_radians(),
980 large_arc,
981 sweep,
982 };
983
984 match kurbo::Arc::from_svg_arc(&svg_arc) {
985 Some(arc) => {
986 arc.to_cubic_beziers(0.1, |p1, p2, p| {
987 self.buffer.push(SimplePathSegment::CurveTo {
988 x1: p1.x,
989 y1: p1.y,
990 x2: p2.x,
991 y2: p2.y,
992 x: p.x,
993 y: p.y,
994 });
995 });
996 }
997 None => {
998 self.buffer.push(SimplePathSegment::LineTo { x, y });
999 }
1000 }
1001
1002 self.prev_seg = segment;
1003 }
1004 PathSegment::ClosePath { .. } => {
1005 if let Some(SimplePathSegment::ClosePath) = self.prev_simple_seg {
1006 } else {
1009 self.buffer.push(SimplePathSegment::ClosePath);
1010 }
1011
1012 self.prev_seg = segment;
1013 }
1014 }
1015
1016 if let Some(new_segment) = self.buffer.last() {
1018 self.prev_simple_seg = Some(*new_segment);
1019
1020 match *new_segment {
1021 SimplePathSegment::MoveTo { x, y } => {
1022 self.prev_x = x;
1023 self.prev_y = y;
1024 self.prev_mx = self.prev_x;
1025 self.prev_my = self.prev_y;
1026 }
1027 SimplePathSegment::LineTo { x, y } => {
1028 self.prev_x = x;
1029 self.prev_y = y;
1030 }
1031 SimplePathSegment::CurveTo { x, y, .. } => {
1032 self.prev_x = x;
1033 self.prev_y = y;
1034 }
1035 SimplePathSegment::Quadratic { x, y, .. } => {
1036 self.prev_x = x;
1037 self.prev_y = y;
1038 }
1039 SimplePathSegment::ClosePath => {
1040 self.prev_x = self.prev_mx;
1042 self.prev_y = self.prev_my;
1043 }
1044 }
1045 }
1046
1047 if self.buffer.is_empty() {
1048 return self.next();
1049 }
1050
1051 Some(Ok(self.buffer.remove(0)))
1052 }
1053}
1054
1055#[rustfmt::skip]
1056#[cfg(test)]
1057mod simple_tests {
1058 use super::*;
1059
1060 macro_rules! test {
1061 ($name:ident, $text:expr, $( $seg:expr ),*) => (
1062 #[test]
1063 fn $name() {
1064 let mut s = SimplifyingPathParser::from($text);
1065 $(
1066 assert_eq!(s.next().unwrap().unwrap(), $seg);
1067 )*
1068
1069 if let Some(res) = s.next() {
1070 assert!(res.is_err());
1071 }
1072 }
1073 )
1074 }
1075
1076 test!(ignore_duplicated_close_paths, "M 10 20 L 30 40 Z Z Z Z",
1077 SimplePathSegment::MoveTo { x: 10.0, y: 20.0 },
1078 SimplePathSegment::LineTo { x: 30.0, y: 40.0 },
1079 SimplePathSegment::ClosePath
1080 );
1081
1082 test!(relative_move_to, "m 30 40 110 120 -20 -130",
1083 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1084 SimplePathSegment::LineTo { x: 140.0, y: 160.0 },
1085 SimplePathSegment::LineTo { x: 120.0, y: 30.0 }
1086 );
1087
1088 test!(smooth_curve_to_after_move_to, "M 30 40 S 171 45 180 155",
1089 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1090 SimplePathSegment::CurveTo { x1: 30.0, y1: 40.0, x2: 171.0, y2: 45.0, x: 180.0, y: 155.0 }
1091 );
1092
1093 test!(smooth_curve_to_after_curve_to, "M 30 40 C 16 137 171 45 100 90 S 171 45 180 155",
1094 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1095 SimplePathSegment::CurveTo { x1: 16.0, y1: 137.0, x2: 171.0, y2: 45.0, x: 100.0, y: 90.0 },
1096 SimplePathSegment::CurveTo { x1: 29.0, y1: 135.0, x2: 171.0, y2: 45.0, x: 180.0, y: 155.0 }
1097 );
1098
1099 test!(relative_smooth_curve_to_after_arc_to, "M 1 5 A 5 5 0 0 1 3 1 s 3 2 8 2",
1100 SimplePathSegment::MoveTo { x: 1.0, y: 5.0 },
1101 SimplePathSegment::CurveTo {
1102 x1: 1.0, y1: 3.4262134833347355,
1103 x2: 1.7409707866677877, y2: 1.9442719099991592,
1104 x: 2.9999999999999996, y: 1.0000000000000004
1105 },
1106 SimplePathSegment::CurveTo {
1107 x1: 2.9999999999999996, y1: 1.0000000000000004,
1108 x2: 6.0, y2: 3.0000000000000004,
1109 x: 11.0, y: 3.0000000000000004
1110 }
1111 );
1112
1113 test!(smooth_quadratic_after_move_to, "M 30 40 T 180 155",
1114 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1115 SimplePathSegment::Quadratic { x1: 30.0, y1: 40.0, x: 180.0, y: 155.0 }
1116 );
1117
1118 test!(smooth_quadratic_after_quadratic, "M 30 40 Q 171 45 100 90 T 160 180",
1119 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1120 SimplePathSegment::Quadratic { x1: 171.0, y1: 45.0, x: 100.0, y: 90.0 },
1121 SimplePathSegment::Quadratic { x1: 29.0, y1: 135.0, x: 160.0, y: 180.0 }
1122 );
1123
1124 test!(relative_smooth_quadratic_after_quadratic, "M 30 40 Q 171 45 100 90 t 60 80",
1125 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1126 SimplePathSegment::Quadratic { x1: 171.0, y1: 45.0, x: 100.0, y: 90.0 },
1127 SimplePathSegment::Quadratic { x1: 29.0, y1: 135.0, x: 160.0, y: 170.0 }
1128 );
1129
1130 test!(relative_smooth_quadratic_after_relative_quadratic, "M 30 40 q 171 45 50 40 t 60 80",
1131 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1132 SimplePathSegment::Quadratic { x1: 201.0, y1: 85.0, x: 80.0, y: 80.0 },
1133 SimplePathSegment::Quadratic { x1: -41.0, y1: 75.0, x: 140.0, y: 160.0 }
1134 );
1135
1136 test!(smooth_quadratic_after_smooth_quadratic, "M 30 30 T 40 140 T 170 30",
1137 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1138 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1139 SimplePathSegment::Quadratic { x1: 50.0, y1: 250.0, x: 170.0, y: 30.0 }
1140 );
1141
1142 test!(smooth_quadratic_after_relative_smooth_quadratic, "M 30 30 T 40 140 t 100 -30",
1143 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1144 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1145 SimplePathSegment::Quadratic { x1: 50.0, y1: 250.0, x: 140.0, y: 110.0 }
1146 );
1147
1148 test!(smooth_quadratic_after_relative_quadratic, "M 30 30 T 40 140 q 30 100 120 -30",
1149 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1150 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1151 SimplePathSegment::Quadratic { x1: 70.0, y1: 240.0, x: 160.0, y: 110.0 }
1152 );
1153
1154 test!(smooth_quadratic_after_relative_smooth_curve_to, "M 30 30 T 40 170 s 90 -20 90 -90",
1155 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1156 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 170.0 },
1157 SimplePathSegment::CurveTo { x1: 40.0, y1: 170.0, x2: 130.0, y2: 150.0, x: 130.0, y: 80.0 }
1158 );
1159
1160 test!(quadratic_after_smooth_quadratic, "M 30 30 T 40 140 Q 80 180 170 30",
1161 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1162 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1163 SimplePathSegment::Quadratic { x1: 80.0, y1: 180.0, x: 170.0, y: 30.0 }
1164 );
1165
1166 test!(relative_smooth_quadratic_to_after_arc_to, "M 1 5 A 5 5 0 0 1 3 1 t 8 2",
1167 SimplePathSegment::MoveTo { x: 1.0, y: 5.0 },
1168 SimplePathSegment::CurveTo {
1169 x1: 1.0, y1: 3.4262134833347355,
1170 x2: 1.7409707866677877, y2: 1.9442719099991592,
1171 x: 2.9999999999999996, y: 1.0000000000000004
1172 },
1173 SimplePathSegment::Quadratic {
1174 x1: 2.9999999999999996, y1: 1.0000000000000004,
1175 x: 11.0, y: 3.0000000000000004
1176 }
1177 );
1178
1179 test!(implicit_move_to_after_close_path, "M 10 20 L 30 40 Z L 50 60",
1180 SimplePathSegment::MoveTo { x: 10.0, y: 20.0 },
1181 SimplePathSegment::LineTo { x: 30.0, y: 40.0 },
1182 SimplePathSegment::ClosePath,
1183 SimplePathSegment::MoveTo { x: 10.0, y: 20.0 },
1184 SimplePathSegment::LineTo { x: 50.0, y: 60.0 }
1185 );
1186
1187 #[test]
1188 fn arc_to() {
1189 let mut s = SimplifyingPathParser::from("M 30 40 A 40 30 20 1 1 150 100");
1190 assert_eq!(s.next().unwrap().unwrap(), SimplePathSegment::MoveTo { x: 30.0, y: 40.0 });
1191 let curve1 = s.next().unwrap().unwrap();
1192 let curve2 = s.next().unwrap().unwrap();
1193 if let Some(res) = s.next() {
1194 assert!(res.is_err());
1195 }
1196
1197 if let SimplePathSegment::CurveTo { x1, y1, x2, y2, x, y } = curve1 {
1198 assert_eq!(x1.round(), 45.0);
1199 assert_eq!(y1.round(), 16.0);
1200 assert_eq!(x2.round(), 84.0);
1201 assert_eq!(y2.round(), 10.0);
1202 assert_eq!(x.round(), 117.0);
1203 assert_eq!(y.round(), 27.0);
1204 } else {
1205 panic!("invalid type");
1206 }
1207
1208 if let SimplePathSegment::CurveTo { x1, y1, x2, y2, x, y } = curve2 {
1209 assert_eq!(x1.round(), 150.0);
1210 assert_eq!(y1.round(), 43.0);
1211 assert_eq!(x2.round(), 165.0);
1212 assert_eq!(y2.round(), 76.0);
1213 assert_eq!(x.round(), 150.0);
1214 assert_eq!(y.round(), 100.0);
1215 } else {
1216 panic!("invalid type");
1217 }
1218 }
1219}