1 | use path::{ |
2 | geom::{ArcFlags, SvgArc}, |
3 | math::{point, vector, Angle, Point}, |
4 | traits::PathBuilder, |
5 | }; |
6 | |
7 | extern crate thiserror; |
8 | |
9 | use self::thiserror::Error; |
10 | |
11 | #[non_exhaustive ] |
12 | #[derive (Error, Clone, Debug, PartialEq)] |
13 | pub 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)] |
34 | pub 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 | |
41 | impl 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. |
49 | pub struct Source<Iter> { |
50 | src: Iter, |
51 | current: char, |
52 | line: i32, |
53 | col: i32, |
54 | finished: bool, |
55 | } |
56 | |
57 | impl<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)] |
138 | pub 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 | |
147 | impl 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)] |
491 | use crate::path::Path; |
492 | |
493 | #[test ] |
494 | fn 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 ] |
514 | fn 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 ] |
527 | fn 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 ] |
540 | fn 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 ] |
553 | fn 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 ] |
593 | fn 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 ] |
627 | fn 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 ] |
640 | fn 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 ] |
672 | fn 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 ] |
695 | fn 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 ] |
738 | fn 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 ] |
759 | fn 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 | |