1use crate::{Error, Stream};
2
3/// Representation of a path segment.
4///
5/// If you want to change the segment type (for example MoveTo to LineTo)
6/// you should create a new segment.
7/// But you still can change points or make segment relative or absolute.
8#[allow(missing_docs)]
9#[derive(Clone, Copy, PartialEq, Debug)]
10pub enum PathSegment {
11 MoveTo {
12 abs: bool,
13 x: f64,
14 y: f64,
15 },
16 LineTo {
17 abs: bool,
18 x: f64,
19 y: f64,
20 },
21 HorizontalLineTo {
22 abs: bool,
23 x: f64,
24 },
25 VerticalLineTo {
26 abs: bool,
27 y: f64,
28 },
29 CurveTo {
30 abs: bool,
31 x1: f64,
32 y1: f64,
33 x2: f64,
34 y2: f64,
35 x: f64,
36 y: f64,
37 },
38 SmoothCurveTo {
39 abs: bool,
40 x2: f64,
41 y2: f64,
42 x: f64,
43 y: f64,
44 },
45 Quadratic {
46 abs: bool,
47 x1: f64,
48 y1: f64,
49 x: f64,
50 y: f64,
51 },
52 SmoothQuadratic {
53 abs: bool,
54 x: f64,
55 y: f64,
56 },
57 EllipticalArc {
58 abs: bool,
59 rx: f64,
60 ry: f64,
61 x_axis_rotation: f64,
62 large_arc: bool,
63 sweep: bool,
64 x: f64,
65 y: f64,
66 },
67 ClosePath {
68 abs: bool,
69 },
70}
71
72/// A pull-based [path data] parser.
73///
74/// # Errors
75///
76/// - Most of the `Error` types can occur.
77///
78/// # Notes
79///
80/// The library does not support implicit commands, so they will be converted to an explicit one.
81/// It mostly affects an implicit MoveTo, which will be converted, according to the spec,
82/// into explicit LineTo.
83///
84/// Example: `M 10 20 30 40 50 60` -> `M 10 20 L 30 40 L 50 60`
85///
86/// # Examples
87///
88/// ```
89/// use svgtypes::{PathParser, PathSegment};
90///
91/// let mut segments = Vec::new();
92/// for segment in PathParser::from("M10-20l30.1.5.1-20z") {
93/// segments.push(segment.unwrap());
94/// }
95///
96/// assert_eq!(segments, &[
97/// PathSegment::MoveTo { abs: true, x: 10.0, y: -20.0 },
98/// PathSegment::LineTo { abs: false, x: 30.1, y: 0.5 },
99/// PathSegment::LineTo { abs: false, x: 0.1, y: -20.0 },
100/// PathSegment::ClosePath { abs: false },
101/// ]);
102/// ```
103///
104/// [path data]: https://www.w3.org/TR/SVG2/paths.html#PathData
105#[derive(Clone, Copy, PartialEq, Eq, Debug)]
106pub struct PathParser<'a> {
107 stream: Stream<'a>,
108 prev_cmd: Option<u8>,
109}
110
111impl<'a> From<&'a str> for PathParser<'a> {
112 #[inline]
113 fn from(v: &'a str) -> Self {
114 PathParser {
115 stream: Stream::from(v),
116 prev_cmd: None,
117 }
118 }
119}
120
121impl<'a> Iterator for PathParser<'a> {
122 type Item = Result<PathSegment, Error>;
123
124 #[inline]
125 fn next(&mut self) -> Option<Self::Item> {
126 let s: &mut Stream<'_> = &mut self.stream;
127
128 s.skip_spaces();
129
130 if s.at_end() {
131 return None;
132 }
133
134 let res: Result = next_impl(s, &mut self.prev_cmd);
135 if res.is_err() {
136 s.jump_to_end();
137 }
138
139 Some(res)
140 }
141}
142
143fn next_impl(s: &mut Stream, prev_cmd: &mut Option<u8>) -> Result<PathSegment, Error> {
144 let start = s.pos();
145
146 let has_prev_cmd = prev_cmd.is_some();
147 let first_char = s.curr_byte_unchecked();
148
149 if !has_prev_cmd && !is_cmd(first_char) {
150 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
151 }
152
153 if !has_prev_cmd && !matches!(first_char, b'M' | b'm') {
154 // The first segment must be a MoveTo.
155 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
156 }
157
158 // TODO: simplify
159 let is_implicit_move_to;
160 let cmd: u8;
161 if is_cmd(first_char) {
162 is_implicit_move_to = false;
163 cmd = first_char;
164 s.advance(1);
165 } else if is_number_start(first_char) && has_prev_cmd {
166 // unwrap is safe, because we checked 'has_prev_cmd'
167 let p_cmd = prev_cmd.unwrap();
168
169 if p_cmd == b'Z' || p_cmd == b'z' {
170 // ClosePath cannot be followed by a number.
171 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
172 }
173
174 if p_cmd == b'M' || p_cmd == b'm' {
175 // 'If a moveto is followed by multiple pairs of coordinates,
176 // the subsequent pairs are treated as implicit lineto commands.'
177 // So we parse them as LineTo.
178 is_implicit_move_to = true;
179 cmd = if is_absolute(p_cmd) { b'L' } else { b'l' };
180 } else {
181 is_implicit_move_to = false;
182 cmd = p_cmd;
183 }
184 } else {
185 return Err(Error::UnexpectedData(s.calc_char_pos_at(start)));
186 }
187
188 let cmdl = to_relative(cmd);
189 let absolute = is_absolute(cmd);
190 let token = match cmdl {
191 b'm' => PathSegment::MoveTo {
192 abs: absolute,
193 x: s.parse_list_number()?,
194 y: s.parse_list_number()?,
195 },
196 b'l' => PathSegment::LineTo {
197 abs: absolute,
198 x: s.parse_list_number()?,
199 y: s.parse_list_number()?,
200 },
201 b'h' => PathSegment::HorizontalLineTo {
202 abs: absolute,
203 x: s.parse_list_number()?,
204 },
205 b'v' => PathSegment::VerticalLineTo {
206 abs: absolute,
207 y: s.parse_list_number()?,
208 },
209 b'c' => PathSegment::CurveTo {
210 abs: absolute,
211 x1: s.parse_list_number()?,
212 y1: s.parse_list_number()?,
213 x2: s.parse_list_number()?,
214 y2: s.parse_list_number()?,
215 x: s.parse_list_number()?,
216 y: s.parse_list_number()?,
217 },
218 b's' => PathSegment::SmoothCurveTo {
219 abs: absolute,
220 x2: s.parse_list_number()?,
221 y2: s.parse_list_number()?,
222 x: s.parse_list_number()?,
223 y: s.parse_list_number()?,
224 },
225 b'q' => PathSegment::Quadratic {
226 abs: absolute,
227 x1: s.parse_list_number()?,
228 y1: s.parse_list_number()?,
229 x: s.parse_list_number()?,
230 y: s.parse_list_number()?,
231 },
232 b't' => PathSegment::SmoothQuadratic {
233 abs: absolute,
234 x: s.parse_list_number()?,
235 y: s.parse_list_number()?,
236 },
237 b'a' => {
238 // TODO: radius cannot be negative
239 PathSegment::EllipticalArc {
240 abs: absolute,
241 rx: s.parse_list_number()?,
242 ry: s.parse_list_number()?,
243 x_axis_rotation: s.parse_list_number()?,
244 large_arc: parse_flag(s)?,
245 sweep: parse_flag(s)?,
246 x: s.parse_list_number()?,
247 y: s.parse_list_number()?,
248 }
249 }
250 b'z' => PathSegment::ClosePath { abs: absolute },
251 _ => unreachable!(),
252 };
253
254 *prev_cmd = Some(if is_implicit_move_to {
255 if absolute {
256 b'M'
257 } else {
258 b'm'
259 }
260 } else {
261 cmd
262 });
263
264 Ok(token)
265}
266
267/// Returns `true` if the selected char is the command.
268#[rustfmt::skip]
269#[inline]
270fn is_cmd(c: u8) -> bool {
271 matches!(c,
272 b'M' | b'm'
273 | b'Z' | b'z'
274 | b'L' | b'l'
275 | b'H' | b'h'
276 | b'V' | b'v'
277 | b'C' | b'c'
278 | b'S' | b's'
279 | b'Q' | b'q'
280 | b'T' | b't'
281 | b'A' | b'a')
282}
283
284/// Returns `true` if the selected char is the absolute command.
285#[inline]
286fn is_absolute(c: u8) -> bool {
287 debug_assert!(is_cmd(c));
288 matches!(
289 c,
290 b'M' | b'Z' | b'L' | b'H' | b'V' | b'C' | b'S' | b'Q' | b'T' | b'A'
291 )
292}
293
294/// Converts the selected command char into the relative command char.
295#[inline]
296fn to_relative(c: u8) -> u8 {
297 debug_assert!(is_cmd(c));
298 match c {
299 b'M' => b'm',
300 b'Z' => b'z',
301 b'L' => b'l',
302 b'H' => b'h',
303 b'V' => b'v',
304 b'C' => b'c',
305 b'S' => b's',
306 b'Q' => b'q',
307 b'T' => b't',
308 b'A' => b'a',
309 _ => c,
310 }
311}
312
313#[inline]
314fn is_number_start(c: u8) -> bool {
315 matches!(c, b'0'..=b'9' | b'.' | b'-' | b'+')
316}
317
318// By the SVG spec 'large-arc' and 'sweep' must contain only one char
319// and can be written without any separators, e.g.: 10 20 30 01 10 20.
320fn parse_flag(s: &mut Stream) -> Result<bool, Error> {
321 s.skip_spaces();
322
323 let c: u8 = s.curr_byte()?;
324 match c {
325 b'0' | b'1' => {
326 s.advance(1);
327 if s.is_curr_byte_eq(b',') {
328 s.advance(1);
329 }
330 s.skip_spaces();
331
332 Ok(c == b'1')
333 }
334 _ => Err(Error::UnexpectedData(s.calc_char_pos_at(byte_pos:s.pos()))),
335 }
336}
337
338#[rustfmt::skip]
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 macro_rules! test {
344 ($name:ident, $text:expr, $( $seg:expr ),*) => (
345 #[test]
346 fn $name() {
347 let mut s = PathParser::from($text);
348 $(
349 assert_eq!(s.next().unwrap().unwrap(), $seg);
350 )*
351
352 if let Some(res) = s.next() {
353 assert!(res.is_err());
354 }
355 }
356 )
357 }
358
359 test!(null, "", );
360 test!(not_a_path, "q", );
361 test!(not_a_move_to, "L 20 30", );
362 test!(stop_on_err_1, "M 10 20 L 30 40 L 50",
363 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
364 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 }
365 );
366
367 test!(move_to_1, "M 10 20", PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 });
368 test!(move_to_2, "m 10 20", PathSegment::MoveTo { abs: false, x: 10.0, y: 20.0 });
369 test!(move_to_3, "M 10 20 30 40 50 60",
370 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
371 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
372 PathSegment::LineTo { abs: true, x: 50.0, y: 60.0 }
373 );
374 test!(move_to_4, "M 10 20 30 40 50 60 M 70 80 90 100 110 120",
375 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
376 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
377 PathSegment::LineTo { abs: true, x: 50.0, y: 60.0 },
378 PathSegment::MoveTo { abs: true, x: 70.0, y: 80.0 },
379 PathSegment::LineTo { abs: true, x: 90.0, y: 100.0 },
380 PathSegment::LineTo { abs: true, x: 110.0, y: 120.0 }
381 );
382
383 test!(arc_to_1, "M 10 20 A 5 5 30 1 1 20 20",
384 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
385 PathSegment::EllipticalArc {
386 abs: true,
387 rx: 5.0, ry: 5.0,
388 x_axis_rotation: 30.0,
389 large_arc: true, sweep: true,
390 x: 20.0, y: 20.0
391 }
392 );
393
394 test!(arc_to_2, "M 10 20 a 5 5 30 0 0 20 20",
395 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
396 PathSegment::EllipticalArc {
397 abs: false,
398 rx: 5.0, ry: 5.0,
399 x_axis_rotation: 30.0,
400 large_arc: false, sweep: false,
401 x: 20.0, y: 20.0
402 }
403 );
404
405 test!(arc_to_10, "M10-20A5.5.3-4 010-.1",
406 PathSegment::MoveTo { abs: true, x: 10.0, y: -20.0 },
407 PathSegment::EllipticalArc {
408 abs: true,
409 rx: 5.5, ry: 0.3,
410 x_axis_rotation: -4.0,
411 large_arc: false, sweep: true,
412 x: 0.0, y: -0.1
413 }
414 );
415
416 test!(separator_1, "M 10 20 L 5 15 C 10 20 30 40 50 60",
417 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
418 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
419 PathSegment::CurveTo {
420 abs: true,
421 x1: 10.0, y1: 20.0,
422 x2: 30.0, y2: 40.0,
423 x: 50.0, y: 60.0,
424 }
425 );
426
427 test!(separator_2, "M 10, 20 L 5, 15 C 10, 20 30, 40 50, 60",
428 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
429 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
430 PathSegment::CurveTo {
431 abs: true,
432 x1: 10.0, y1: 20.0,
433 x2: 30.0, y2: 40.0,
434 x: 50.0, y: 60.0,
435 }
436 );
437
438 test!(separator_3, "M 10,20 L 5,15 C 10,20 30,40 50,60",
439 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
440 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
441 PathSegment::CurveTo {
442 abs: true,
443 x1: 10.0, y1: 20.0,
444 x2: 30.0, y2: 40.0,
445 x: 50.0, y: 60.0,
446 }
447 );
448
449 test!(separator_4, "M10, 20 L5, 15 C10, 20 30 40 50 60",
450 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
451 PathSegment::LineTo { abs: true, x: 5.0, y: 15.0 },
452 PathSegment::CurveTo {
453 abs: true,
454 x1: 10.0, y1: 20.0,
455 x2: 30.0, y2: 40.0,
456 x: 50.0, y: 60.0,
457 }
458 );
459
460 test!(separator_5, "M10 20V30H40V50H60Z",
461 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
462 PathSegment::VerticalLineTo { abs: true, y: 30.0 },
463 PathSegment::HorizontalLineTo { abs: true, x: 40.0 },
464 PathSegment::VerticalLineTo { abs: true, y: 50.0 },
465 PathSegment::HorizontalLineTo { abs: true, x: 60.0 },
466 PathSegment::ClosePath { abs: true }
467 );
468
469 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
470 Q 170 180 190 200 T 210 220 A 50 50 30 1 1 230 240 Z",
471 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
472 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
473 PathSegment::HorizontalLineTo { abs: true, x: 50.0 },
474 PathSegment::VerticalLineTo { abs: true, y: 60.0 },
475 PathSegment::CurveTo {
476 abs: true,
477 x1: 70.0, y1: 80.0,
478 x2: 90.0, y2: 100.0,
479 x: 110.0, y: 120.0,
480 },
481 PathSegment::SmoothCurveTo {
482 abs: true,
483 x2: 130.0, y2: 140.0,
484 x: 150.0, y: 160.0,
485 },
486 PathSegment::Quadratic {
487 abs: true,
488 x1: 170.0, y1: 180.0,
489 x: 190.0, y: 200.0,
490 },
491 PathSegment::SmoothQuadratic { abs: true, x: 210.0, y: 220.0 },
492 PathSegment::EllipticalArc {
493 abs: true,
494 rx: 50.0, ry: 50.0,
495 x_axis_rotation: 30.0,
496 large_arc: true, sweep: true,
497 x: 230.0, y: 240.0
498 },
499 PathSegment::ClosePath { abs: true }
500 );
501
502 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
503 q 170 180 190 200 t 210 220 a 50 50 30 1 1 230 240 z",
504 PathSegment::MoveTo { abs: false, x: 10.0, y: 20.0 },
505 PathSegment::LineTo { abs: false, x: 30.0, y: 40.0 },
506 PathSegment::HorizontalLineTo { abs: false, x: 50.0 },
507 PathSegment::VerticalLineTo { abs: false, y: 60.0 },
508 PathSegment::CurveTo {
509 abs: false,
510 x1: 70.0, y1: 80.0,
511 x2: 90.0, y2: 100.0,
512 x: 110.0, y: 120.0,
513 },
514 PathSegment::SmoothCurveTo {
515 abs: false,
516 x2: 130.0, y2: 140.0,
517 x: 150.0, y: 160.0,
518 },
519 PathSegment::Quadratic {
520 abs: false,
521 x1: 170.0, y1: 180.0,
522 x: 190.0, y: 200.0,
523 },
524 PathSegment::SmoothQuadratic { abs: false, x: 210.0, y: 220.0 },
525 PathSegment::EllipticalArc {
526 abs: false,
527 rx: 50.0, ry: 50.0,
528 x_axis_rotation: 30.0,
529 large_arc: true, sweep: true,
530 x: 230.0, y: 240.0
531 },
532 PathSegment::ClosePath { abs: false }
533 );
534
535 test!(close_path_1, "M10 20 L 30 40 ZM 100 200 L 300 400",
536 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
537 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
538 PathSegment::ClosePath { abs: true },
539 PathSegment::MoveTo { abs: true, x: 100.0, y: 200.0 },
540 PathSegment::LineTo { abs: true, x: 300.0, y: 400.0 }
541 );
542
543 test!(close_path_2, "M10 20 L 30 40 zM 100 200 L 300 400",
544 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
545 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
546 PathSegment::ClosePath { abs: false },
547 PathSegment::MoveTo { abs: true, x: 100.0, y: 200.0 },
548 PathSegment::LineTo { abs: true, x: 300.0, y: 400.0 }
549 );
550
551 test!(close_path_3, "M10 20 L 30 40 Z Z Z",
552 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 },
553 PathSegment::LineTo { abs: true, x: 30.0, y: 40.0 },
554 PathSegment::ClosePath { abs: true },
555 PathSegment::ClosePath { abs: true },
556 PathSegment::ClosePath { abs: true }
557 );
558
559 // first token should be EndOfStream
560 test!(invalid_1, "M\t.", );
561
562 // ClosePath can't be followed by a number
563 test!(invalid_2, "M 0 0 Z 2",
564 PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 },
565 PathSegment::ClosePath { abs: true }
566 );
567
568 // ClosePath can be followed by any command
569 test!(invalid_3, "M 0 0 Z H 10",
570 PathSegment::MoveTo { abs: true, x: 0.0, y: 0.0 },
571 PathSegment::ClosePath { abs: true },
572 PathSegment::HorizontalLineTo { abs: true, x: 10.0 }
573 );
574}
575
576/// Representation of a simple path segment.
577#[allow(missing_docs)]
578#[derive(Clone, Copy, PartialEq, Debug)]
579pub enum SimplePathSegment {
580 MoveTo {
581 x: f64,
582 y: f64,
583 },
584 LineTo {
585 x: f64,
586 y: f64,
587 },
588 CurveTo {
589 x1: f64,
590 y1: f64,
591 x2: f64,
592 y2: f64,
593 x: f64,
594 y: f64,
595 },
596 Quadratic {
597 x1: f64,
598 y1: f64,
599 x: f64,
600 y: f64,
601 },
602 ClosePath,
603}
604
605/// A simplifying Path Data parser.
606///
607/// A more high-level Path Data parser on top of [`PathParser`] that provides:
608///
609/// - Relative to absolute segment coordinates conversion
610/// - ArcTo to CurveTos conversion
611/// - SmoothCurveTo and SmoothQuadratic conversion
612/// - HorizontalLineTo and VerticalLineTo to LineTo conversion
613/// - Implicit MoveTo after ClosePath handling
614///
615/// In the end, only absolute MoveTo, LineTo, CurveTo, Quadratic and ClosePath segments
616/// will be produced.
617#[derive(Clone, Debug)]
618pub struct SimplifyingPathParser<'a> {
619 parser: PathParser<'a>,
620
621 // Previous MoveTo coordinates.
622 prev_mx: f64,
623 prev_my: f64,
624
625 // Previous coordinates.
626 prev_x: f64,
627 prev_y: f64,
628
629 prev_segment: SimplePathSegment,
630
631 buffer: Vec<SimplePathSegment>,
632}
633
634impl<'a> From<&'a str> for SimplifyingPathParser<'a> {
635 #[inline]
636 fn from(v: &'a str) -> Self {
637 SimplifyingPathParser {
638 parser: PathParser::from(v),
639 prev_mx: 0.0,
640 prev_my: 0.0,
641 prev_x: 0.0,
642 prev_y: 0.0,
643 prev_segment: SimplePathSegment::MoveTo { x: 0.0, y: 0.0 },
644 buffer: Vec::new(),
645 }
646 }
647}
648
649impl<'a> Iterator for SimplifyingPathParser<'a> {
650 type Item = Result<SimplePathSegment, Error>;
651
652 fn next(&mut self) -> Option<Self::Item> {
653 if !self.buffer.is_empty() {
654 return Some(Ok(self.buffer.remove(0)));
655 }
656
657 let segment = match self.parser.next()? {
658 Ok(v) => v,
659 Err(e) => return Some(Err(e)),
660 };
661
662 // If a ClosePath segment is followed by any command other than MoveTo or ClosePath
663 // then MoveTo is implicit.
664 if let SimplePathSegment::ClosePath = self.prev_segment {
665 match segment {
666 PathSegment::MoveTo { .. } | PathSegment::ClosePath { .. } => {}
667 _ => {
668 let new_seg = SimplePathSegment::MoveTo {
669 x: self.prev_mx,
670 y: self.prev_my,
671 };
672 self.buffer.push(new_seg);
673 self.prev_segment = new_seg;
674 }
675 }
676 }
677
678 match segment {
679 PathSegment::MoveTo { abs, mut x, mut y } => {
680 if !abs {
681 // When we get 'm'(relative) segment, which is not first segment - then it's
682 // relative to a previous 'M'(absolute) segment, not to the first segment.
683 if let SimplePathSegment::ClosePath = self.prev_segment {
684 x += self.prev_mx;
685 y += self.prev_my;
686 } else {
687 x += self.prev_x;
688 y += self.prev_y;
689 }
690 }
691
692 self.buffer.push(SimplePathSegment::MoveTo { x, y });
693 }
694 PathSegment::LineTo { abs, mut x, mut y } => {
695 if !abs {
696 x += self.prev_x;
697 y += self.prev_y;
698 }
699
700 self.buffer.push(SimplePathSegment::LineTo { x, y });
701 }
702 PathSegment::HorizontalLineTo { abs, mut x } => {
703 if !abs {
704 x += self.prev_x;
705 }
706
707 self.buffer
708 .push(SimplePathSegment::LineTo { x, y: self.prev_y });
709 }
710 PathSegment::VerticalLineTo { abs, mut y } => {
711 if !abs {
712 y += self.prev_y;
713 }
714
715 self.buffer
716 .push(SimplePathSegment::LineTo { x: self.prev_x, y });
717 }
718 PathSegment::CurveTo {
719 abs,
720 mut x1,
721 mut y1,
722 mut x2,
723 mut y2,
724 mut x,
725 mut y,
726 } => {
727 if !abs {
728 x1 += self.prev_x;
729 y1 += self.prev_y;
730 x2 += self.prev_x;
731 y2 += self.prev_y;
732 x += self.prev_x;
733 y += self.prev_y;
734 }
735
736 self.buffer.push(SimplePathSegment::CurveTo {
737 x1,
738 y1,
739 x2,
740 y2,
741 x,
742 y,
743 });
744 }
745 PathSegment::SmoothCurveTo {
746 abs,
747 mut x2,
748 mut y2,
749 mut x,
750 mut y,
751 } => {
752 // 'The first control point is assumed to be the reflection of the second control
753 // point on the previous command relative to the current point.
754 // (If there is no previous command or if the previous command
755 // was not an C, c, S or s, assume the first control point is
756 // coincident with the current point.)'
757 let (x1, y1) = match self.prev_segment {
758 SimplePathSegment::CurveTo { x2, y2, x, y, .. } => (x * 2.0 - x2, y * 2.0 - y2),
759 _ => (self.prev_x, self.prev_y),
760 };
761
762 if !abs {
763 x2 += self.prev_x;
764 y2 += self.prev_y;
765 x += self.prev_x;
766 y += self.prev_y;
767 }
768
769 self.buffer.push(SimplePathSegment::CurveTo {
770 x1,
771 y1,
772 x2,
773 y2,
774 x,
775 y,
776 });
777 }
778 PathSegment::Quadratic {
779 abs,
780 mut x1,
781 mut y1,
782 mut x,
783 mut y,
784 } => {
785 if !abs {
786 x1 += self.prev_x;
787 y1 += self.prev_y;
788 x += self.prev_x;
789 y += self.prev_y;
790 }
791
792 self.buffer
793 .push(SimplePathSegment::Quadratic { x1, y1, x, y });
794 }
795 PathSegment::SmoothQuadratic { abs, mut x, mut y } => {
796 // 'The control point is assumed to be the reflection of
797 // the control point on the previous command relative to
798 // the current point. (If there is no previous command or
799 // if the previous command was not a Q, q, T or t, assume
800 // the control point is coincident with the current point.)'
801 let (x1, y1) = match self.prev_segment {
802 SimplePathSegment::Quadratic { x1, y1, x, y, .. } => {
803 (x * 2.0 - x1, y * 2.0 - y1)
804 }
805 _ => (self.prev_x, self.prev_y),
806 };
807
808 if !abs {
809 x += self.prev_x;
810 y += self.prev_y;
811 }
812
813 self.buffer
814 .push(SimplePathSegment::Quadratic { x1, y1, x, y });
815 }
816 PathSegment::EllipticalArc {
817 abs,
818 rx,
819 ry,
820 x_axis_rotation,
821 large_arc,
822 sweep,
823 mut x,
824 mut y,
825 } => {
826 if !abs {
827 x += self.prev_x;
828 y += self.prev_y;
829 }
830
831 let svg_arc = kurbo::SvgArc {
832 from: kurbo::Point::new(self.prev_x, self.prev_y),
833 to: kurbo::Point::new(x, y),
834 radii: kurbo::Vec2::new(rx, ry),
835 x_rotation: x_axis_rotation.to_radians(),
836 large_arc,
837 sweep,
838 };
839
840 match kurbo::Arc::from_svg_arc(&svg_arc) {
841 Some(arc) => {
842 arc.to_cubic_beziers(0.1, |p1, p2, p| {
843 self.buffer.push(SimplePathSegment::CurveTo {
844 x1: p1.x,
845 y1: p1.y,
846 x2: p2.x,
847 y2: p2.y,
848 x: p.x,
849 y: p.y,
850 });
851 });
852 }
853 None => {
854 self.buffer.push(SimplePathSegment::LineTo { x, y });
855 }
856 }
857 }
858 PathSegment::ClosePath { .. } => {
859 if let SimplePathSegment::ClosePath = self.prev_segment {
860 // Do not add sequential ClosePath segments.
861 // Otherwise it will break markers rendering.
862 } else {
863 self.buffer.push(SimplePathSegment::ClosePath);
864 }
865 }
866 }
867
868 // Remember last position.
869 if let Some(new_segment) = self.buffer.last() {
870 self.prev_segment = *new_segment;
871
872 match *new_segment {
873 SimplePathSegment::MoveTo { x, y } => {
874 self.prev_x = x;
875 self.prev_y = y;
876 self.prev_mx = self.prev_x;
877 self.prev_my = self.prev_y;
878 }
879 SimplePathSegment::LineTo { x, y } => {
880 self.prev_x = x;
881 self.prev_y = y;
882 }
883 SimplePathSegment::CurveTo { x, y, .. } => {
884 self.prev_x = x;
885 self.prev_y = y;
886 }
887 SimplePathSegment::Quadratic { x, y, .. } => {
888 self.prev_x = x;
889 self.prev_y = y;
890 }
891 SimplePathSegment::ClosePath => {
892 // ClosePath moves us to the last MoveTo coordinate.
893 self.prev_x = self.prev_mx;
894 self.prev_y = self.prev_my;
895 }
896 }
897 }
898
899 if self.buffer.is_empty() {
900 return self.next();
901 }
902
903 Some(Ok(self.buffer.remove(0)))
904 }
905}
906
907#[rustfmt::skip]
908#[cfg(test)]
909mod simple_tests {
910 use super::*;
911
912 macro_rules! test {
913 ($name:ident, $text:expr, $( $seg:expr ),*) => (
914 #[test]
915 fn $name() {
916 let mut s = SimplifyingPathParser::from($text);
917 $(
918 assert_eq!(s.next().unwrap().unwrap(), $seg);
919 )*
920
921 if let Some(res) = s.next() {
922 assert!(res.is_err());
923 }
924 }
925 )
926 }
927
928 test!(ignore_duplicated_close_paths, "M 10 20 L 30 40 Z Z Z Z",
929 SimplePathSegment::MoveTo { x: 10.0, y: 20.0 },
930 SimplePathSegment::LineTo { x: 30.0, y: 40.0 },
931 SimplePathSegment::ClosePath
932 );
933
934 test!(relative_move_to, "m 30 40 110 120 -20 -130",
935 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
936 SimplePathSegment::LineTo { x: 140.0, y: 160.0 },
937 SimplePathSegment::LineTo { x: 120.0, y: 30.0 }
938 );
939
940 test!(smooth_curve_to_after_move_to, "M 30 40 S 171 45 180 155",
941 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
942 SimplePathSegment::CurveTo { x1: 30.0, y1: 40.0, x2: 171.0, y2: 45.0, x: 180.0, y: 155.0 }
943 );
944
945 test!(smooth_curve_to_after_curve_to, "M 30 40 C 16 137 171 45 100 90 S 171 45 180 155",
946 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
947 SimplePathSegment::CurveTo { x1: 16.0, y1: 137.0, x2: 171.0, y2: 45.0, x: 100.0, y: 90.0 },
948 SimplePathSegment::CurveTo { x1: 29.0, y1: 135.0, x2: 171.0, y2: 45.0, x: 180.0, y: 155.0 }
949 );
950
951 test!(smooth_quadratic_after_move_to, "M 30 40 T 180 155",
952 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
953 SimplePathSegment::Quadratic { x1: 30.0, y1: 40.0, x: 180.0, y: 155.0 }
954 );
955
956 test!(smooth_quadratic_after_quadratic, "M 30 40 Q 171 45 100 90 T 160 180",
957 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
958 SimplePathSegment::Quadratic { x1: 171.0, y1: 45.0, x: 100.0, y: 90.0 },
959 SimplePathSegment::Quadratic { x1: 29.0, y1: 135.0, x: 160.0, y: 180.0 }
960 );
961
962 test!(relative_smooth_quadratic_after_quadratic, "M 30 40 Q 171 45 100 90 t 60 80",
963 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
964 SimplePathSegment::Quadratic { x1: 171.0, y1: 45.0, x: 100.0, y: 90.0 },
965 SimplePathSegment::Quadratic { x1: 29.0, y1: 135.0, x: 160.0, y: 170.0 }
966 );
967
968 test!(relative_smooth_quadratic_after_relative_quadratic, "M 30 40 q 171 45 50 40 t 60 80",
969 SimplePathSegment::MoveTo { x: 30.0, y: 40.0 },
970 SimplePathSegment::Quadratic { x1: 201.0, y1: 85.0, x: 80.0, y: 80.0 },
971 SimplePathSegment::Quadratic { x1: -41.0, y1: 75.0, x: 140.0, y: 160.0 }
972 );
973
974 test!(smooth_quadratic_after_smooth_quadratic, "M 30 30 T 40 140 T 170 30",
975 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
976 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
977 SimplePathSegment::Quadratic { x1: 50.0, y1: 250.0, x: 170.0, y: 30.0 }
978 );
979
980 test!(smooth_quadratic_after_relative_smooth_quadratic, "M 30 30 T 40 140 t 100 -30",
981 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
982 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
983 SimplePathSegment::Quadratic { x1: 50.0, y1: 250.0, x: 140.0, y: 110.0 }
984 );
985
986 test!(smooth_quadratic_after_relative_quadratic, "M 30 30 T 40 140 q 30 100 120 -30",
987 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
988 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
989 SimplePathSegment::Quadratic { x1: 70.0, y1: 240.0, x: 160.0, y: 110.0 }
990 );
991
992 test!(smooth_quadratic_after_relative_smooth_curve_to, "M 30 30 T 40 170 s 90 -20 90 -90",
993 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
994 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 170.0 },
995 SimplePathSegment::CurveTo { x1: 40.0, y1: 170.0, x2: 130.0, y2: 150.0, x: 130.0, y: 80.0 }
996 );
997
998 test!(quadratic_after_smooth_quadratic, "M 30 30 T 40 140 Q 80 180 170 30",
999 SimplePathSegment::MoveTo { x: 30.0, y: 30.0 },
1000 SimplePathSegment::Quadratic { x1: 30.0, y1: 30.0, x: 40.0, y: 140.0 },
1001 SimplePathSegment::Quadratic { x1: 80.0, y1: 180.0, x: 170.0, y: 30.0 }
1002 );
1003
1004 test!(implicit_move_to_after_close_path, "M 10 20 L 30 40 Z L 50 60",
1005 SimplePathSegment::MoveTo { x: 10.0, y: 20.0 },
1006 SimplePathSegment::LineTo { x: 30.0, y: 40.0 },
1007 SimplePathSegment::ClosePath,
1008 SimplePathSegment::MoveTo { x: 10.0, y: 20.0 },
1009 SimplePathSegment::LineTo { x: 50.0, y: 60.0 }
1010 );
1011
1012 #[test]
1013 fn arc_to() {
1014 let mut s = SimplifyingPathParser::from("M 30 40 A 40 30 20 1 1 150 100");
1015 assert_eq!(s.next().unwrap().unwrap(), SimplePathSegment::MoveTo { x: 30.0, y: 40.0 });
1016 let curve1 = s.next().unwrap().unwrap();
1017 let curve2 = s.next().unwrap().unwrap();
1018 if let Some(res) = s.next() {
1019 assert!(res.is_err());
1020 }
1021
1022 if let SimplePathSegment::CurveTo { x1, y1, x2, y2, x, y } = curve1 {
1023 assert_eq!(x1.round(), 45.0);
1024 assert_eq!(y1.round(), 16.0);
1025 assert_eq!(x2.round(), 84.0);
1026 assert_eq!(y2.round(), 10.0);
1027 assert_eq!(x.round(), 117.0);
1028 assert_eq!(y.round(), 27.0);
1029 } else {
1030 panic!("invalid type");
1031 }
1032
1033 if let SimplePathSegment::CurveTo { x1, y1, x2, y2, x, y } = curve2 {
1034 assert_eq!(x1.round(), 150.0);
1035 assert_eq!(y1.round(), 43.0);
1036 assert_eq!(x2.round(), 165.0);
1037 assert_eq!(y2.round(), 76.0);
1038 assert_eq!(x.round(), 150.0);
1039 assert_eq!(y.round(), 100.0);
1040 } else {
1041 panic!("invalid type");
1042 }
1043 }
1044}
1045