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