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