Skip to main content

nanachi/path_data_notation/
mod.rs

1mod tokenize;
2
3use std::{iter::Peekable, str::Chars};
4use tokenize::{Tokenize, Token};
5use crate::point::Point;
6use crate::path::Path;
7use crate::path_builder::PathBuilder;
8
9enum LastControlPoint {
10    Quad(Point),
11    Cubic(Point),
12    None
13}
14
15/// Parse [SVG path notation](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths).
16pub fn parse(str: &str) -> Result<Path, String> {
17    let mut chars = str.chars();
18    let mut tokens = Tokenize::new(&mut chars).peekable();
19    let mut pb = PathBuilder::new();
20    let mut last_control_point = LastControlPoint::None;
21    loop {
22        match tokens.next().unwrap()? {
23            Token::LargeM => {
24                let ns = parse_n_nums(&mut tokens, 2)?;
25                pb.move_to(ns[0], ns[1]);
26                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
27                    let ns = parse_n_nums(&mut tokens, 2)?;
28                    pb.line_to(ns[0], ns[1]);
29                }
30                last_control_point = LastControlPoint::None;
31            }
32            Token::SmallM => {
33                let ns = parse_n_nums(&mut tokens, 2)?;
34                let current = pb.current_pos().unwrap_or((0.0, 0.0));
35                pb.move_to(current.0 + ns[0], current.1 + ns[1]);
36                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
37                    let ns = parse_n_nums(&mut tokens, 2)?;
38                    let current = pb.current_pos().unwrap_or((0.0, 0.0));
39                    pb.line_to(current.0 + ns[0], current.1 + ns[1]);
40                }
41                last_control_point = LastControlPoint::None;
42            }
43            Token::LargeL => {
44                let ns = parse_n_nums(&mut tokens, 2)?;
45                pb.line_to(ns[0], ns[1]);
46                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
47                    let ns = parse_n_nums(&mut tokens, 2)?;
48                    pb.line_to(ns[0], ns[1]);
49                }
50                last_control_point = LastControlPoint::None;
51            }
52            Token::SmallL => {
53                let ns = parse_n_nums(&mut tokens, 2)?;
54                let current = pb.current_pos().unwrap_or((0.0, 0.0));
55                pb.line_to(current.0 + ns[0], current.1 + ns[1]);
56                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
57                    let ns = parse_n_nums(&mut tokens, 2)?;
58                    let current = pb.current_pos().unwrap_or((0.0, 0.0));
59                    pb.line_to(current.0 + ns[0], current.1 + ns[1]);
60                }
61                last_control_point = LastControlPoint::None;
62            }
63            Token::LargeH => {
64                let ns = parse_n_nums(&mut tokens, 1)?;
65                let current = pb.current_pos().unwrap_or((0.0, 0.0));
66                pb.line_to(ns[0], current.1);
67                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
68                    let ns = parse_n_nums(&mut tokens, 1)?;
69                    let current = pb.current_pos().unwrap_or((0.0, 0.0));
70                    pb.line_to(ns[0], current.1);
71                }
72                last_control_point = LastControlPoint::None;
73            }
74            Token::SmallH => {
75                let ns = parse_n_nums(&mut tokens, 1)?;
76                let current = pb.current_pos().unwrap_or((0.0, 0.0));
77                pb.line_to(current.0 + ns[0], current.1);
78                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
79                    let ns = parse_n_nums(&mut tokens, 1)?;
80                    let current = pb.current_pos().unwrap_or((0.0, 0.0));
81                    pb.line_to(current.0 + ns[0], current.1);
82                }
83                last_control_point = LastControlPoint::None;
84            }
85            Token::LargeV => {
86                let ns = parse_n_nums(&mut tokens, 1)?;
87                let current = pb.current_pos().unwrap_or((0.0, 0.0));
88                pb.line_to(current.0, ns[0]);
89                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
90                    let ns = parse_n_nums(&mut tokens, 1)?;
91                    let current = pb.current_pos().unwrap_or((0.0, 0.0));
92                    pb.line_to(current.0, ns[0]);
93                }
94                last_control_point = LastControlPoint::None;
95            }
96            Token::SmallV => {
97                let ns = parse_n_nums(&mut tokens, 1)?;
98                let current = pb.current_pos().unwrap_or((0.0, 0.0));
99                pb.line_to(current.0, current.1 + ns[0]);
100                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
101                    let ns = parse_n_nums(&mut tokens, 1)?;
102                    let current = pb.current_pos().unwrap_or((0.0, 0.0));
103                    pb.line_to(current.0, current.1 + ns[0]);
104                }
105                last_control_point = LastControlPoint::None;
106            }
107            Token::LargeQ => {
108                let mut ns = parse_n_nums(&mut tokens, 4)?;
109                pb.quad(ns[0], ns[1], ns[2], ns[3]);
110                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
111                    ns = parse_n_nums(&mut tokens, 4)?;
112                    pb.quad(ns[0], ns[1], ns[2], ns[3]);
113                }
114                last_control_point = LastControlPoint::Quad(Point(ns[0], ns[1]));
115            }
116            Token::SmallQ => {
117                let mut ns = parse_n_nums(&mut tokens, 4)?;
118                let mut current = pb.current_pos().unwrap_or((0.0, 0.0));
119                pb.quad(current.0 + ns[0], current.1 + ns[1], current.0 + ns[2], current.1 + ns[3]);
120                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
121                    ns = parse_n_nums(&mut tokens, 4)?;
122                    current = pb.current_pos().unwrap_or((0.0, 0.0));
123                    pb.quad(current.0 + ns[0], current.1 + ns[1], current.0 + ns[2], current.1 + ns[3]);
124                }
125                last_control_point = LastControlPoint::Quad(Point(current.0 + ns[0], current.1 + ns[1]));
126            }
127            Token::LargeT => {
128                let mut ns = parse_n_nums(&mut tokens, 2)?;
129                let mut cp = quad_reflection_point(&last_control_point, pb.current_pos().unwrap_or((0.0, 0.0)).into());
130                pb.quad(cp.0, cp.1, ns[0], ns[1]);
131                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
132                    cp = Point(ns[0], ns[1]) * 2.0 - cp;
133                    ns = parse_n_nums(&mut tokens, 2)?;
134                    pb.quad(cp.0, cp.1, ns[0], ns[1]);
135                }
136                last_control_point = LastControlPoint::Quad(cp);
137            }
138            Token::SmallT => {
139                let mut ns = parse_n_nums(&mut tokens, 2)?;
140                let mut current = pb.current_pos().unwrap_or((0.0, 0.0)).into();
141                let mut cp = quad_reflection_point(&last_control_point, current);
142                pb.quad(cp.0, cp.1, current.0 + ns[0], current.1 + ns[1]);
143                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
144                    cp = (current + Point(ns[0], ns[1])) * 2.0 - cp;
145                    current = current + Point(ns[0], ns[1]);
146                    ns = parse_n_nums(&mut tokens, 2)?;
147                    pb.quad(cp.0, cp.1, current.0 + ns[0], current.1 + ns[1]);
148                }
149                last_control_point = LastControlPoint::Quad(cp);
150            }
151            Token::LargeC => {
152                let mut ns = parse_n_nums(&mut tokens, 6)?;
153                pb.cubic(ns[0], ns[1], ns[2], ns[3], ns[4], ns[5]);
154                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
155                    ns = parse_n_nums(&mut tokens, 6)?;
156                    pb.cubic(ns[0], ns[1], ns[2], ns[3], ns[4], ns[5]);
157                }
158                last_control_point = LastControlPoint::Cubic(Point(ns[2], ns[3]));
159            }
160            Token::SmallC => {
161                let mut ns = parse_n_nums(&mut tokens, 6)?;
162                let mut current = pb.current_pos().unwrap_or((0.0, 0.0));
163                pb.cubic(current.0 + ns[0], current.1 + ns[1], current.0 + ns[2], current.1 + ns[3], current.0 + ns[4], current.1 + ns[5]);
164                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
165                    ns = parse_n_nums(&mut tokens, 6)?;
166                    current = pb.current_pos().unwrap_or((0.0, 0.0));
167                    pb.cubic(current.0 + ns[0], current.1 + ns[1], current.0 + ns[2], current.1 + ns[3], current.0 + ns[4], current.1 + ns[5]);
168                }
169                last_control_point = LastControlPoint::Cubic(Point(current.0 + ns[2], current.1 + ns[3]));
170            }
171            Token::LargeS => {
172                let mut ns = parse_n_nums(&mut tokens, 4)?;
173                let mut cp = cubic_reflection_point(&last_control_point, pb.current_pos().unwrap_or((0.0, 0.0)).into());
174                pb.cubic(cp.0, cp.1, ns[0], ns[1], ns[2], ns[3]);
175                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
176                    cp = Point(ns[2], ns[3]) * 2.0 - Point(ns[0], ns[1]);
177                    ns = parse_n_nums(&mut tokens, 4)?;
178                    pb.cubic(cp.0, cp.1, ns[0], ns[1], ns[2], ns[3]);
179                }
180                last_control_point = LastControlPoint::Quad(Point(ns[0], ns[1]));
181            }
182            Token::SmallS => {
183                let mut ns = parse_n_nums(&mut tokens, 4)?;
184                let mut current = pb.current_pos().unwrap_or((0.0, 0.0)).into();
185                let mut cp = cubic_reflection_point(&last_control_point, current);
186                pb.cubic(cp.0, cp.1, current.0 + ns[0], current.1 + ns[1], current.0 + ns[2], current.1 + ns[3]);
187                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
188                    cp = current + Point(ns[0], ns[1]) * 2.0 - Point(ns[2], ns[3]);
189                    current = current + Point(ns[2], ns[3]);
190                    ns = parse_n_nums(&mut tokens, 4)?;
191                    pb.cubic(cp.0, cp.1, current.0 + ns[0], current.1 + ns[1], current.0 + ns[2], current.1 + ns[3]);
192                }
193                last_control_point = LastControlPoint::Quad(Point(current.0 + ns[0], current.1 + ns[1]));
194            }
195            Token::LargeA => {
196                let mut ns = parse_n_nums(&mut tokens, 7)?;
197                pb.ellipse_from_endpoint(ns[0], ns[1], ns[2].to_radians(), ns[3] != 0.0, ns[4] != 0.0, ns[5], ns[6]);
198                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
199                    ns = parse_n_nums(&mut tokens, 7)?;
200                    pb.ellipse_from_endpoint(ns[0], ns[1], ns[2].to_radians(), ns[3] != 0.0, ns[4] != 0.0, ns[5], ns[6]);
201                }
202                last_control_point = LastControlPoint::None;
203            }
204            Token::SmallA => {
205                let mut ns = parse_n_nums(&mut tokens, 7)?;
206                let mut current: Point = pb.current_pos().unwrap_or((0.0, 0.0)).into();
207                current = current + Point(ns[5], ns[6]);
208                pb.ellipse_from_endpoint(ns[0], ns[1], ns[2].to_radians(), ns[3] != 0.0, ns[4] != 0.0, current.0, current.1);
209                while is_num_or_comma(&tokens.peek().unwrap().to_owned()?) {
210                    ns = parse_n_nums(&mut tokens, 7)?;
211                    current = current + Point(ns[5], ns[6]);
212                    pb.ellipse_from_endpoint(ns[0], ns[1], ns[2].to_radians(), ns[3] != 0.0, ns[4] != 0.0, current.0, current.1);
213                }
214                last_control_point = LastControlPoint::None;
215            }
216            Token::LargeZ | Token::SmallZ => {
217                pb.close();
218                last_control_point = LastControlPoint::None;
219            }
220            Token::EOS => {
221                break;
222            }
223            token => {
224                return Err(format!("Unexpected token: {:?}", token));
225            }
226        }
227    }
228    Ok(pb.end())
229}
230
231fn parse_n_nums(tokens: &mut Peekable<Tokenize<Chars>>, n: usize) -> Result<Vec<f64>, String> {
232    let mut v = Vec::new();
233    for _ in 0..n {
234        skip_comma(tokens)?;
235        match tokens.next().unwrap()? {
236            Token::Num(n) => {
237                v.push(n);
238            }
239            token => {
240                return Err(format!("Expects num, but found: {:?}", token));
241            }
242        };
243    }
244    Ok(v)
245}
246
247fn is_num_or_comma(token: &Token) -> bool {
248    match token {
249        Token::Num(_) | Token::Comma => true,
250        _ => false,
251    }
252}
253
254fn skip_comma(tokens: &mut Peekable<Tokenize<Chars>>) -> Result<(), String> {
255    match tokens.peek().unwrap().to_owned()? {
256        Token::Comma => {
257            tokens.next().unwrap()?;
258        }
259        _ => {}
260    }
261    Ok(())
262}
263
264fn quad_reflection_point(lcp: &LastControlPoint, pos: Point) -> Point {
265    match lcp {
266        LastControlPoint::Quad(p) => {
267            pos * 2.0 - *p
268        }
269        _ => {
270            pos
271        }
272    }
273}
274
275fn cubic_reflection_point(lcp: &LastControlPoint, pos: Point) -> Point {
276    match lcp {
277        LastControlPoint::Cubic(p) => {
278            pos * 2.0 - *p
279        }
280        _ => {
281            pos
282        }
283    }
284}