1 | use path::{ |
2 | geom::{ArcFlags, SvgArc}, |
3 | math::{point, vector, Angle, Point}, |
4 | traits::PathBuilder, |
5 | }; |
6 | |
7 | use std::fmt; |
8 | |
9 | #[non_exhaustive ] |
10 | #[derive (Clone, PartialEq)] |
11 | pub 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 | |
34 | impl 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)] |
79 | pub 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 | |
86 | impl 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. |
94 | pub struct Source<Iter> { |
95 | src: Iter, |
96 | current: char, |
97 | line: i32, |
98 | col: i32, |
99 | finished: bool, |
100 | } |
101 | |
102 | impl<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. |
182 | pub 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 | |
191 | impl 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)] |
537 | use crate::path::{path::BuilderWithAttributes, Path}; |
538 | |
539 | #[test ] |
540 | fn 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 ] |
560 | fn 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 ] |
573 | fn 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 ] |
586 | fn 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 ] |
599 | fn 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 ] |
639 | fn 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 ] |
673 | fn 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 ] |
686 | fn 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 ] |
750 | fn 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 ] |
793 | fn 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 | |