1use path::{
2 geom::{ArcFlags, SvgArc},
3 math::{point, vector, Angle, Point},
4 traits::PathBuilder,
5};
6
7use std::fmt;
8
9#[non_exhaustive]
10#[derive(Clone, PartialEq)]
11pub enum ParseError {
12 Number {
13 src: String,
14 line: i32,
15 column: i32,
16 },
17 Flag {
18 src: char,
19 line: i32,
20 column: i32,
21 },
22 Command {
23 command: char,
24 line: i32,
25 column: i32,
26 },
27 MissingMoveTo {
28 command: char,
29 line: i32,
30 column: i32,
31 },
32}
33
34impl fmt::Debug for ParseError {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
36 match self {
37 ParseError::Number { src, line, column } => {
38 write!(
39 f,
40 "Line {} Column {}: Expected number, got {:?}.",
41 line, column, src
42 )
43 }
44 ParseError::Flag { src, line, column } => {
45 write!(
46 f,
47 "Line {} Column {}: Expected flag (0/1), got {:?}.",
48 line, column, src
49 )
50 }
51 ParseError::Command {
52 command,
53 line,
54 column,
55 } => {
56 write!(
57 f,
58 "Line {} Column {}: Invalid command {:?}.",
59 line, column, command
60 )
61 }
62 ParseError::MissingMoveTo {
63 command,
64 line,
65 column,
66 } => {
67 write!(
68 f,
69 "Line {} Column {}: Expected move-to command, got {:?}.",
70 line, column, command
71 )
72 }
73 }
74 }
75}
76
77#[non_exhaustive]
78#[derive(Clone, Debug, PartialEq)]
79pub struct ParserOptions {
80 /// Number of custom attributes per endpoint.
81 pub num_attributes: usize,
82 /// Optionally stop parsing when encountering a provided special character.
83 pub stop_at: Option<char>,
84}
85
86impl ParserOptions {
87 pub const DEFAULT: ParserOptions = ParserOptions {
88 num_attributes: 0,
89 stop_at: None,
90 };
91}
92
93// A buffered iterator of characters keeping track of line and column.
94pub struct Source<Iter> {
95 src: Iter,
96 current: char,
97 line: i32,
98 col: i32,
99 finished: bool,
100}
101
102impl<Iter: Iterator<Item = char>> Source<Iter> {
103 pub fn new<IntoIter>(src: IntoIter) -> Self
104 where
105 IntoIter: IntoIterator<IntoIter = Iter>,
106 {
107 Self::with_position(0, 0, src)
108 }
109
110 pub fn with_position<IntoIter>(line: i32, column: i32, src: IntoIter) -> Self
111 where
112 IntoIter: IntoIterator<IntoIter = Iter>,
113 {
114 let mut src = src.into_iter();
115
116 let (current, finished) = match src.next() {
117 Some(c) => (c, false),
118 None => (' ', true),
119 };
120
121 let line = line + if current == '\n' { 1 } else { 0 };
122
123 Source {
124 current,
125 finished,
126 src,
127 line,
128 col: column,
129 }
130 }
131
132 /// Consume the source and returns the iterator, line and column.
133 pub fn unwrap(self) -> (Iter, i32, i32) {
134 (self.src, self.line, self.col)
135 }
136
137 fn skip_whitespace(&mut self) {
138 while !self.finished && (self.current.is_whitespace() || self.current == ',') {
139 self.advance_one();
140 }
141 }
142
143 fn advance_one(&mut self) {
144 if self.finished {
145 return;
146 }
147 match self.src.next() {
148 Some('\n') => {
149 self.current = '\n';
150 self.line += 1;
151 self.col = -1;
152 }
153 Some(c) => {
154 self.current = c;
155 self.col += 1;
156 }
157 None => {
158 self.current = '~';
159 self.finished = true;
160 }
161 }
162 }
163}
164
165/// A context object for parsing the extended path syntax.
166///
167/// # Syntax
168///
169/// The extended path syntax is a super-set of the SVG path syntax, with support for extra per-endpoint
170/// data for custom attributes.
171///
172/// Wherever an endpoint is specified, the extended path syntax expects a sequence N extra numbers
173/// where N is the number of custom attributes.
174///
175/// For example with 1 custom attribute `M 0 0 10 Q 1 1 2 2 20 L 3 3 30` reads as follows:
176///
177/// - Begin at endpoint position [0, 0] with custom attribute 10,
178/// - quadratic bézier curve ending at endpoint [2, 2] custom attribute 20, with control point [1, 1],
179/// - line to endpoint [3, 3] custom attribute 30.
180///
181/// Note that only endpoints have custom attributes, control points do not.
182pub struct PathParser {
183 attribute_buffer: Vec<f32>,
184 float_buffer: String,
185 num_attributes: usize,
186 stop_at: Option<char>,
187 current_position: Point,
188 need_end: bool,
189}
190
191impl PathParser {
192 pub fn new() -> Self {
193 PathParser {
194 attribute_buffer: Vec::new(),
195 float_buffer: String::new(),
196 num_attributes: 0,
197 stop_at: None,
198 current_position: point(0.0, 0.0),
199 need_end: false,
200 }
201 }
202
203 pub fn parse<Iter, Builder>(
204 &mut self,
205 options: &ParserOptions,
206 src: &mut Source<Iter>,
207 output: &mut Builder,
208 ) -> Result<(), ParseError>
209 where
210 Iter: Iterator<Item = char>,
211 Builder: PathBuilder,
212 {
213 self.num_attributes = options.num_attributes;
214 self.stop_at = options.stop_at;
215 self.need_end = false;
216
217 let res = self.parse_path(src, output);
218
219 if self.need_end {
220 output.end(false);
221 }
222
223 res
224 }
225
226 fn parse_path(
227 &mut self,
228 src: &mut Source<impl Iterator<Item = char>>,
229 output: &mut impl PathBuilder,
230 ) -> Result<(), ParseError> {
231 // Per-spec: "If a relative moveto (m) appears as the first element of the path, then it is
232 // treated as a pair of absolute coordinates."
233 self.current_position = point(0.0, 0.0);
234 let mut first_position = point(0.0, 0.0);
235
236 let mut need_start = false;
237 let mut prev_cubic_ctrl = None;
238 let mut prev_quadratic_ctrl = None;
239 let mut implicit_cmd = 'M';
240
241 src.skip_whitespace();
242
243 while !src.finished {
244 let mut cmd = src.current;
245 let cmd_line = src.line;
246 let cmd_col = src.col;
247
248 if self.stop_at == Some(cmd) {
249 break;
250 }
251
252 if cmd.is_ascii_alphabetic() {
253 src.advance_one();
254 } else {
255 cmd = implicit_cmd;
256 }
257
258 if need_start && cmd != 'm' && cmd != 'M' {
259 return Err(ParseError::MissingMoveTo {
260 command: cmd,
261 line: cmd_line,
262 column: cmd_col,
263 });
264 }
265
266 //println!("{:?} at line {:?} column {:?}", cmd, cmd_line, cmd_col);
267
268 let is_relatve = cmd.is_lowercase();
269
270 match cmd {
271 'l' | 'L' => {
272 let to = self.parse_endpoint(is_relatve, src)?;
273 output.line_to(to, &self.attribute_buffer);
274 }
275 'h' | 'H' => {
276 let mut x = self.parse_number(src)?;
277 if is_relatve {
278 x += self.current_position.x;
279 }
280 let to = point(x, self.current_position.y);
281 self.current_position = to;
282 self.parse_attributes(src)?;
283 output.line_to(to, &self.attribute_buffer);
284 }
285 'v' | 'V' => {
286 let mut y = self.parse_number(src)?;
287 if is_relatve {
288 y += self.current_position.y;
289 }
290 let to = point(self.current_position.x, y);
291 self.current_position = to;
292 self.parse_attributes(src)?;
293 output.line_to(to, &self.attribute_buffer);
294 }
295 'q' | 'Q' => {
296 let ctrl = self.parse_point(is_relatve, src)?;
297 let to = self.parse_endpoint(is_relatve, src)?;
298 prev_quadratic_ctrl = Some(ctrl);
299 output.quadratic_bezier_to(ctrl, to, &self.attribute_buffer);
300 }
301 't' | 'T' => {
302 let ctrl = self.get_smooth_ctrl(prev_quadratic_ctrl);
303 let to = self.parse_endpoint(is_relatve, src)?;
304 prev_quadratic_ctrl = Some(ctrl);
305 output.quadratic_bezier_to(ctrl, to, &self.attribute_buffer);
306 }
307 'c' | 'C' => {
308 let ctrl1 = self.parse_point(is_relatve, src)?;
309 let ctrl2 = self.parse_point(is_relatve, src)?;
310 let to = self.parse_endpoint(is_relatve, src)?;
311 prev_cubic_ctrl = Some(ctrl2);
312 output.cubic_bezier_to(ctrl1, ctrl2, to, &self.attribute_buffer);
313 }
314 's' | 'S' => {
315 let ctrl1 = self.get_smooth_ctrl(prev_cubic_ctrl);
316 let ctrl2 = self.parse_point(is_relatve, src)?;
317 let to = self.parse_endpoint(is_relatve, src)?;
318 prev_cubic_ctrl = Some(ctrl2);
319 output.cubic_bezier_to(ctrl1, ctrl2, to, &self.attribute_buffer);
320 }
321 'a' | 'A' => {
322 let prev_attributes = self.attribute_buffer.clone();
323 let mut interpolated_attributes = self.attribute_buffer.clone();
324
325 let from = self.current_position;
326 let rx = self.parse_number(src)?;
327 let ry = self.parse_number(src)?;
328 let x_rotation = self.parse_number(src)?;
329 let large_arc = self.parse_flag(src)?;
330 let sweep = self.parse_flag(src)?;
331 let to = self.parse_endpoint(is_relatve, src)?;
332 let svg_arc = SvgArc {
333 from,
334 to,
335 radii: vector(rx, ry),
336 x_rotation: Angle::degrees(x_rotation),
337 flags: ArcFlags { large_arc, sweep },
338 };
339
340 if svg_arc.is_straight_line() {
341 output.line_to(to, &self.attribute_buffer[..]);
342 } else {
343 let arc = svg_arc.to_arc();
344
345 arc.for_each_quadratic_bezier_with_t(&mut |curve, range| {
346 for i in 0..self.num_attributes {
347 interpolated_attributes[i] = prev_attributes[i] * (1.0 - range.end)
348 + self.attribute_buffer[i] * range.end;
349 }
350 output.quadratic_bezier_to(curve.ctrl, curve.to, &interpolated_attributes);
351 });
352 }
353 }
354 'm' | 'M' => {
355 if self.need_end {
356 output.end(false);
357 }
358
359 let to = self.parse_endpoint(is_relatve, src)?;
360 first_position = to;
361 output.begin(to, &self.attribute_buffer);
362 self.need_end = true;
363 need_start = false;
364 }
365 'z' | 'Z' => {
366 output.end(true);
367 self.current_position = first_position;
368 self.need_end = false;
369 need_start = true;
370 }
371 _ => {
372 return Err(ParseError::Command {
373 command: cmd,
374 line: cmd_line,
375 column: cmd_col,
376 });
377 }
378 }
379
380 match cmd {
381 'c' | 'C' | 's' | 'S' => {
382 prev_quadratic_ctrl = None;
383 }
384 'q' | 'Q' | 't' | 'T' => {
385 prev_cubic_ctrl = None;
386 }
387 _ => {
388 prev_cubic_ctrl = None;
389 prev_quadratic_ctrl = None;
390 }
391 }
392
393 implicit_cmd = match cmd {
394 'm' => 'l',
395 'M' => 'L',
396 'z' => 'm',
397 'Z' => 'M',
398 c => c,
399 };
400
401 src.skip_whitespace();
402 }
403
404 Ok(())
405 }
406
407 fn get_smooth_ctrl(&self, prev_ctrl: Option<Point>) -> Point {
408 if let Some(prev_ctrl) = prev_ctrl {
409 self.current_position + (self.current_position - prev_ctrl)
410 } else {
411 self.current_position
412 }
413 }
414
415 fn parse_endpoint(
416 &mut self,
417 is_relatve: bool,
418 src: &mut Source<impl Iterator<Item = char>>,
419 ) -> Result<Point, ParseError> {
420 let position = self.parse_point(is_relatve, src)?;
421 self.current_position = position;
422
423 self.parse_attributes(src)?;
424
425 Ok(position)
426 }
427
428 fn parse_attributes(
429 &mut self,
430 src: &mut Source<impl Iterator<Item = char>>,
431 ) -> Result<(), ParseError> {
432 self.attribute_buffer.clear();
433 for _ in 0..self.num_attributes {
434 let value = self.parse_number(src)?;
435 self.attribute_buffer.push(value);
436 }
437
438 Ok(())
439 }
440
441 fn parse_point(
442 &mut self,
443 is_relatve: bool,
444 src: &mut Source<impl Iterator<Item = char>>,
445 ) -> Result<Point, ParseError> {
446 let mut x = self.parse_number(src)?;
447 let mut y = self.parse_number(src)?;
448
449 if is_relatve {
450 x += self.current_position.x;
451 y += self.current_position.y;
452 }
453
454 Ok(point(x, y))
455 }
456
457 fn parse_number(
458 &mut self,
459 src: &mut Source<impl Iterator<Item = char>>,
460 ) -> Result<f32, ParseError> {
461 self.float_buffer.clear();
462
463 src.skip_whitespace();
464
465 let line = src.line;
466 let column = src.col;
467
468 if src.current == '-' {
469 self.float_buffer.push('-');
470 src.advance_one();
471 }
472
473 while src.current.is_numeric() {
474 self.float_buffer.push(src.current);
475 src.advance_one();
476 }
477
478 if src.current == 'e' || src.current == 'E' {
479 self.float_buffer.push(src.current);
480 src.advance_one();
481
482 if src.current == '-' {
483 self.float_buffer.push('-');
484 src.advance_one();
485 }
486
487 while src.current.is_numeric() {
488 self.float_buffer.push(src.current);
489 src.advance_one();
490 }
491 }
492
493 if src.current == '.' {
494 self.float_buffer.push('.');
495 src.advance_one();
496 }
497
498 while src.current.is_numeric() {
499 self.float_buffer.push(src.current);
500 src.advance_one();
501 }
502
503 match self.float_buffer.parse::<f32>() {
504 Ok(val) => Ok(val),
505 Err(_) => Err(ParseError::Number {
506 src: std::mem::take(&mut self.float_buffer),
507 line,
508 column,
509 }),
510 }
511 }
512
513 fn parse_flag(
514 &mut self,
515 src: &mut Source<impl Iterator<Item = char>>,
516 ) -> Result<bool, ParseError> {
517 src.skip_whitespace();
518 match src.current {
519 '1' => {
520 src.advance_one();
521 Ok(true)
522 }
523 '0' => {
524 src.advance_one();
525 Ok(false)
526 }
527 _ => Err(ParseError::Flag {
528 src: src.current,
529 line: src.line,
530 column: src.col,
531 })
532 }
533 }
534}
535
536#[cfg(test)]
537use crate::path::{path::BuilderWithAttributes, Path};
538
539#[test]
540fn empty() {
541 let options: ParserOptions = ParserOptions {
542 num_attributes: 0,
543 ..ParserOptions::DEFAULT
544 };
545
546 let mut parser: PathParser = PathParser::new();
547
548 let mut builder = Path::builder_with_attributes(options.num_attributes);
549 parserResult<(), ParseError>
550 .parse(&options, &mut Source::new("".chars()), &mut builder)
551 .unwrap();
552
553 let mut builder = Path::builder_with_attributes(options.num_attributes);
554 parserResult<(), ParseError>
555 .parse(&options, &mut Source::new(" ".chars()), &mut builder)
556 .unwrap();
557}
558
559#[test]
560fn simple_square() {
561 let options: ParserOptions = ParserOptions {
562 num_attributes: 0,
563 ..ParserOptions::DEFAULT
564 };
565 let mut parser: PathParser = PathParser::new();
566 let mut builder = Path::builder_with_attributes(options.num_attributes);
567 let mut src: Source> = Source::new(src:"M 0 0 L 1 0 L 1 1 L 0 1 Z".chars());
568
569 parser.parse(&options, &mut src, &mut builder).unwrap();
570}
571
572#[test]
573fn simple_attr() {
574 let options: ParserOptions = ParserOptions {
575 num_attributes: 1,
576 ..ParserOptions::DEFAULT
577 };
578 let mut parser: PathParser = PathParser::new();
579 let mut builder = Path::builder_with_attributes(options.num_attributes);
580 let mut src: Source> = Source::new(src:"M 0 0 1.0 L 1 0 2.0 L 1 1 3.0 L 0 1 4.0 Z".chars());
581
582 parser.parse(&options, &mut src, &mut builder).unwrap();
583}
584
585#[test]
586fn implicit_polyline() {
587 let options: ParserOptions = ParserOptions {
588 num_attributes: 1,
589 ..ParserOptions::DEFAULT
590 };
591 let mut parser: PathParser = PathParser::new();
592 let mut builder = Path::builder_with_attributes(options.num_attributes);
593 let mut src: Source> = Source::new(src:"0 0 0 1 1 1.0 2 2 2.0 3 3 3".chars());
594
595 parser.parse(&options, &mut src, &mut builder).unwrap();
596}
597
598#[test]
599fn invalid_cmd() {
600 let options = ParserOptions {
601 num_attributes: 1,
602 ..ParserOptions::DEFAULT
603 };
604 let mut parser = PathParser::new();
605 let mut src = Source::new("x 0 0 0".chars());
606
607 let mut builder = Path::builder_with_attributes(options.num_attributes);
608 let result = parser
609 .parse(&options, &mut src, &mut builder)
610 .err()
611 .unwrap();
612 assert_eq!(
613 result,
614 ParseError::Command {
615 command: 'x',
616 line: 0,
617 column: 0
618 }
619 );
620
621 let mut builder = Path::builder_with_attributes(options.num_attributes);
622 let mut src = Source::new("\n M 0 \n0 1 x 1 1 1".chars());
623
624 let result = parser
625 .parse(&options, &mut src, &mut builder)
626 .err()
627 .unwrap();
628 assert_eq!(
629 result,
630 ParseError::Command {
631 command: 'x',
632 line: 2,
633 column: 4
634 }
635 );
636}
637
638#[test]
639fn number_01() {
640 let options = ParserOptions {
641 num_attributes: 0,
642 ..ParserOptions::DEFAULT
643 };
644 let mut parser = PathParser::new();
645
646 // Per SVG spec, this is equivalent to "M 0.6 0.5".
647 let mut src = Source::new("M 0.6.5".chars());
648 let mut builder = Path::builder_with_attributes(options.num_attributes);
649
650 parser.parse(&options, &mut src, &mut builder).unwrap();
651 let path = builder.build();
652
653 let mut iter = path.iter();
654 use path::PathEvent;
655 assert_eq!(
656 iter.next(),
657 Some(PathEvent::Begin {
658 at: point(0.6, 0.5)
659 })
660 );
661 assert_eq!(
662 iter.next(),
663 Some(PathEvent::End {
664 last: point(0.6, 0.5),
665 first: point(0.6, 0.5),
666 close: false
667 })
668 );
669 assert_eq!(iter.next(), None);
670}
671
672#[test]
673fn number_scientific_notation() {
674 let options: ParserOptions = ParserOptions {
675 num_attributes: 0,
676 ..ParserOptions::DEFAULT
677 };
678 let mut parser: PathParser = PathParser::new();
679 let mut builder = Path::builder_with_attributes(options.num_attributes);
680 let mut src: Source> = Source::new(src:"M 1e-2 -1E3".chars());
681
682 parser.parse(&options, &mut src, &mut builder).unwrap();
683}
684
685#[test]
686fn bad_numbers() {
687 let options = ParserOptions {
688 num_attributes: 0,
689 ..ParserOptions::DEFAULT
690 };
691 let mut parser = PathParser::new();
692
693 fn bad_number(r: Result<(), ParseError>) -> bool {
694 match r {
695 Err(ParseError::Number { .. }) => true,
696 _ => {
697 println!("{:?}", r);
698 false
699 }
700 }
701 }
702
703 fn builder(num_attributes: usize) -> BuilderWithAttributes {
704 Path::builder_with_attributes(num_attributes)
705 }
706
707 assert!(bad_number(parser.parse(
708 &options,
709 &mut Source::new("M 0 a".chars()),
710 &mut builder(0)
711 )));
712 assert!(bad_number(parser.parse(
713 &options,
714 &mut Source::new("M 0 --1".chars()),
715 &mut builder(0)
716 )));
717 assert!(bad_number(parser.parse(
718 &options,
719 &mut Source::new("M 0 1ee2".chars()),
720 &mut builder(0)
721 )));
722 assert!(bad_number(parser.parse(
723 &options,
724 &mut Source::new("M 0 1e--1".chars()),
725 &mut builder(0)
726 )));
727 assert!(bad_number(parser.parse(
728 &options,
729 &mut Source::new("M 0 *2".chars()),
730 &mut builder(0)
731 )));
732 assert!(bad_number(parser.parse(
733 &options,
734 &mut Source::new("M 0 e".chars()),
735 &mut builder(0)
736 )));
737 assert!(bad_number(parser.parse(
738 &options,
739 &mut Source::new("M 0 1e".chars()),
740 &mut builder(0)
741 )));
742 assert!(bad_number(parser.parse(
743 &options,
744 &mut Source::new("M 0 +1".chars()),
745 &mut builder(0)
746 )));
747}
748
749#[test]
750fn stop() {
751 let options = ParserOptions {
752 num_attributes: 0,
753 stop_at: Some('|'),
754 ..ParserOptions::DEFAULT
755 };
756 let mut parser = PathParser::new();
757
758 fn builder(num_attributes: usize) -> BuilderWithAttributes {
759 Path::builder_with_attributes(num_attributes)
760 }
761
762 parser
763 .parse(
764 &options,
765 &mut Source::new("M 0 0 | xxxxxx".chars()),
766 &mut builder(0),
767 )
768 .unwrap();
769 parser
770 .parse(
771 &options,
772 &mut Source::new("M 0 0| xxxxxx".chars()),
773 &mut builder(0),
774 )
775 .unwrap();
776 parser
777 .parse(
778 &options,
779 &mut Source::new("| xxxxxx".chars()),
780 &mut builder(0),
781 )
782 .unwrap();
783 parser
784 .parse(
785 &options,
786 &mut Source::new(" | xxxxxx".chars()),
787 &mut builder(0),
788 )
789 .unwrap();
790}
791
792#[test]
793fn need_start() {
794 let options = ParserOptions {
795 num_attributes: 0,
796 stop_at: Some('|'),
797 ..ParserOptions::DEFAULT
798 };
799 let mut parser = PathParser::new();
800
801 let mut builder = Path::builder_with_attributes(options.num_attributes);
802 let res = parser.parse(
803 &options,
804 &mut Source::new("M 0 0 Z L 1 1 2 2 L 3 3 Z M 4 4".chars()),
805 &mut builder,
806 );
807 let p1 = builder.build();
808 match res {
809 Err(ParseError::MissingMoveTo { .. }) => {}
810 _ => {
811 panic!("{:?}", res);
812 }
813 }
814
815 let mut builder = Path::builder_with_attributes(options.num_attributes);
816 parser
817 .parse(&options, &mut Source::new("M 0 0 Z".chars()), &mut builder)
818 .unwrap();
819 let p2 = builder.build();
820
821 let mut p1 = p1.iter();
822 let mut p2 = p2.iter();
823 loop {
824 let e1 = p1.next();
825 let e2 = p2.next();
826
827 assert_eq!(e1, e2);
828
829 if e1.is_none() {
830 break;
831 }
832 }
833}
834