1use std::hash::Hash;
2
3use crate::{tokens_helper::TokenizableNumber, Error, Stream};
4
5#[allow(missing_docs)]
11#[derive(Clone, Copy, PartialEq, Debug)]
12pub enum PathSegment {
13 MoveTo {
14 abs: bool,
15 x: f64,
16 y: f64,
17 },
18 LineTo {
19 abs: bool,
20 x: f64,
21 y: f64,
22 },
23 HorizontalLineTo {
24 abs: bool,
25 x: f64,
26 },
27 VerticalLineTo {
28 abs: bool,
29 y: f64,
30 },
31 CurveTo {
32 abs: bool,
33 x1: f64,
34 y1: f64,
35 x2: f64,
36 y2: f64,
37 x: f64,
38 y: f64,
39 },
40 SmoothCurveTo {
41 abs: bool,
42 x2: f64,
43 y2: f64,
44 x: f64,
45 y: f64,
46 },
47 Quadratic {
48 abs: bool,
49 x1: f64,
50 y1: f64,
51 x: f64,
52 y: f64,
53 },
54 SmoothQuadratic {
55 abs: bool,
56 x: f64,
57 y: f64,
58 },
59 EllipticalArc {
60 abs: bool,
61 rx: f64,
62 ry: f64,
63 x_axis_rotation: f64,
64 large_arc: bool,
65 sweep: bool,
66 x: f64,
67 y: f64,
68 },
69 ClosePath {
70 abs: bool,
71 },
72}
73
74impl Hash for PathSegment {
75 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
76 let discriminant = std::mem::discriminant(self);
77 discriminant.hash(state);
78
79 match self {
80 PathSegment::MoveTo { abs, x, y } => {
81 abs.hash(state);
82 x.to_bits().hash(state);
83 y.to_bits().hash(state);
84 }
85 PathSegment::LineTo { abs, x, y } => {
86 abs.hash(state);
87 x.to_bits().hash(state);
88 y.to_bits().hash(state);
89 }
90 PathSegment::HorizontalLineTo { abs, x } => {
91 abs.hash(state);
92 x.to_bits().hash(state);
93 }
94 PathSegment::VerticalLineTo { abs, y } => {
95 abs.hash(state);
96 y.to_bits().hash(state);
97 }
98 PathSegment::CurveTo {
99 abs,
100 x1,
101 y1,
102 x2,
103 y2,
104 x,
105 y,
106 } => {
107 abs.hash(state);
108 x1.to_bits().hash(state);
109 y1.to_bits().hash(state);
110 x2.to_bits().hash(state);
111 y2.to_bits().hash(state);
112 x.to_bits().hash(state);
113 y.to_bits().hash(state);
114 }
115 PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => {
116 abs.hash(state);
117 x2.to_bits().hash(state);
118 y2.to_bits().hash(state);
119 x.to_bits().hash(state);
120 y.to_bits().hash(state);
121 }
122 PathSegment::Quadratic { abs, x1, y1, x, y } => {
123 abs.hash(state);
124 x1.to_bits().hash(state);
125 y1.to_bits().hash(state);
126 x.to_bits().hash(state);
127 y.to_bits().hash(state);
128 }
129 PathSegment::SmoothQuadratic { abs, x, y } => {
130 abs.hash(state);
131 x.to_bits().hash(state);
132 y.to_bits().hash(state);
133 }
134 PathSegment::EllipticalArc {
135 abs,
136 rx,
137 ry,
138 x_axis_rotation,
139 large_arc,
140 sweep,
141 x,
142 y,
143 } => {
144 abs.hash(state);
145 rx.to_bits().hash(state);
146 ry.to_bits().hash(state);
147 x_axis_rotation.to_bits().hash(state);
148 large_arc.hash(state);
149 sweep.hash(state);
150 x.to_bits().hash(state);
151 y.to_bits().hash(state);
152 }
153 PathSegment::ClosePath { abs } => {
154 abs.hash(state);
155 }
156 }
157 }
158}
159
160impl quote::ToTokens for PathSegment {
161 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
162 use quote::quote;
163 match self {
164 PathSegment::MoveTo { abs, x, y } => {
165 let x = TokenizableNumber(*x);
166 let y = TokenizableNumber(*y);
167 quote! { svgrtypes::PathSegment::MoveTo { abs: #abs, x: #x, y: #y } }
168 }
169 PathSegment::LineTo { abs, x, y } => {
170 let x = TokenizableNumber(*x);
171 let y = TokenizableNumber(*y);
172 quote! { svgrtypes::PathSegment::LineTo { abs: #abs, x: #x, y: #y } }
173 }
174 PathSegment::HorizontalLineTo { abs, x } => {
175 let x = TokenizableNumber(*x);
176 quote! { svgrtypes::PathSegment::HorizontalLineTo { abs: #abs, x: #x } }
177 }
178 PathSegment::VerticalLineTo { abs, y } => {
179 let y = TokenizableNumber(*y);
180 quote! { svgrtypes::PathSegment::VerticalLineTo { abs: #abs, y: #y } }
181 }
182 PathSegment::CurveTo { abs, x1, y1, x2, y2, x, y } => {
183 let x1 = TokenizableNumber(*x1);
184 let y1 = TokenizableNumber(*y1);
185 let x2 = TokenizableNumber(*x2);
186 let y2 = TokenizableNumber(*y2);
187 let x = TokenizableNumber(*x);
188 let y = TokenizableNumber(*y);
189 quote! { svgrtypes::PathSegment::CurveTo { abs: #abs, x1: #x1, y1: #y1, x2: #x2, y2: #y2, x: #x, y: #y } }
190 }
191 PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => {
192 let x2 = TokenizableNumber(*x2);
193 let y2 = TokenizableNumber(*y2);
194 let x = TokenizableNumber(*x);
195 let y = TokenizableNumber(*y);
196 quote! { svgrtypes::PathSegment::SmoothCurveTo { abs: #abs, x2: #x2, y2: #y2, x: #x, y: #y } }
197 }
198 PathSegment::Quadratic { abs, x1, y1, x, y } => {
199 let x1 = TokenizableNumber(*x1);
200 let y1 = TokenizableNumber(*y1);
201 let x = TokenizableNumber(*x);
202 let y = TokenizableNumber(*y);
203 quote! { svgrtypes::PathSegment::Quadratic { abs: #abs, x1: #x1, y1: #y1, x: #x, y: #y } }
204 }
205 PathSegment::SmoothQuadratic { abs, x, y } => {
206 let x = TokenizableNumber(*x);
207 let y = TokenizableNumber(*y);
208 quote! { svgrtypes::PathSegment::SmoothQuadratic { abs: #abs, x: #x, y: #y } }
209 }
210 PathSegment::EllipticalArc { abs, rx, ry, x_axis_rotation, large_arc, sweep, x, y } => {
211 let rx = TokenizableNumber(*rx);
212 let ry = TokenizableNumber(*ry);
213 let x_axis_rotation = TokenizableNumber(*x_axis_rotation);
214 let x = TokenizableNumber(*x);
215 let y = TokenizableNumber(*y);
216 quote! { svgrtypes::PathSegment::EllipticalArc { abs: #abs, rx: #rx, ry: #ry, x_axis_rotation: #x_axis_rotation, large_arc: #large_arc, sweep: #sweep, x: #x, y: #y } }
217 }
218 PathSegment::ClosePath { abs } => {
219 quote! { svgrtypes::PathSegment::ClosePath { abs: #abs } }
220 }
221 }
222 .to_tokens(tokens)
223 }
224}
225
226#[derive(Clone, Copy, PartialEq, Eq, Debug)]
260pub struct PathParser<'a> {
261 stream: Stream<'a>,
262 prev_cmd: Option<u8>,
263}
264
265impl<'a> From<&'a str> for PathParser<'a> {
266 #[inline]
267 fn from(v: &'a str) -> Self {
268 PathParser {
269 stream: Stream::from(v),
270 prev_cmd: None,
271 }
272 }
273}
274
275impl<'a> Iterator for PathParser<'a> {
276 type Item = Result<PathSegment, Error>;
277
278 #[inline]
279 fn next(&mut self) -> Option<Self::Item> {
280 let s = &mut self.stream;
281
282 s.skip_spaces();
283
284 if s.at_end() {
285 return None;
286 }
287
288 let res = next_impl(s, &mut self.prev_cmd);
289 if res.is_err() {
290 s.jump_to_end();
291 }
292
293 Some(res)
294 }
295}
296
297fn next_impl(s: &mut Stream, prev_cmd: &mut Option<u8>) -> Result<PathSegment, Error> {
298 let start = s.pos();
299
300 let has_prev_cmd = prev_cmd.is_some();
301 let first_char = s.curr_byte_unchecked();
302
303 if !has_prev_cmd && !is_cmd(first_char) {
304 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
305 }
306
307 if !has_prev_cmd && !matches!(first_char, b'M' | b'm') {
308 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
310 }
311
312 let is_implicit_move_to;
314 let cmd: u8;
315 if is_cmd(first_char) {
316 is_implicit_move_to = false;
317 cmd = first_char;
318 s.advance(1);
319 } else if is_number_start(first_char) && has_prev_cmd {
320 let p_cmd = prev_cmd.unwrap();
322
323 if p_cmd == b'Z' || p_cmd == b'z' {
324 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
326 }
327
328 if p_cmd == b'M' || p_cmd == b'm' {
329 is_implicit_move_to = true;
333 cmd = if is_absolute(p_cmd) { b'L' } else { b'l' };
334 } else {
335 is_implicit_move_to = false;
336 cmd = p_cmd;
337 }
338 } else {
339 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
340 }
341
342 let cmdl = to_relative(cmd);
343 let absolute = is_absolute(cmd);
344 let token = match cmdl {
345 b'm' => PathSegment::MoveTo {
346 abs: absolute,
347 x: s.parse_list_number()?,
348 y: s.parse_list_number()?,
349 },
350 b'l' => PathSegment::LineTo {
351 abs: absolute,
352 x: s.parse_list_number()?,
353 y: s.parse_list_number()?,
354 },
355 b'h' => PathSegment::HorizontalLineTo {
356 abs: absolute,
357 x: s.parse_list_number()?,
358 },
359 b'v' => PathSegment::VerticalLineTo {
360 abs: absolute,
361 y: s.parse_list_number()?,
362 },
363 b'c' => PathSegment::CurveTo {
364 abs: absolute,
365 x1: s.parse_list_number()?,
366 y1: s.parse_list_number()?,
367 x2: s.parse_list_number()?,
368 y2: s.parse_list_number()?,
369 x: s.parse_list_number()?,
370 y: s.parse_list_number()?,
371 },
372 b's' => PathSegment::SmoothCurveTo {
373 abs: absolute,
374 x2: s.parse_list_number()?,
375 y2: s.parse_list_number()?,
376 x: s.parse_list_number()?,
377 y: s.parse_list_number()?,
378 },
379 b'q' => PathSegment::Quadratic {
380 abs: absolute,
381 x1: s.parse_list_number()?,
382 y1: s.parse_list_number()?,
383 x: s.parse_list_number()?,
384 y: s.parse_list_number()?,
385 },
386 b't' => PathSegment::SmoothQuadratic {
387 abs: absolute,
388 x: s.parse_list_number()?,
389 y: s.parse_list_number()?,
390 },
391 b'a' => {
392 PathSegment::EllipticalArc {
394 abs: absolute,
395 rx: s.parse_list_number()?,
396 ry: s.parse_list_number()?,
397 x_axis_rotation: s.parse_list_number()?,
398 large_arc: parse_flag(s)?,
399 sweep: parse_flag(s)?,
400 x: s.parse_list_number()?,
401 y: s.parse_list_number()?,
402 }
403 }
404 b'z' => PathSegment::ClosePath { abs: absolute },
405 _ => unreachable!(),
406 };
407
408 *prev_cmd = Some(if is_implicit_move_to {
409 if absolute {
410 b'M'
411 } else {
412 b'm'
413 }
414 } else {
415 cmd
416 });
417
418 Ok(token)
419}
420
421#[rustfmt::skip]
423#[inline]
424fn is_cmd(c: u8) -> bool {
425 matches!(c,
426 b'M' | b'm'
427 | b'Z' | b'z'
428 | b'L' | b'l'
429 | b'H' | b'h'
430 | b'V' | b'v'
431 | b'C' | b'c'
432 | b'S' | b's'
433 | b'Q' | b'q'
434 | b'T' | b't'
435 | b'A' | b'a')
436}
437
438#[inline]
440fn is_absolute(c: u8) -> bool {
441 debug_assert!(is_cmd(c));
442 matches!(
443 c,
444 b'M' | b'Z' | b'L' | b'H' | b'V' | b'C' | b'S' | b'Q' | b'T' | b'A'
445 )
446}
447
448#[inline]
450fn to_relative(c: u8) -> u8 {
451 debug_assert!(is_cmd(c));
452 match c {
453 b'M' => b'm',
454 b'Z' => b'z',
455 b'L' => b'l',
456 b'H' => b'h',
457 b'V' => b'v',
458 b'C' => b'c',
459 b'S' => b's',
460 b'Q' => b'q',
461 b'T' => b't',
462 b'A' => b'a',
463 _ => c,
464 }
465}
466
467#[inline]
468fn is_number_start(c: u8) -> bool {
469 matches!(c, b'0'..=b'9' | b'.' | b'-' | b'+')
470}
471
472fn parse_flag(s: &mut Stream) -> Result<bool, Error> {
475 s.skip_spaces();
476
477 let c = s.curr_byte()?;
478 match c {
479 b'0' | b'1' => {
480 s.advance(1);
481 if s.is_curr_byte_eq(b',') {
482 s.advance(1);
483 }
484 s.skip_spaces();
485
486 Ok(c == b'1')
487 }
488 _ => Err(Error::UnexpectedData(s.calc_char_pos_at(s.pos()))),
489 }
490}
491
492#[rustfmt::skip]
493#[cfg(test)]
494mod tests {
495 use super::*;
496
497 macro_rules! test {
498 ($name:ident, $text:expr, $( $seg:expr ),*) => (
499 #[test]
500 fn $name() {
501 let mut s = PathParser::from($text);
502 $(
503 assert_eq!(s.next().unwrap().unwrap(), $seg);
504 )*
505
506 if let Some(res) = s.next() {
507 assert!(res.is_err());
508 }
509 }
510 )
511 }
512
513 test!(null, "", );
514 test!(not_a_path, "q", );
515 test!(not_a_move_to, "L 20 30", );
516 test!(stop_on_err_1, "M 10 20 L 30 40 L 50",
517 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
518 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 }
519 );
520
521 test!(move_to_1, "M 10 20", PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 });
522 test!(move_to_2, "m 10 20", PathSegment::MoveTo { abs: false, x: 10.0, y: 20.0 });
523 test!(move_to_3, "M 10 20 30 40 50 60",
524 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
525 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
526 PathSegment::LineTo { abs: true, x: 50.0, y: 60.0 }
527 );
528 test!(move_to_4, "M 10 20 30 40 50 60 M 70 80 90 100 110 120",
529 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
530 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
531 PathSegment::LineTo { abs: true, x: 50.0, y: 60.0 },
532 PathSegment::MoveTo { abs: true, x: 70.0, y: 80.0 },
533 PathSegment::LineTo { abs: true, x: 90.0, y: 100.0 },
534 PathSegment::LineTo { abs: true, x: 110.0, y: 120.0 }
535 );
536
537 test!(arc_to_1, "M 10 20 A 5 5 30 1 1 20 20",
538 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
539 PathSegment::EllipticalArc {
540 abs: true,
541 rx: 5.0, ry: 5.0,
542 x_axis_rotation: 30.0,
543 large_arc: true, sweep: true,
544 x: 20.0, y: 20.0
545 }
546 );
547
548 test!(arc_to_2, "M 10 20 a 5 5 30 0 0 20 20",
549 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
550 PathSegment::EllipticalArc {
551 abs: false,
552 rx: 5.0, ry: 5.0,
553 x_axis_rotation: 30.0,
554 large_arc: false, sweep: false,
555 x: 20.0, y: 20.0
556 }
557 );
558
559 test!(arc_to_10, "M10-20A5.5.3-4 010-.1",
560 PathSegment::MoveTo { abs: true, x: 10.0, y: -20.0 },
561 PathSegment::EllipticalArc {
562 abs: true,
563 rx: 5.5, ry: 0.3,
564 x_axis_rotation: -4.0,
565 large_arc: false, sweep: true,
566 x: 0.0, y: -0.1
567 }
568 );
569
570 test!(separator_1, "M 10 20 L 5 15 C 10 20 30 40 50 60",
571 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
572 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
573 PathSegment::CurveTo {
574 abs: true,
575 x1: 10.0, y1: 20.0,
576 x2: 30.0, y2: 40.0,
577 x: 50.0, y: 60.0,
578 }
579 );
580
581 test!(separator_2, "M 10, 20 L 5, 15 C 10, 20 30, 40 50, 60",
582 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
583 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
584 PathSegment::CurveTo {
585 abs: true,
586 x1: 10.0, y1: 20.0,
587 x2: 30.0, y2: 40.0,
588 x: 50.0, y: 60.0,
589 }
590 );
591
592 test!(separator_3, "M 10,20 L 5,15 C 10,20 30,40 50,60",
593 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
594 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
595 PathSegment::CurveTo {
596 abs: true,
597 x1: 10.0, y1: 20.0,
598 x2: 30.0, y2: 40.0,
599 x: 50.0, y: 60.0,
600 }
601 );
602
603 test!(separator_4, "M10, 20 L5, 15 C10, 20 30 40 50 60",
604 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
605 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
606 PathSegment::CurveTo {
607 abs: true,
608 x1: 10.0, y1: 20.0,
609 x2: 30.0, y2: 40.0,
610 x: 50.0, y: 60.0,
611 }
612 );
613
614 test!(separator_5, "M10 20V30H40V50H60Z",
615 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
616 PathSegment::VerticalLineTo { abs: true, y: 30.0 },
617 PathSegment::HorizontalLineTo { abs: true, x: 40.0 },
618 PathSegment::VerticalLineTo { abs: true, y: 50.0 },
619 PathSegment::HorizontalLineTo { abs: true, x: 60.0 },
620 PathSegment::ClosePath { abs: true }
621 );
622
623 test!(all_segments_1, "M 10 20 L 30 40 H 50 V 60 C 70 80 90 100 110 120 S 130 140 150 160
624 Q 170 180 190 200 T 210 220 A 50 50 30 1 1 230 240 Z",
625 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
626 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
627 PathSegment::HorizontalLineTo { abs: true, x: 50.0 },
628 PathSegment::VerticalLineTo { abs: true, y: 60.0 },
629 PathSegment::CurveTo {
630 abs: true,
631 x1: 70.0, y1: 80.0,
632 x2: 90.0, y2: 100.0,
633 x: 110.0, y: 120.0,
634 },
635 PathSegment::SmoothCurveTo {
636 abs: true,
637 x2: 130.0, y2: 140.0,
638 x: 150.0, y: 160.0,
639 },
640 PathSegment::Quadratic {
641 abs: true,
642 x1: 170.0, y1: 180.0,
643 x: 190.0, y: 200.0,
644 },
645 PathSegment::SmoothQuadratic { abs: true, x: 210.0, y: 220.0 },
646 PathSegment::EllipticalArc {
647 abs: true,
648 rx: 50.0, ry: 50.0,
649 x_axis_rotation: 30.0,
650 large_arc: true, sweep: true,
651 x: 230.0, y: 240.0
652 },
653 PathSegment::ClosePath { abs: true }
654 );
655
656 test!(all_segments_2, "m 10 20 l 30 40 h 50 v 60 c 70 80 90 100 110 120 s 130 140 150 160
657 q 170 180 190 200 t 210 220 a 50 50 30 1 1 230 240 z",
658 PathSegment::MoveTo { abs: false, x: 10.0, y: 20.0 },
659 PathSegment::LineTo { abs: false, x: 30.0, y: 40.0 },
660 PathSegment::HorizontalLineTo { abs: false, x: 50.0 },
661 PathSegment::VerticalLineTo { abs: false, y: 60.0 },
662 PathSegment::CurveTo {
663 abs: false,
664 x1: 70.0, y1: 80.0,
665 x2: 90.0, y2: 100.0,
666 x: 110.0, y: 120.0,
667 },
668 PathSegment::SmoothCurveTo {
669 abs: false,
670 x2: 130.0, y2: 140.0,
671 x: 150.0, y: 160.0,
672 },
673 PathSegment::Quadratic {
674 abs: false,
675 x1: 170.0, y1: 180.0,
676 x: 190.0, y: 200.0,
677 },
678 PathSegment::SmoothQuadratic { abs: false, x: 210.0, y: 220.0 },
679 PathSegment::EllipticalArc {
680 abs: false,
681 rx: 50.0, ry: 50.0,
682 x_axis_rotation: 30.0,
683 large_arc: true, sweep: true,
684 x: 230.0, y: 240.0
685 },
686 PathSegment::ClosePath { abs: false }
687 );
688
689 test!(close_path_1, "M10 20 L 30 40 ZM 100 200 L 300 400",
690 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
691 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
692 PathSegment::ClosePath { abs: true },
693 PathSegment::MoveTo { abs: true, x: 100.0, y: 200.0 },
694 PathSegment::LineTo { abs: true, x: 300.0, y: 400.0 }
695 );
696
697 test!(close_path_2, "M10 20 L 30 40 zM 100 200 L 300 400",
698 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
699 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
700 PathSegment::ClosePath { abs: false },
701 PathSegment::MoveTo { abs: true, x: 100.0, y: 200.0 },
702 PathSegment::LineTo { abs: true, x: 300.0, y: 400.0 }
703 );
704
705 test!(close_path_3, "M10 20 L 30 40 Z Z Z",
706 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
707 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
708 PathSegment::ClosePath { abs: true },
709 PathSegment::ClosePath { abs: true },
710 PathSegment::ClosePath { abs: true }
711 );
712
713 test!(invalid_1, "M\t.", );
715
716 test!(invalid_2, "M 0 0 Z 2",
718 PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 },
719 PathSegment::ClosePath { abs: true }
720 );
721
722 test!(invalid_3, "M 0 0 Z H 10",
724 PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 },
725 PathSegment::ClosePath { abs: true },
726 PathSegment::HorizontalLineTo { abs: true, x: 10.0 }
727 );
728}
729
730#[allow(missing_docs)]
732#[derive(Clone, Copy, PartialEq, Debug)]
733pub enum SimplePathSegment {
734 MoveTo {
735 x: f64,
736 y: f64,
737 },
738 LineTo {
739 x: f64,
740 y: f64,
741 },
742 CurveTo {
743 x1: f64,
744 y1: f64,
745 x2: f64,
746 y2: f64,
747 x: f64,
748 y: f64,
749 },
750 Quadratic {
751 x1: f64,
752 y1: f64,
753 x: f64,
754 y: f64,
755 },
756 ClosePath,
757}
758
759#[derive(Clone, Debug)]
771pub struct SimplifyingPathParser<'a> {
772 parser: PathParser<'a>,
773
774 prev_mx: f64,
776 prev_my: f64,
777
778 prev_tx: f64,
780 prev_ty: f64,
781
782 prev_x: f64,
784 prev_y: f64,
785
786 prev_seg: PathSegment,
787 prev_simple_seg: Option<SimplePathSegment>,
788
789 buffer: Vec<SimplePathSegment>,
790}
791
792impl<'a> From<&'a str> for SimplifyingPathParser<'a> {
793 #[inline]
794 fn from(v: &'a str) -> Self {
795 SimplifyingPathParser {
796 parser: PathParser::from(v),
797 prev_mx: 0.0,
798 prev_my: 0.0,
799 prev_tx: 0.0,
800 prev_ty: 0.0,
801 prev_x: 0.0,
802 prev_y: 0.0,
803 prev_seg: PathSegment::MoveTo {
804 abs: true,
805 x: 0.0,
806 y: 0.0,
807 },
808 prev_simple_seg: None,
809 buffer: Vec::new(),
810 }
811 }
812}
813
814impl<'a> Iterator for SimplifyingPathParser<'a> {
815 type Item = Result<SimplePathSegment, Error>;
816
817 #[inline]
818 fn next(&mut self) -> Option<Self::Item> {
819 if !self.buffer.is_empty() {
820 return Some(Ok(self.buffer.remove(0)));
821 }
822
823 let segment = match self.parser.next()? {
824 Ok(v) => v,
825 Err(e) => return Some(Err(e)),
826 };
827
828 match segment {
829 PathSegment::MoveTo { abs, mut x, mut y } => {
830 if !abs {
831 if let Some(SimplePathSegment::ClosePath) = self.prev_simple_seg {
834 x += self.prev_mx;
835 y += self.prev_my;
836 } else {
837 x += self.prev_x;
838 y += self.prev_y;
839 }
840 }
841
842 self.buffer.push(SimplePathSegment::MoveTo { x, y });
843 self.prev_seg = segment;
844 }
845 PathSegment::LineTo { abs, mut x, mut y } => {
846 if !abs {
847 x += self.prev_x;
848 y += self.prev_y;
849 }
850
851 self.buffer.push(SimplePathSegment::LineTo { x, y });
852 self.prev_seg = segment;
853 }
854 PathSegment::HorizontalLineTo { abs, mut x } => {
855 if !abs {
856 x += self.prev_x;
857 }
858
859 self.buffer
860 .push(SimplePathSegment::LineTo { x, y: self.prev_y });
861 self.prev_seg = segment;
862 }
863 PathSegment::VerticalLineTo { abs, mut y } => {
864 if !abs {
865 y += self.prev_y;
866 }
867
868 self.buffer
869 .push(SimplePathSegment::LineTo { x: self.prev_x, y });
870 self.prev_seg = segment;
871 }
872 PathSegment::CurveTo {
873 abs,
874 mut x1,
875 mut y1,
876 mut x2,
877 mut y2,
878 mut x,
879 mut y,
880 } => {
881 if !abs {
882 x1 += self.prev_x;
883 y1 += self.prev_y;
884 x2 += self.prev_x;
885 y2 += self.prev_y;
886 x += self.prev_x;
887 y += self.prev_y;
888 }
889
890 self.buffer.push(SimplePathSegment::CurveTo {
891 x1,
892 y1,
893 x2,
894 y2,
895 x,
896 y,
897 });
898
899 self.prev_seg = PathSegment::CurveTo {
901 abs: true,
902 x1,
903 y1,
904 x2,
905 y2,
906 x,
907 y,
908 };
909 }
910 PathSegment::SmoothCurveTo {
911 abs,
912 mut x2,
913 mut y2,
914 mut x,
915 mut y,
916 } => {
917 let (x1, y1) = match self.prev_seg {
923 PathSegment::CurveTo { x2, y2, x, y, .. }
924 | PathSegment::SmoothCurveTo { x2, y2, x, y, .. } => {
925 (x * 2.0 - x2, y * 2.0 - y2)
926 }
927 _ => (self.prev_x, self.prev_y),
928 };
929
930 if !abs {
931 x2 += self.prev_x;
932 y2 += self.prev_y;
933 x += self.prev_x;
934 y += self.prev_y;
935 }
936
937 self.buffer.push(SimplePathSegment::CurveTo {
938 x1,
939 y1,
940 x2,
941 y2,
942 x,
943 y,
944 });
945
946 self.prev_seg = PathSegment::SmoothCurveTo {
948 abs: true,
949 x2,
950 y2,
951 x,
952 y,
953 };
954 }
955 PathSegment::Quadratic {
956 abs,
957 mut x1,
958 mut y1,
959 mut x,
960 mut y,
961 } => {
962 if !abs {
963 x1 += self.prev_x;
964 y1 += self.prev_y;
965 x += self.prev_x;
966 y += self.prev_y;
967 }
968
969 self.buffer
970 .push(SimplePathSegment::Quadratic { x1, y1, x, y });
971
972 self.prev_seg = PathSegment::Quadratic {
974 abs: true,
975 x1,
976 y1,
977 x,
978 y,
979 };
980 }
981 PathSegment::SmoothQuadratic { abs, mut x, mut y } => {
982 let (x1, y1) = match self.prev_seg {
988 PathSegment::Quadratic { x1, y1, x, y, .. } => (x * 2.0 - x1, y * 2.0 - y1),
989 PathSegment::SmoothQuadratic { x, y, .. } => {
990 (x * 2.0 - self.prev_tx, y * 2.0 - self.prev_ty)
991 }
992 _ => (self.prev_x, self.prev_y),
993 };
994
995 self.prev_tx = x1;
996 self.prev_ty = y1;
997
998 if !abs {
999 x += self.prev_x;
1000 y += self.prev_y;
1001 }
1002
1003 self.buffer
1004 .push(SimplePathSegment::Quadratic { x1, y1, x, y });
1005
1006 self.prev_seg = PathSegment::SmoothQuadratic { abs: true, x, y };
1008 }
1009 PathSegment::EllipticalArc {
1010 abs,
1011 rx,
1012 ry,
1013 x_axis_rotation,
1014 large_arc,
1015 sweep,
1016 mut x,
1017 mut y,
1018 } => {
1019 if !abs {
1020 x += self.prev_x;
1021 y += self.prev_y;
1022 }
1023
1024 let svg_arc = kurbo::SvgArc {
1025 from: kurbo::Point::new(self.prev_x, self.prev_y),
1026 to: kurbo::Point::new(x, y),
1027 radii: kurbo::Vec2::new(rx, ry),
1028 x_rotation: x_axis_rotation.to_radians(),
1029 large_arc,
1030 sweep,
1031 };
1032
1033 match kurbo::Arc::from_svg_arc(&svg_arc) {
1034 Some(arc) => {
1035 arc.to_cubic_beziers(0.1, |p1, p2, p| {
1036 self.buffer.push(SimplePathSegment::CurveTo {
1037 x1: p1.x,
1038 y1: p1.y,
1039 x2: p2.x,
1040 y2: p2.y,
1041 x: p.x,
1042 y: p.y,
1043 });
1044 });
1045 }
1046 None => {
1047 self.buffer.push(SimplePathSegment::LineTo { x, y });
1048 }
1049 }
1050
1051 self.prev_seg = segment;
1052 }
1053 PathSegment::ClosePath { .. } => {
1054 if let Some(SimplePathSegment::ClosePath) = self.prev_simple_seg {
1055 } else {
1058 self.buffer.push(SimplePathSegment::ClosePath);
1059 }
1060
1061 self.prev_seg = segment;
1062 }
1063 }
1064
1065 if let Some(new_segment) = self.buffer.last() {
1067 self.prev_simple_seg = Some(*new_segment);
1068
1069 match *new_segment {
1070 SimplePathSegment::MoveTo { x, y } => {
1071 self.prev_x = x;
1072 self.prev_y = y;
1073 self.prev_mx = self.prev_x;
1074 self.prev_my = self.prev_y;
1075 }
1076 SimplePathSegment::LineTo { x, y } => {
1077 self.prev_x = x;
1078 self.prev_y = y;
1079 }
1080 SimplePathSegment::CurveTo { x, y, .. } => {
1081 self.prev_x = x;
1082 self.prev_y = y;
1083 }
1084 SimplePathSegment::Quadratic { x, y, .. } => {
1085 self.prev_x = x;
1086 self.prev_y = y;
1087 }
1088 SimplePathSegment::ClosePath => {
1089 self.prev_x = self.prev_mx;
1092 self.prev_y = self.prev_my;
1093 }
1094 }
1095 }
1096
1097 if self.buffer.is_empty() {
1098 return self.next();
1099 }
1100
1101 Some(Ok(self.buffer.remove(0)))
1102 }
1103}
1104
1105#[rustfmt::skip]
1106#[cfg(test)]
1107mod simple_tests {
1108 use super::*;
1109
1110 macro_rules! test {
1111 ($name:ident, $text:expr, $( $seg:expr ),*) => (
1112 #[test]
1113 fn $name() {
1114 let mut s = SimplifyingPathParser::from($text);
1115 $(
1116 assert_eq!(s.next().unwrap().unwrap(), $seg);
1117 )*
1118
1119 if let Some(res) = s.next() {
1120 assert!(res.is_err());
1121 }
1122 }
1123 )
1124 }
1125
1126 test!(ignore_duplicated_close_paths, "M 10 20 L 30 40 Z Z Z Z",
1127 SimplePathSegment::MoveTo { x: 10.0, y: 20.0 },
1128 SimplePathSegment::LineTo { x: 30.0, y: 40.0 },
1129 SimplePathSegment::ClosePath
1130 );
1131
1132 test!(relative_move_to, "m 30 40 110 120 -20 -130",
1133 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1134 SimplePathSegment::LineTo { x: 140.0, y: 160.0 },
1135 SimplePathSegment::LineTo { x: 120.0, y: 30.0 }
1136 );
1137
1138 test!(smooth_curve_to_after_move_to, "M 30 40 S 171 45 180 155",
1139 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1140 SimplePathSegment::CurveTo { x1: 30.0, y1: 40.0, x2: 171.0, y2: 45.0, x: 180.0, y: 155.0 }
1141 );
1142
1143 test!(smooth_curve_to_after_curve_to, "M 30 40 C 16 137 171 45 100 90 S 171 45 180 155",
1144 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1145 SimplePathSegment::CurveTo { x1: 16.0, y1: 137.0, x2: 171.0, y2: 45.0, x: 100.0, y: 90.0 },
1146 SimplePathSegment::CurveTo { x1: 29.0, y1: 135.0, x2: 171.0, y2: 45.0, x: 180.0, y: 155.0 }
1147 );
1148
1149 test!(smooth_quadratic_after_move_to, "M 30 40 T 180 155",
1150 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1151 SimplePathSegment::Quadratic { x1: 30.0, y1: 40.0, x: 180.0, y: 155.0 }
1152 );
1153
1154 test!(smooth_quadratic_after_quadratic, "M 30 40 Q 171 45 100 90 T 160 180",
1155 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1156 SimplePathSegment::Quadratic { x1: 171.0, y1: 45.0, x: 100.0, y: 90.0 },
1157 SimplePathSegment::Quadratic { x1: 29.0, y1: 135.0, x: 160.0, y: 180.0 }
1158 );
1159
1160 test!(relative_smooth_quadratic_after_quadratic, "M 30 40 Q 171 45 100 90 t 60 80",
1161 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1162 SimplePathSegment::Quadratic { x1: 171.0, y1: 45.0, x: 100.0, y: 90.0 },
1163 SimplePathSegment::Quadratic { x1: 29.0, y1: 135.0, x: 160.0, y: 170.0 }
1164 );
1165
1166 test!(relative_smooth_quadratic_after_relative_quadratic, "M 30 40 q 171 45 50 40 t 60 80",
1167 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1168 SimplePathSegment::Quadratic { x1: 201.0, y1: 85.0, x: 80.0, y: 80.0 },
1169 SimplePathSegment::Quadratic { x1: -41.0, y1: 75.0, x: 140.0, y: 160.0 }
1170 );
1171
1172 test!(smooth_quadratic_after_smooth_quadratic, "M 30 30 T 40 140 T 170 30",
1173 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1174 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1175 SimplePathSegment::Quadratic { x1: 50.0, y1: 250.0, x: 170.0, y: 30.0 }
1176 );
1177
1178 test!(smooth_quadratic_after_relative_smooth_quadratic, "M 30 30 T 40 140 t 100 -30",
1179 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1180 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1181 SimplePathSegment::Quadratic { x1: 50.0, y1: 250.0, x: 140.0, y: 110.0 }
1182 );
1183
1184 test!(smooth_quadratic_after_relative_quadratic, "M 30 30 T 40 140 q 30 100 120 -30",
1185 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1186 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1187 SimplePathSegment::Quadratic { x1: 70.0, y1: 240.0, x: 160.0, y: 110.0 }
1188 );
1189
1190 test!(smooth_quadratic_after_relative_smooth_curve_to, "M 30 30 T 40 170 s 90 -20 90 -90",
1191 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1192 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 170.0 },
1193 SimplePathSegment::CurveTo { x1: 40.0, y1: 170.0, x2: 130.0, y2: 150.0, x: 130.0, y: 80.0 }
1194 );
1195
1196 test!(quadratic_after_smooth_quadratic, "M 30 30 T 40 140 Q 80 180 170 30",
1197 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1198 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1199 SimplePathSegment::Quadratic { x1: 80.0, y1: 180.0, x: 170.0, y: 30.0 }
1200 );
1201
1202 test!(arc_to, "M 30 40 A 40 30 20 1 1 150 100",
1203 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
1204 SimplePathSegment::CurveTo {
1205 x1: 44.74826984236894, y1: 15.992274712892893,
1206 x2: 83.56702078968499, y2: 9.961625634418603,
1207 x: 116.70410629329004, y: 26.53016838622112
1208 },
1209 SimplePathSegment::CurveTo {
1210 x1: 149.8411917968951, y1: 43.09871113802364,
1211 x2: 164.74827129549442, y2: 75.99227543945563,
1212 x: 150.0, y: 100.0
1213 }
1214 );
1215}