zeno/
svg_parser.rs

1//! SVG path data parser.
2
3use super::command::Command;
4use super::geometry::Vector;
5use super::path_builder::{Arc, ArcSize, ArcSweep};
6
7#[derive(Copy, Clone)]
8enum State {
9    Initial,
10    Next,
11    Continue(u8),
12}
13
14#[derive(Clone)]
15pub struct SvgCommands<'a> {
16    buf: &'a [u8],
17    cur: u8,
18    pub pos: usize,
19    cmd_pos: usize,
20    pub error: bool,
21    pub done: bool,
22    start_point: Vector,
23    cur_point: Vector,
24    last_control: Vector,
25    last_cmd: u8,
26    state: State,
27    arc: Arc,
28}
29
30impl Iterator for SvgCommands<'_> {
31    type Item = Command;
32
33    fn next(&mut self) -> Option<Self::Item> {
34        self.parse()
35    }
36}
37
38impl<'a> SvgCommands<'a> {
39    pub(crate) fn new(source: &'a str) -> Self {
40        Self {
41            buf: source.as_bytes(),
42            cur: 0,
43            pos: 0,
44            cmd_pos: 0,
45            error: false,
46            done: false,
47            start_point: Vector::ZERO,
48            cur_point: Vector::ZERO,
49            last_control: Vector::ZERO,
50            last_cmd: 0,
51            state: State::Initial,
52            arc: Arc::default(),
53        }
54    }
55
56    fn parse(&mut self) -> Option<Command> {
57        use Command::*;
58        let mut cmd = self.cur;
59        loop {
60            if let Some(cmd) = self.arc.next() {
61                return Some(cmd);
62            }
63            self.last_cmd = cmd;
64            match self.state {
65                State::Initial => {
66                    self.advance();
67                    self.skip_whitespace();
68                    self.state = State::Next;
69                    continue;
70                }
71                State::Next => {
72                    self.skip_whitespace();
73                    self.cmd_pos = self.pos;
74                    cmd = self.cur;
75                    self.advance();
76                    self.skip_whitespace();
77                    self.state = State::Continue(cmd);
78                    match cmd {
79                        b'z' | b'Z' => {
80                            self.state = State::Next;
81                            self.cur_point = self.start_point;
82                            return Some(Close);
83                        }
84                        b'M' => {
85                            let to = self.point_to()?;
86                            self.start_point = to;
87                            self.skip_comma_whitespace();
88                            return Some(MoveTo(to));
89                        }
90                        b'm' => {
91                            let to = self.rel_point_to()?;
92                            self.start_point = to;
93                            self.skip_comma_whitespace();
94                            return Some(MoveTo(to));
95                        }
96                        b'L' => {
97                            let to = self.point_to()?;
98                            self.skip_comma_whitespace();
99                            return Some(LineTo(to));
100                        }
101                        b'l' => {
102                            let to = self.rel_point_to()?;
103                            self.skip_comma_whitespace();
104                            return Some(LineTo(to));
105                        }
106                        b'H' => {
107                            let x = self.coord()?;
108                            let to = Vector::new(x, self.cur_point.y);
109                            self.cur_point = to;
110                            self.skip_comma_whitespace();
111                            return Some(LineTo(to));
112                        }
113                        b'h' => {
114                            let x = self.coord()?;
115                            let to = Vector::new(self.cur_point.x + x, self.cur_point.y);
116                            self.cur_point = to;
117                            self.skip_comma_whitespace();
118                            return Some(LineTo(to));
119                        }
120                        b'V' => {
121                            let y = self.coord()?;
122                            let to = Vector::new(self.cur_point.x, y);
123                            self.cur_point = to;
124                            self.skip_comma_whitespace();
125                            return Some(LineTo(to));
126                        }
127                        b'v' => {
128                            let y = self.coord()?;
129                            let to = Vector::new(self.cur_point.x, self.cur_point.y + y);
130                            self.cur_point = to;
131                            self.skip_comma_whitespace();
132                            return Some(LineTo(to));
133                        }
134                        b'C' => {
135                            let (c1, c2, to) = self.three_points_to()?;
136                            self.last_control = c2;
137                            self.skip_comma_whitespace();
138                            return Some(CurveTo(c1, c2, to));
139                        }
140                        b'c' => {
141                            let (c1, c2, to) = self.rel_three_points_to()?;
142                            self.last_control = c2;
143                            self.skip_comma_whitespace();
144                            return Some(CurveTo(c1, c2, to));
145                        }
146                        b'S' => {
147                            let (c2, to) = self.two_points()?;
148                            let c1 = self.reflected_control(cmd);
149                            self.cur_point = to;
150                            self.last_control = c2;
151                            self.skip_comma_whitespace();
152                            return Some(CurveTo(c1, c2, to));
153                        }
154                        b's' => {
155                            let (c2, to) = self.rel_two_points()?;
156                            let c1 = self.reflected_control(cmd);
157                            self.cur_point = to;
158                            self.last_control = c2;
159                            self.skip_comma_whitespace();
160                            return Some(CurveTo(c1, c2, to));
161                        }
162                        b'Q' => {
163                            let (c, to) = self.two_points_to()?;
164                            self.last_control = c;
165                            self.skip_comma_whitespace();
166                            return Some(QuadTo(c, to));
167                        }
168                        b'q' => {
169                            let (c, to) = self.rel_two_points_to()?;
170                            self.last_control = c;
171                            self.skip_comma_whitespace();
172                            return Some(QuadTo(c, to));
173                        }
174                        b'T' => {
175                            let to = self.point()?;
176                            let c = self.reflected_control(cmd);
177                            self.cur_point = to;
178                            self.last_control = c;
179                            self.skip_comma_whitespace();
180                            return Some(QuadTo(c, to));
181                        }
182                        b't' => {
183                            let to = self.rel_point()?;
184                            let c = self.reflected_control(cmd);
185                            self.cur_point = to;
186                            self.last_control = c;
187                            self.skip_comma_whitespace();
188                            return Some(QuadTo(c, to));
189                        }
190                        b'A' => {
191                            let from = self.cur_point;
192                            let (rx, ry, a, size, sweep, to) = self.arc_arguments(false)?;
193                            self.arc = Arc::new(from, rx, ry, a.to_radians(), size, sweep, to);
194                            self.skip_comma_whitespace();
195                            continue;
196                        }
197                        b'a' => {
198                            let from = self.cur_point;
199                            let (rx, ry, a, size, sweep, to) = self.arc_arguments(true)?;
200                            self.arc = Arc::new(from, rx, ry, a.to_radians(), size, sweep, to);
201                            self.skip_comma_whitespace();
202                            continue;
203                        }
204                        _ => {
205                            if !self.done || cmd != 0 {
206                                self.error = true;
207                                self.pos = self.cmd_pos;
208                            }
209                            return None;
210                        }
211                    }
212                }
213                State::Continue(cmd) => match cmd {
214                    b'M' => {
215                        if let Some(to) = self.point_to() {
216                            self.skip_comma_whitespace();
217                            return Some(LineTo(to));
218                        } else {
219                            self.state = State::Next;
220                        }
221                    }
222                    b'm' => {
223                        if let Some(to) = self.rel_point_to() {
224                            self.skip_comma_whitespace();
225                            return Some(LineTo(to));
226                        } else {
227                            self.state = State::Next;
228                        }
229                    }
230                    b'L' => {
231                        if let Some(to) = self.point_to() {
232                            self.skip_comma_whitespace();
233                            return Some(LineTo(to));
234                        } else {
235                            self.state = State::Next;
236                        }
237                    }
238                    b'l' => {
239                        if let Some(to) = self.rel_point_to() {
240                            self.skip_comma_whitespace();
241                            return Some(LineTo(to));
242                        } else {
243                            self.state = State::Next;
244                        }
245                    }
246                    b'H' => {
247                        if let Some(x) = self.coord() {
248                            let to = Vector::new(x, self.cur_point.y);
249                            self.cur_point = to;
250                            self.skip_comma_whitespace();
251                            return Some(LineTo(to));
252                        } else {
253                            self.state = State::Next;
254                        }
255                    }
256                    b'h' => {
257                        if let Some(x) = self.coord() {
258                            let to = Vector::new(self.cur_point.x + x, self.cur_point.y);
259                            self.cur_point = to;
260                            self.skip_comma_whitespace();
261                            return Some(LineTo(to));
262                        } else {
263                            self.state = State::Next;
264                        }
265                    }
266                    b'V' => {
267                        if let Some(y) = self.coord() {
268                            let to = Vector::new(self.cur_point.x, y);
269                            self.cur_point = to;
270                            self.skip_comma_whitespace();
271                            return Some(LineTo(to));
272                        } else {
273                            self.state = State::Next;
274                        }
275                    }
276                    b'v' => {
277                        if let Some(y) = self.coord() {
278                            let to = Vector::new(self.cur_point.x, self.cur_point.y + y);
279                            self.cur_point = to;
280                            self.skip_comma_whitespace();
281                            return Some(LineTo(to));
282                        } else {
283                            self.state = State::Next;
284                        }
285                    }
286                    b'C' => {
287                        if let Some(c1) = self.point() {
288                            self.skip_comma_whitespace();
289                            let (c2, to) = self.two_points_to()?;
290                            self.last_control = c2;
291                            self.skip_comma_whitespace();
292                            return Some(CurveTo(c1, c2, to));
293                        } else {
294                            self.state = State::Next;
295                        }
296                    }
297                    b'c' => {
298                        if let Some(c1) = self.rel_point() {
299                            self.skip_comma_whitespace();
300                            let (c2, to) = self.rel_two_points_to()?;
301                            self.last_control = c2;
302                            self.skip_comma_whitespace();
303                            return Some(CurveTo(c1, c2, to));
304                        } else {
305                            self.state = State::Next;
306                        }
307                    }
308                    b'S' => {
309                        if let Some(c2) = self.point() {
310                            self.skip_comma_whitespace();
311                            let to = self.point()?;
312                            let c1 = self.reflected_control(cmd);
313                            self.cur_point = to;
314                            self.last_control = c2;
315                            self.skip_comma_whitespace();
316                            return Some(CurveTo(c1, c2, to));
317                        } else {
318                            self.state = State::Next;
319                        }
320                    }
321                    b's' => {
322                        if let Some(c2) = self.rel_point() {
323                            self.skip_comma_whitespace();
324                            let to = self.rel_point()?;
325                            let c1 = self.reflected_control(cmd);
326                            self.cur_point = to;
327                            self.last_control = c2;
328                            self.skip_comma_whitespace();
329                            return Some(CurveTo(c1, c2, to));
330                        } else {
331                            self.state = State::Next;
332                        }
333                    }
334                    b'Q' => {
335                        if let Some(c) = self.point() {
336                            self.last_control = c;
337                            self.skip_comma_whitespace();
338                            let to = self.point_to()?;
339                            self.skip_comma_whitespace();
340                            return Some(QuadTo(c, to));
341                        } else {
342                            self.state = State::Next;
343                        }
344                    }
345                    b'q' => {
346                        if let Some(c) = self.rel_point() {
347                            self.last_control = c;
348                            self.skip_comma_whitespace();
349                            let to = self.rel_point_to()?;
350                            self.skip_comma_whitespace();
351                            return Some(QuadTo(c, to));
352                        } else {
353                            self.state = State::Next;
354                        }
355                    }
356                    b'T' => {
357                        if let Some(to) = self.point() {
358                            let c = self.reflected_control(cmd);
359                            self.cur_point = to;
360                            self.last_control = c;
361                            self.skip_comma_whitespace();
362                            return Some(QuadTo(c, to));
363                        } else {
364                            self.state = State::Next;
365                        }
366                    }
367                    b't' => {
368                        if let Some(to) = self.rel_point() {
369                            let c = self.reflected_control(cmd);
370                            self.cur_point = to;
371                            self.last_control = c;
372                            self.skip_comma_whitespace();
373                            return Some(QuadTo(c, to));
374                        } else {
375                            self.state = State::Next;
376                        }
377                    }
378                    b'A' => {
379                        if let Some(rx) = self.coord() {
380                            let from = self.cur_point;
381                            let (ry, a, size, sweep, to) = self.arc_rest_arguments(false)?;
382                            self.arc = Arc::new(from, rx, ry, a.to_radians(), size, sweep, to);
383                            self.skip_comma_whitespace();
384                        } else {
385                            self.state = State::Next;
386                        }
387                    }
388                    b'a' => {
389                        if let Some(rx) = self.coord() {
390                            let from = self.cur_point;
391                            let (ry, a, size, sweep, to) = self.arc_rest_arguments(true)?;
392                            self.arc = Arc::new(from, rx, ry, a.to_radians(), size, sweep, to);
393                            self.skip_comma_whitespace();
394                        } else {
395                            self.state = State::Next;
396                        }
397                    }
398                    _ => {
399                        if !self.done || cmd != 0 {
400                            self.error = true;
401                            self.pos = self.cmd_pos;
402                        }
403                        return None;
404                    }
405                },
406            }
407        }
408    }
409
410    fn reflected_control(&self, cmd: u8) -> Vector {
411        let cur = self.cur_point;
412        let old = self.last_control;
413        if cmd == b'S' || cmd == b's' {
414            match self.last_cmd {
415                b'C' | b'c' | b'S' | b's' => (2. * cur.x - old.x, 2. * cur.y - old.y).into(),
416                _ => self.cur_point,
417            }
418        } else {
419            match self.last_cmd {
420                b'Q' | b'q' | b'T' | b't' => (2. * cur.x - old.x, 2. * cur.y - old.y).into(),
421                _ => self.cur_point,
422            }
423        }
424    }
425
426    fn arc_arguments(&mut self, rel: bool) -> Option<(f32, f32, f32, ArcSize, ArcSweep, Vector)> {
427        let rx = self.coord()?;
428        self.skip_comma_whitespace();
429        let ry = self.coord()?;
430        self.skip_comma_whitespace();
431        let a = self.coord()?;
432        self.skip_comma_whitespace();
433        let large_arc = self.boolean()?;
434        self.skip_comma_whitespace();
435        let sweep = self.boolean()?;
436        self.skip_comma_whitespace();
437        let to = if rel {
438            self.rel_point_to()?
439        } else {
440            self.point_to()?
441        };
442        let size = if large_arc {
443            ArcSize::Large
444        } else {
445            ArcSize::Small
446        };
447        let sweep = if sweep {
448            ArcSweep::Positive
449        } else {
450            ArcSweep::Negative
451        };
452        Some((rx, ry, a, size, sweep, to))
453    }
454
455    fn arc_rest_arguments(&mut self, rel: bool) -> Option<(f32, f32, ArcSize, ArcSweep, Vector)> {
456        let ry = self.coord()?;
457        self.skip_comma_whitespace();
458        let a = self.coord()?;
459        self.skip_comma_whitespace();
460        let large_arc = self.boolean()?;
461        self.skip_comma_whitespace();
462        let sweep = self.boolean()?;
463        self.skip_comma_whitespace();
464        let to = if rel {
465            self.rel_point_to()?
466        } else {
467            self.point_to()?
468        };
469        let size = if large_arc {
470            ArcSize::Large
471        } else {
472            ArcSize::Small
473        };
474        let sweep = if sweep {
475            ArcSweep::Positive
476        } else {
477            ArcSweep::Negative
478        };
479        Some((ry, a, size, sweep, to))
480    }
481
482    fn point(&mut self) -> Option<Vector> {
483        let a = self.coord()?;
484        self.skip_comma_whitespace();
485        let b = self.coord()?;
486        Some((a, b).into())
487    }
488
489    fn point_to(&mut self) -> Option<Vector> {
490        let p = self.point()?;
491        self.cur_point = p;
492        Some(p)
493    }
494
495    fn rel_point(&mut self) -> Option<Vector> {
496        let p = self.point()?;
497        Some(p + self.cur_point)
498    }
499
500    fn rel_point_to(&mut self) -> Option<Vector> {
501        let p = self.rel_point()?;
502        self.cur_point = p;
503        Some(p)
504    }
505
506    fn two_points_to(&mut self) -> Option<(Vector, Vector)> {
507        let a = self.point()?;
508        self.skip_comma_whitespace();
509        let b = self.point_to()?;
510        Some((a, b))
511    }
512
513    fn two_points(&mut self) -> Option<(Vector, Vector)> {
514        let a = self.point()?;
515        self.skip_comma_whitespace();
516        let b = self.point()?;
517        Some((a, b))
518    }
519
520    fn rel_two_points_to(&mut self) -> Option<(Vector, Vector)> {
521        let a = self.rel_point()?;
522        self.skip_comma_whitespace();
523        let b = self.rel_point_to()?;
524        Some((a, b))
525    }
526
527    fn rel_two_points(&mut self) -> Option<(Vector, Vector)> {
528        let a = self.rel_point()?;
529        self.skip_comma_whitespace();
530        let b = self.rel_point()?;
531        Some((a, b))
532    }
533
534    fn three_points_to(&mut self) -> Option<(Vector, Vector, Vector)> {
535        let a = self.point()?;
536        self.skip_comma_whitespace();
537        let b = self.point()?;
538        self.skip_comma_whitespace();
539        let c = self.point_to()?;
540        Some((a, b, c))
541    }
542
543    fn rel_three_points_to(&mut self) -> Option<(Vector, Vector, Vector)> {
544        let a = self.rel_point()?;
545        self.skip_comma_whitespace();
546        let b = self.rel_point()?;
547        self.skip_comma_whitespace();
548        let c = self.rel_point_to()?;
549        Some((a, b, c))
550    }
551
552    fn coord(&mut self) -> Option<f32> {
553        match self.cur {
554            b'+' => {
555                self.advance();
556                self.number()
557            }
558            b'-' => {
559                self.advance();
560                Some(-self.number()?)
561            }
562            _ => self.number(),
563        }
564    }
565
566    fn number(&mut self) -> Option<f32> {
567        let mut buf = [0u8; 32];
568        let mut pos = 0;
569        let mut has_decimal = false;
570        loop {
571            match self.cur {
572                b'.' => {
573                    if has_decimal {
574                        break;
575                    } else {
576                        *buf.get_mut(pos)? = self.cur;
577                        pos += 1;
578                        has_decimal = true;
579                    }
580                }
581                b'0'..=b'9' => {
582                    *buf.get_mut(pos)? = self.cur;
583                    pos += 1;
584                }
585                _ => break,
586            }
587            self.advance();
588        }
589        let s = core::str::from_utf8(&buf[..pos]).ok()?;
590        s.parse::<f32>().ok()
591    }
592
593    fn boolean(&mut self) -> Option<bool> {
594        match self.cur {
595            b'0' => {
596                self.advance();
597                Some(false)
598            }
599            b'1' => {
600                self.advance();
601                Some(true)
602            }
603            _ => None,
604        }
605    }
606
607    fn skip_comma_whitespace(&mut self) {
608        self.skip_whitespace();
609        if self.accept(b',') {
610            self.skip_whitespace();
611        }
612    }
613
614    #[allow(clippy::match_like_matches_macro)]
615    fn skip_whitespace(&mut self) {
616        while self.accept_by(|b| match b {
617            0x9 | 0x20 | 0xA | 0xC | 0xD => true,
618            _ => false,
619        }) {}
620    }
621
622    fn accept(&mut self, b: u8) -> bool {
623        if self.cur == b {
624            self.advance();
625            return true;
626        }
627        false
628    }
629
630    fn accept_by(&mut self, f: impl Fn(u8) -> bool) -> bool {
631        if f(self.cur) {
632            self.advance();
633            return true;
634        }
635        false
636    }
637
638    fn advance(&mut self) {
639        if self.pos == self.buf.len() {
640            self.done = true;
641            self.cur = 0;
642            return;
643        }
644        self.cur = self.buf[self.pos];
645        self.pos += 1;
646    }
647}
648
649/// Returns an error indicating the first position of invalid SVG path data.
650pub fn validate_svg(svg: &str) -> Result<(), usize> {
651    let cmds = &mut SvgCommands::new(svg);
652    cmds.count();
653    let pos = cmds.pos;
654    if cmds.error || pos != svg.len() {
655        Err(pos.saturating_sub(1))
656    } else {
657        Ok(())
658    }
659}