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