1use std::str::FromStr;
2
3use super::{Error, Path, PathSegment, Result, Stream};
4
5impl FromStr for Path {
6 type Err = Error;
7
8 fn from_str(text: &str) -> Result<Self> {
15 let mut data = Vec::new();
16 for token in PathParser::from(text) {
17 match token {
18 Ok(token) => data.push(token),
19 Err(_) => break,
20 }
21 }
22
23 Ok(Path(data))
24 }
25}
26
27#[derive(Clone, Copy, PartialEq, Debug)]
61pub struct PathParser<'a> {
62 stream: Stream<'a>,
63 prev_cmd: Option<u8>,
64}
65
66impl<'a> From<&'a str> for PathParser<'a> {
67 #[inline]
68 fn from(v: &'a str) -> Self {
69 PathParser {
70 stream: Stream::from(v),
71 prev_cmd: None,
72 }
73 }
74}
75
76impl<'a> Iterator for PathParser<'a> {
77 type Item = Result<PathSegment>;
78
79 #[inline]
80 fn next(&mut self) -> Option<Self::Item> {
81 let s = &mut self.stream;
82
83 s.skip_spaces();
84
85 if s.at_end() {
86 return None;
87 }
88
89 let res = next_impl(s, &mut self.prev_cmd);
90 if res.is_err() {
91 s.jump_to_end();
92 }
93
94 Some(res)
95 }
96}
97
98fn next_impl(s: &mut Stream, prev_cmd: &mut Option<u8>) -> Result<PathSegment> {
99 let start = s.pos();
100
101 let has_prev_cmd = prev_cmd.is_some();
102 let first_char = s.curr_byte_unchecked();
103
104 if !has_prev_cmd && !is_cmd(first_char) {
105 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
106 }
107
108 if !has_prev_cmd {
109 if !matches!(first_char, b'M' | b'm') {
110 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
112 }
113 }
114
115 let is_implicit_move_to;
117 let cmd: u8;
118 if is_cmd(first_char) {
119 is_implicit_move_to = false;
120 cmd = first_char;
121 s.advance(1);
122 } else if is_number_start(first_char) && has_prev_cmd {
123 let p_cmd = prev_cmd.unwrap();
125
126 if p_cmd == b'Z' || p_cmd == b'z' {
127 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
129 }
130
131 if p_cmd == b'M' || p_cmd == b'm' {
132 is_implicit_move_to = true;
136 cmd = if is_absolute(p_cmd) { b'L' } else { b'l' };
137 } else {
138 is_implicit_move_to = false;
139 cmd = p_cmd;
140 }
141 } else {
142 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
143 }
144
145 let cmdl = to_relative(cmd);
146 let absolute = is_absolute(cmd);
147 let token = match cmdl {
148 b'm' => PathSegment::MoveTo {
149 abs: absolute,
150 x: s.parse_list_number()?,
151 y: s.parse_list_number()?,
152 },
153 b'l' => PathSegment::LineTo {
154 abs: absolute,
155 x: s.parse_list_number()?,
156 y: s.parse_list_number()?,
157 },
158 b'h' => PathSegment::HorizontalLineTo {
159 abs: absolute,
160 x: s.parse_list_number()?,
161 },
162 b'v' => PathSegment::VerticalLineTo {
163 abs: absolute,
164 y: s.parse_list_number()?,
165 },
166 b'c' => PathSegment::CurveTo {
167 abs: absolute,
168 x1: s.parse_list_number()?,
169 y1: s.parse_list_number()?,
170 x2: s.parse_list_number()?,
171 y2: s.parse_list_number()?,
172 x: s.parse_list_number()?,
173 y: s.parse_list_number()?,
174 },
175 b's' => PathSegment::SmoothCurveTo {
176 abs: absolute,
177 x2: s.parse_list_number()?,
178 y2: s.parse_list_number()?,
179 x: s.parse_list_number()?,
180 y: s.parse_list_number()?,
181 },
182 b'q' => PathSegment::Quadratic {
183 abs: absolute,
184 x1: s.parse_list_number()?,
185 y1: s.parse_list_number()?,
186 x: s.parse_list_number()?,
187 y: s.parse_list_number()?,
188 },
189 b't' => PathSegment::SmoothQuadratic {
190 abs: absolute,
191 x: s.parse_list_number()?,
192 y: s.parse_list_number()?,
193 },
194 b'a' => {
195 PathSegment::EllipticalArc {
197 abs: absolute,
198 rx: s.parse_list_number()?,
199 ry: s.parse_list_number()?,
200 x_axis_rotation: s.parse_list_number()?,
201 large_arc: parse_flag(s)?,
202 sweep: parse_flag(s)?,
203 x: s.parse_list_number()?,
204 y: s.parse_list_number()?,
205 }
206 }
207 b'z' => PathSegment::ClosePath { abs: absolute },
208 _ => unreachable!(),
209 };
210
211 *prev_cmd = Some(if is_implicit_move_to {
212 if absolute {
213 b'M'
214 } else {
215 b'm'
216 }
217 } else {
218 cmd
219 });
220
221 Ok(token)
222}
223
224#[inline]
226fn is_cmd(c: u8) -> bool {
227 match c {
228 b'M' | b'm' | b'Z' | b'z' | b'L' | b'l' | b'H' | b'h' | b'V' | b'v' | b'C' | b'c'
229 | b'S' | b's' | b'Q' | b'q' | b'T' | b't' | b'A' | b'a' => true,
230 _ => false,
231 }
232}
233
234#[inline]
236fn is_absolute(c: u8) -> bool {
237 debug_assert!(is_cmd(c));
238 match c {
239 b'M' | b'Z' | b'L' | b'H' | b'V' | b'C' | b'S' | b'Q' | b'T' | b'A' => true,
240 _ => false,
241 }
242}
243
244#[inline]
246fn to_relative(c: u8) -> u8 {
247 debug_assert!(is_cmd(c));
248 match c {
249 b'M' => b'm',
250 b'Z' => b'z',
251 b'L' => b'l',
252 b'H' => b'h',
253 b'V' => b'v',
254 b'C' => b'c',
255 b'S' => b's',
256 b'Q' => b'q',
257 b'T' => b't',
258 b'A' => b'a',
259 _ => c,
260 }
261}
262
263#[inline]
264fn is_number_start(c: u8) -> bool {
265 matches!(c, b'0'..=b'9' | b'.' | b'-' | b'+')
266}
267
268fn parse_flag(s: &mut Stream) -> Result<bool> {
271 s.skip_spaces();
272
273 let c = s.curr_byte()?;
274 match c {
275 b'0' | b'1' => {
276 s.advance(1);
277 if s.is_curr_byte_eq(b',') {
278 s.advance(1);
279 }
280 s.skip_spaces();
281
282 Ok(c == b'1')
283 }
284 _ => Err(Error::UnexpectedData(s.calc_char_pos_at(s.pos()))),
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 macro_rules! test {
293 ($name:ident, $text:expr, $( $seg:expr ),*) => (
294 #[test]
295 fn $name() {
296 let mut s = PathParser::from($text);
297 $(
298 assert_eq!(s.next().unwrap().unwrap(), $seg);
299 )*
300
301 if let Some(res) = s.next() {
302 assert!(res.is_err());
303 }
304 }
305 )
306 }
307
308 test!(null, "",);
309 test!(not_a_path, "q",);
310 test!(not_a_move_to, "L 20 30",);
311 test!(
312 stop_on_err_1,
313 "M 10 20 L 30 40 L 50",
314 PathSegment::MoveTo {
315 abs: true,
316 x: 10.0,
317 y: 20.0
318 },
319 PathSegment::LineTo {
320 abs: true,
321 x: 30.0,
322 y: 40.0
323 }
324 );
325
326 test!(
327 move_to_1,
328 "M 10 20",
329 PathSegment::MoveTo {
330 abs: true,
331 x: 10.0,
332 y: 20.0
333 }
334 );
335 test!(
336 move_to_2,
337 "m 10 20",
338 PathSegment::MoveTo {
339 abs: false,
340 x: 10.0,
341 y: 20.0
342 }
343 );
344 test!(
345 move_to_3,
346 "M 10 20 30 40 50 60",
347 PathSegment::MoveTo {
348 abs: true,
349 x: 10.0,
350 y: 20.0
351 },
352 PathSegment::LineTo {
353 abs: true,
354 x: 30.0,
355 y: 40.0
356 },
357 PathSegment::LineTo {
358 abs: true,
359 x: 50.0,
360 y: 60.0
361 }
362 );
363 test!(
364 move_to_4,
365 "M 10 20 30 40 50 60 M 70 80 90 100 110 120",
366 PathSegment::MoveTo {
367 abs: true,
368 x: 10.0,
369 y: 20.0
370 },
371 PathSegment::LineTo {
372 abs: true,
373 x: 30.0,
374 y: 40.0
375 },
376 PathSegment::LineTo {
377 abs: true,
378 x: 50.0,
379 y: 60.0
380 },
381 PathSegment::MoveTo {
382 abs: true,
383 x: 70.0,
384 y: 80.0
385 },
386 PathSegment::LineTo {
387 abs: true,
388 x: 90.0,
389 y: 100.0
390 },
391 PathSegment::LineTo {
392 abs: true,
393 x: 110.0,
394 y: 120.0
395 }
396 );
397
398 test!(
399 arc_to_1,
400 "M 10 20 A 5 5 30 1 1 20 20",
401 PathSegment::MoveTo {
402 abs: true,
403 x: 10.0,
404 y: 20.0
405 },
406 PathSegment::EllipticalArc {
407 abs: true,
408 rx: 5.0,
409 ry: 5.0,
410 x_axis_rotation: 30.0,
411 large_arc: true,
412 sweep: true,
413 x: 20.0,
414 y: 20.0
415 }
416 );
417
418 test!(
419 arc_to_2,
420 "M 10 20 a 5 5 30 0 0 20 20",
421 PathSegment::MoveTo {
422 abs: true,
423 x: 10.0,
424 y: 20.0
425 },
426 PathSegment::EllipticalArc {
427 abs: false,
428 rx: 5.0,
429 ry: 5.0,
430 x_axis_rotation: 30.0,
431 large_arc: false,
432 sweep: false,
433 x: 20.0,
434 y: 20.0
435 }
436 );
437
438 test!(
439 arc_to_10,
440 "M10-20A5.5.3-4 010-.1",
441 PathSegment::MoveTo {
442 abs: true,
443 x: 10.0,
444 y: -20.0
445 },
446 PathSegment::EllipticalArc {
447 abs: true,
448 rx: 5.5,
449 ry: 0.3,
450 x_axis_rotation: -4.0,
451 large_arc: false,
452 sweep: true,
453 x: 0.0,
454 y: -0.1
455 }
456 );
457
458 test!(
459 separator_1,
460 "M 10 20 L 5 15 C 10 20 30 40 50 60",
461 PathSegment::MoveTo {
462 abs: true,
463 x: 10.0,
464 y: 20.0
465 },
466 PathSegment::LineTo {
467 abs: true,
468 x: 5.0,
469 y: 15.0
470 },
471 PathSegment::CurveTo {
472 abs: true,
473 x1: 10.0,
474 y1: 20.0,
475 x2: 30.0,
476 y2: 40.0,
477 x: 50.0,
478 y: 60.0,
479 }
480 );
481
482 test!(
483 separator_2,
484 "M 10, 20 L 5, 15 C 10, 20 30, 40 50, 60",
485 PathSegment::MoveTo {
486 abs: true,
487 x: 10.0,
488 y: 20.0
489 },
490 PathSegment::LineTo {
491 abs: true,
492 x: 5.0,
493 y: 15.0
494 },
495 PathSegment::CurveTo {
496 abs: true,
497 x1: 10.0,
498 y1: 20.0,
499 x2: 30.0,
500 y2: 40.0,
501 x: 50.0,
502 y: 60.0,
503 }
504 );
505
506 test!(
507 separator_3,
508 "M 10,20 L 5,15 C 10,20 30,40 50,60",
509 PathSegment::MoveTo {
510 abs: true,
511 x: 10.0,
512 y: 20.0
513 },
514 PathSegment::LineTo {
515 abs: true,
516 x: 5.0,
517 y: 15.0
518 },
519 PathSegment::CurveTo {
520 abs: true,
521 x1: 10.0,
522 y1: 20.0,
523 x2: 30.0,
524 y2: 40.0,
525 x: 50.0,
526 y: 60.0,
527 }
528 );
529
530 test!(
531 separator_4,
532 "M10, 20 L5, 15 C10, 20 30 40 50 60",
533 PathSegment::MoveTo {
534 abs: true,
535 x: 10.0,
536 y: 20.0
537 },
538 PathSegment::LineTo {
539 abs: true,
540 x: 5.0,
541 y: 15.0
542 },
543 PathSegment::CurveTo {
544 abs: true,
545 x1: 10.0,
546 y1: 20.0,
547 x2: 30.0,
548 y2: 40.0,
549 x: 50.0,
550 y: 60.0,
551 }
552 );
553
554 test!(
555 separator_5,
556 "M10 20V30H40V50H60Z",
557 PathSegment::MoveTo {
558 abs: true,
559 x: 10.0,
560 y: 20.0
561 },
562 PathSegment::VerticalLineTo { abs: true, y: 30.0 },
563 PathSegment::HorizontalLineTo { abs: true, x: 40.0 },
564 PathSegment::VerticalLineTo { abs: true, y: 50.0 },
565 PathSegment::HorizontalLineTo { abs: true, x: 60.0 },
566 PathSegment::ClosePath { abs: true }
567 );
568
569 test!(
570 all_segments_1,
571 "M 10 20 L 30 40 H 50 V 60 C 70 80 90 100 110 120 S 130 140 150 160
572 Q 170 180 190 200 T 210 220 A 50 50 30 1 1 230 240 Z",
573 PathSegment::MoveTo {
574 abs: true,
575 x: 10.0,
576 y: 20.0
577 },
578 PathSegment::LineTo {
579 abs: true,
580 x: 30.0,
581 y: 40.0
582 },
583 PathSegment::HorizontalLineTo { abs: true, x: 50.0 },
584 PathSegment::VerticalLineTo { abs: true, y: 60.0 },
585 PathSegment::CurveTo {
586 abs: true,
587 x1: 70.0,
588 y1: 80.0,
589 x2: 90.0,
590 y2: 100.0,
591 x: 110.0,
592 y: 120.0,
593 },
594 PathSegment::SmoothCurveTo {
595 abs: true,
596 x2: 130.0,
597 y2: 140.0,
598 x: 150.0,
599 y: 160.0,
600 },
601 PathSegment::Quadratic {
602 abs: true,
603 x1: 170.0,
604 y1: 180.0,
605 x: 190.0,
606 y: 200.0,
607 },
608 PathSegment::SmoothQuadratic {
609 abs: true,
610 x: 210.0,
611 y: 220.0
612 },
613 PathSegment::EllipticalArc {
614 abs: true,
615 rx: 50.0,
616 ry: 50.0,
617 x_axis_rotation: 30.0,
618 large_arc: true,
619 sweep: true,
620 x: 230.0,
621 y: 240.0
622 },
623 PathSegment::ClosePath { abs: true }
624 );
625
626 test!(
627 all_segments_2,
628 "m 10 20 l 30 40 h 50 v 60 c 70 80 90 100 110 120 s 130 140 150 160
629 q 170 180 190 200 t 210 220 a 50 50 30 1 1 230 240 z",
630 PathSegment::MoveTo {
631 abs: false,
632 x: 10.0,
633 y: 20.0
634 },
635 PathSegment::LineTo {
636 abs: false,
637 x: 30.0,
638 y: 40.0
639 },
640 PathSegment::HorizontalLineTo {
641 abs: false,
642 x: 50.0
643 },
644 PathSegment::VerticalLineTo {
645 abs: false,
646 y: 60.0
647 },
648 PathSegment::CurveTo {
649 abs: false,
650 x1: 70.0,
651 y1: 80.0,
652 x2: 90.0,
653 y2: 100.0,
654 x: 110.0,
655 y: 120.0,
656 },
657 PathSegment::SmoothCurveTo {
658 abs: false,
659 x2: 130.0,
660 y2: 140.0,
661 x: 150.0,
662 y: 160.0,
663 },
664 PathSegment::Quadratic {
665 abs: false,
666 x1: 170.0,
667 y1: 180.0,
668 x: 190.0,
669 y: 200.0,
670 },
671 PathSegment::SmoothQuadratic {
672 abs: false,
673 x: 210.0,
674 y: 220.0
675 },
676 PathSegment::EllipticalArc {
677 abs: false,
678 rx: 50.0,
679 ry: 50.0,
680 x_axis_rotation: 30.0,
681 large_arc: true,
682 sweep: true,
683 x: 230.0,
684 y: 240.0
685 },
686 PathSegment::ClosePath { abs: false }
687 );
688
689 test!(
690 close_path_1,
691 "M10 20 L 30 40 ZM 100 200 L 300 400",
692 PathSegment::MoveTo {
693 abs: true,
694 x: 10.0,
695 y: 20.0
696 },
697 PathSegment::LineTo {
698 abs: true,
699 x: 30.0,
700 y: 40.0
701 },
702 PathSegment::ClosePath { abs: true },
703 PathSegment::MoveTo {
704 abs: true,
705 x: 100.0,
706 y: 200.0
707 },
708 PathSegment::LineTo {
709 abs: true,
710 x: 300.0,
711 y: 400.0
712 }
713 );
714
715 test!(
716 close_path_2,
717 "M10 20 L 30 40 zM 100 200 L 300 400",
718 PathSegment::MoveTo {
719 abs: true,
720 x: 10.0,
721 y: 20.0
722 },
723 PathSegment::LineTo {
724 abs: true,
725 x: 30.0,
726 y: 40.0
727 },
728 PathSegment::ClosePath { abs: false },
729 PathSegment::MoveTo {
730 abs: true,
731 x: 100.0,
732 y: 200.0
733 },
734 PathSegment::LineTo {
735 abs: true,
736 x: 300.0,
737 y: 400.0
738 }
739 );
740
741 test!(
742 close_path_3,
743 "M10 20 L 30 40 Z Z Z",
744 PathSegment::MoveTo {
745 abs: true,
746 x: 10.0,
747 y: 20.0
748 },
749 PathSegment::LineTo {
750 abs: true,
751 x: 30.0,
752 y: 40.0
753 },
754 PathSegment::ClosePath { abs: true },
755 PathSegment::ClosePath { abs: true },
756 PathSegment::ClosePath { abs: true }
757 );
758
759 test!(invalid_1, "M\t.",);
761
762 test!(
764 invalid_2,
765 "M 0 0 Z 2",
766 PathSegment::MoveTo {
767 abs: true,
768 x: 0.0,
769 y: 0.0
770 },
771 PathSegment::ClosePath { abs: true }
772 );
773
774 test!(
776 invalid_3,
777 "M 0 0 Z H 10",
778 PathSegment::MoveTo {
779 abs: true,
780 x: 0.0,
781 y: 0.0
782 },
783 PathSegment::ClosePath { abs: true },
784 PathSegment::HorizontalLineTo { abs: true, x: 10.0 }
785 );
786}