/src/librsvg/rsvg/src/path_parser.rs
Line | Count | Source |
1 | | //! Parser for SVG path data. |
2 | | |
3 | | use std::fmt; |
4 | | use std::iter::Enumerate; |
5 | | use std::str; |
6 | | use std::str::Bytes; |
7 | | |
8 | | use crate::path_builder::*; |
9 | | |
10 | | #[derive(Debug, PartialEq, Copy, Clone)] |
11 | | pub enum Token { |
12 | | // pub to allow benchmarking |
13 | | Number(f64), |
14 | | Flag(bool), |
15 | | Command(u8), |
16 | | Comma, |
17 | | } |
18 | | |
19 | | use crate::path_parser::Token::{Comma, Command, Flag, Number}; |
20 | | |
21 | | /// Splits an input string from a `path`'s `d` attribute into tokens. |
22 | | /// |
23 | | /// `Lexer` implements the [Iterator] trait; that's how calling code iterates over tokens. |
24 | | #[derive(Debug)] |
25 | | pub struct Lexer<'a> { |
26 | | // pub to allow benchmarking |
27 | | input: &'a [u8], |
28 | | ci: Enumerate<Bytes<'a>>, |
29 | | current: Option<(usize, u8)>, |
30 | | flags_required: u8, |
31 | | } |
32 | | |
33 | | #[derive(Debug, PartialEq, Copy, Clone)] |
34 | | pub enum LexError { |
35 | | // pub to allow benchmarking |
36 | | ParseFloatError, |
37 | | UnexpectedByte(u8), |
38 | | UnexpectedEof, |
39 | | } |
40 | | |
41 | | impl<'a> Lexer<'_> { |
42 | 22.8k | pub fn new(input: &'a str) -> Lexer<'a> { |
43 | 22.8k | let mut ci = input.bytes().enumerate(); |
44 | 22.8k | let current = ci.next(); |
45 | 22.8k | Lexer { |
46 | 22.8k | input: input.as_bytes(), |
47 | 22.8k | ci, |
48 | 22.8k | current, |
49 | 22.8k | flags_required: 0, |
50 | 22.8k | } |
51 | 22.8k | } |
52 | | |
53 | | // The way Flag tokens work is a little annoying. We don't have |
54 | | // any way to distinguish between numbers and flags without context |
55 | | // from the parser. The only time we need to return flags is within the |
56 | | // argument sequence of an elliptical arc, and then we need 2 in a row |
57 | | // or it's an error. So, when the parser gets to that point, it calls |
58 | | // this method and we switch from our usual mode of handling digits as |
59 | | // numbers to looking for two 'flag' characters (either 0 or 1) in a row |
60 | | // (with optional intervening whitespace, and possibly comma tokens.) |
61 | | // Every time we find a flag we decrement flags_required. |
62 | 21 | pub fn require_flags(&mut self) { |
63 | 21 | self.flags_required = 2; |
64 | 21 | } |
65 | | |
66 | 578 | fn current_pos(&mut self) -> usize { |
67 | 578 | match self.current { |
68 | 0 | None => self.input.len(), |
69 | 578 | Some((pos, _)) => pos, |
70 | | } |
71 | 578 | } |
72 | | |
73 | 408k | fn advance(&mut self) { |
74 | 408k | self.current = self.ci.next(); |
75 | 408k | } |
76 | | |
77 | 515k | fn advance_over_whitespace(&mut self) -> bool { |
78 | 515k | let mut found_some = false; |
79 | 649k | while self.current.is_some() && self.current.unwrap().1.is_ascii_whitespace() { |
80 | 134k | found_some = true; |
81 | 134k | self.current = self.ci.next(); |
82 | 134k | } |
83 | 515k | found_some |
84 | 515k | } |
85 | | |
86 | 1.36M | fn advance_over_optional(&mut self, needle: u8) -> bool { |
87 | 1.35M | match self.current { |
88 | 1.35M | Some((_, c)) if c == needle => { |
89 | 200k | self.advance(); |
90 | 200k | true |
91 | | } |
92 | 1.16M | _ => false, |
93 | | } |
94 | 1.36M | } |
95 | | |
96 | 580k | fn advance_over_digits(&mut self) -> bool { |
97 | 580k | let mut found_some = false; |
98 | 1.54M | while self.current.is_some() && self.current.unwrap().1.is_ascii_digit() { |
99 | 959k | found_some = true; |
100 | 959k | self.current = self.ci.next(); |
101 | 959k | } |
102 | 580k | found_some |
103 | 580k | } |
104 | | |
105 | 288k | fn advance_over_simple_number(&mut self) -> bool { |
106 | 288k | let _ = self.advance_over_optional(b'-') || self.advance_over_optional(b'+'); |
107 | 288k | let found_digit = self.advance_over_digits(); |
108 | 288k | let _ = self.advance_over_optional(b'.'); |
109 | 288k | self.advance_over_digits() || found_digit |
110 | 288k | } |
111 | | |
112 | 288k | fn match_number(&mut self) -> Result<Token, LexError> { |
113 | | // remember the beginning |
114 | 288k | let (start_pos, _) = self.current.unwrap(); |
115 | 288k | if !self.advance_over_simple_number() && start_pos != self.current_pos() { |
116 | 578 | match self.current { |
117 | 0 | None => return Err(LexError::UnexpectedEof), |
118 | 578 | Some((_pos, c)) => return Err(LexError::UnexpectedByte(c)), |
119 | | } |
120 | 288k | } |
121 | 288k | if self.advance_over_optional(b'e') || self.advance_over_optional(b'E') { |
122 | 2.81k | let _ = self.advance_over_optional(b'-') || self.advance_over_optional(b'+'); |
123 | 2.81k | let _ = self.advance_over_digits(); |
124 | 285k | } |
125 | 288k | let end_pos = match self.current { |
126 | 347 | None => self.input.len(), |
127 | 287k | Some((i, _)) => i, |
128 | | }; |
129 | | |
130 | | // If you need path parsing to be faster, you can do from_utf8_unchecked to |
131 | | // avoid re-validating all the chars, and std::str::parse<i*> calls are |
132 | | // faster than std::str::parse<f64> for numbers that are not floats. |
133 | | |
134 | | // bare unwrap here should be safe since we've already checked all the bytes |
135 | | // in the range |
136 | 288k | match std::str::from_utf8(&self.input[start_pos..end_pos]) |
137 | 288k | .unwrap() |
138 | 288k | .parse::<f64>() |
139 | | { |
140 | 288k | Ok(n) => Ok(Number(n)), |
141 | 250 | Err(_e) => Err(LexError::ParseFloatError), |
142 | | } |
143 | 288k | } |
144 | | } |
145 | | |
146 | | impl Iterator for Lexer<'_> { |
147 | | type Item = (usize, Result<Token, LexError>); |
148 | | |
149 | 515k | fn next(&mut self) -> Option<Self::Item> { |
150 | | // eat whitespace |
151 | 515k | self.advance_over_whitespace(); |
152 | | |
153 | 289k | match self.current { |
154 | | // commas are separators |
155 | 57.8k | Some((pos, b',')) => { |
156 | 57.8k | self.advance(); |
157 | 57.8k | Some((pos, Ok(Comma))) |
158 | | } |
159 | | |
160 | | // alphabetic chars are commands |
161 | 439k | Some((pos, c)) if c.is_ascii_alphabetic() => { |
162 | 149k | let token = Command(c); |
163 | 149k | self.advance(); |
164 | 149k | Some((pos, Ok(token))) |
165 | | } |
166 | | |
167 | 289k | Some((pos, c)) if self.flags_required > 0 && c.is_ascii_digit() => match c { |
168 | | b'0' => { |
169 | 42 | self.flags_required -= 1; |
170 | 42 | self.advance(); |
171 | 42 | Some((pos, Ok(Flag(false)))) |
172 | | } |
173 | | b'1' => { |
174 | 0 | self.flags_required -= 1; |
175 | 0 | self.advance(); |
176 | 0 | Some((pos, Ok(Flag(true)))) |
177 | | } |
178 | 0 | _ => Some((pos, Err(LexError::UnexpectedByte(c)))), |
179 | | }, |
180 | | |
181 | 289k | Some((pos, c)) if c.is_ascii_digit() || c == b'-' || c == b'+' || c == b'.' => { |
182 | 288k | Some((pos, self.match_number())) |
183 | | } |
184 | | |
185 | 1.01k | Some((pos, c)) => { |
186 | 1.01k | self.advance(); |
187 | 1.01k | Some((pos, Err(LexError::UnexpectedByte(c)))) |
188 | | } |
189 | | |
190 | 18.1k | None => None, |
191 | | } |
192 | 515k | } |
193 | | } |
194 | | |
195 | | pub struct PathParser<'b> { |
196 | | tokens: Lexer<'b>, |
197 | | current_pos_and_token: Option<(usize, Result<Token, LexError>)>, |
198 | | |
199 | | builder: &'b mut PathBuilder, |
200 | | |
201 | | // Current point; adjusted at every command |
202 | | current_x: f64, |
203 | | current_y: f64, |
204 | | |
205 | | // Last control point from previous cubic curve command, used to reflect |
206 | | // the new control point for smooth cubic curve commands. |
207 | | cubic_reflection_x: f64, |
208 | | cubic_reflection_y: f64, |
209 | | |
210 | | // Last control point from previous quadratic curve command, used to reflect |
211 | | // the new control point for smooth quadratic curve commands. |
212 | | quadratic_reflection_x: f64, |
213 | | quadratic_reflection_y: f64, |
214 | | |
215 | | // Start point of current subpath (i.e. position of last moveto); |
216 | | // used for closepath. |
217 | | subpath_start_x: f64, |
218 | | subpath_start_y: f64, |
219 | | } |
220 | | |
221 | | // This is a recursive descent parser for path data in SVG files, |
222 | | // as specified in https://www.w3.org/TR/SVG/paths.html#PathDataBNF |
223 | | // Some peculiarities: |
224 | | // |
225 | | // - SVG allows optional commas inside coordinate pairs, and between |
226 | | // coordinate pairs. So, for example, these are equivalent: |
227 | | // |
228 | | // M 10 20 30 40 |
229 | | // M 10, 20 30, 40 |
230 | | // M 10, 20, 30, 40 |
231 | | // |
232 | | // - Whitespace is optional. These are equivalent: |
233 | | // |
234 | | // M10,20 30,40 |
235 | | // M10,20,30,40 |
236 | | // |
237 | | // These are also equivalent: |
238 | | // |
239 | | // M-10,20-30-40 |
240 | | // M -10 20 -30 -40 |
241 | | // |
242 | | // M.1-2,3E2-4 |
243 | | // M 0.1 -2 300 -4 |
244 | | impl<'b> PathParser<'b> { |
245 | 22.8k | pub fn new(builder: &'b mut PathBuilder, path_str: &'b str) -> PathParser<'b> { |
246 | 22.8k | let mut lexer = Lexer::new(path_str); |
247 | 22.8k | let pt = lexer.next(); |
248 | 22.8k | PathParser { |
249 | 22.8k | tokens: lexer, |
250 | 22.8k | current_pos_and_token: pt, |
251 | 22.8k | |
252 | 22.8k | builder, |
253 | 22.8k | |
254 | 22.8k | current_x: 0.0, |
255 | 22.8k | current_y: 0.0, |
256 | 22.8k | |
257 | 22.8k | cubic_reflection_x: 0.0, |
258 | 22.8k | cubic_reflection_y: 0.0, |
259 | 22.8k | |
260 | 22.8k | quadratic_reflection_x: 0.0, |
261 | 22.8k | quadratic_reflection_y: 0.0, |
262 | 22.8k | |
263 | 22.8k | subpath_start_x: 0.0, |
264 | 22.8k | subpath_start_y: 0.0, |
265 | 22.8k | } |
266 | 22.8k | } |
267 | | |
268 | | // Our match_* methods all either consume the token we requested |
269 | | // and return the unwrapped value, or return an error without |
270 | | // advancing the token stream. |
271 | | // |
272 | | // You can safely use them to probe for a particular kind of token, |
273 | | // fail to match it, and try some other type. |
274 | | |
275 | 147k | fn match_command(&mut self) -> Result<u8, ParseError> { |
276 | 147k | let result = match &self.current_pos_and_token { |
277 | 147k | Some((_, Ok(Command(c)))) => Ok(*c), |
278 | 24 | Some((pos, Ok(t))) => Err(ParseError::new(*pos, UnexpectedToken(*t))), |
279 | 513 | Some((pos, Err(e))) => Err(ParseError::new(*pos, LexError(*e))), |
280 | 0 | None => Err(ParseError::new(self.tokens.input.len(), UnexpectedEof)), |
281 | | }; |
282 | 147k | if result.is_ok() { |
283 | 147k | self.current_pos_and_token = self.tokens.next(); |
284 | 147k | } |
285 | 147k | result |
286 | 147k | } |
287 | | |
288 | 291k | fn match_number(&mut self) -> Result<f64, ParseError> { |
289 | 291k | let result = match &self.current_pos_and_token { |
290 | 287k | Some((_, Ok(Number(n)))) => Ok(*n), |
291 | 2.34k | Some((pos, Ok(t))) => Err(ParseError::new(*pos, UnexpectedToken(*t))), |
292 | 1.21k | Some((pos, Err(e))) => Err(ParseError::new(*pos, LexError(*e))), |
293 | 244 | None => Err(ParseError::new(self.tokens.input.len(), UnexpectedEof)), |
294 | | }; |
295 | 291k | if result.is_ok() { |
296 | 287k | self.current_pos_and_token = self.tokens.next(); |
297 | 287k | } |
298 | 291k | result |
299 | 291k | } |
300 | | |
301 | 21 | fn match_number_and_flags(&mut self) -> Result<(f64, bool, bool), ParseError> { |
302 | | // We can't just do self.match_number() here, because we have to |
303 | | // tell the lexer, if we do find a number, to switch to looking for flags |
304 | | // before we advance it to the next token. Otherwise it will treat the flag |
305 | | // characters as numbers. |
306 | | // |
307 | | // So, first we do the guts of match_number... |
308 | 21 | let n = match &self.current_pos_and_token { |
309 | 21 | Some((_, Ok(Number(n)))) => Ok(*n), |
310 | 0 | Some((pos, Ok(t))) => Err(ParseError::new(*pos, UnexpectedToken(*t))), |
311 | 0 | Some((pos, Err(e))) => Err(ParseError::new(*pos, LexError(*e))), |
312 | 0 | None => Err(ParseError::new(self.tokens.input.len(), UnexpectedEof)), |
313 | 0 | }?; |
314 | | |
315 | | // Then we tell the lexer that we're going to need to find Flag tokens, |
316 | | // *then* we can advance the token stream. |
317 | 21 | self.tokens.require_flags(); |
318 | 21 | self.current_pos_and_token = self.tokens.next(); |
319 | | |
320 | 21 | self.eat_optional_comma(); |
321 | 21 | let f1 = self.match_flag()?; |
322 | | |
323 | 21 | self.eat_optional_comma(); |
324 | 21 | let f2 = self.match_flag()?; |
325 | | |
326 | 21 | Ok((n, f1, f2)) |
327 | 21 | } |
328 | | |
329 | 287k | fn match_comma(&mut self) -> Result<(), ParseError> { |
330 | 287k | let result = match &self.current_pos_and_token { |
331 | 57.2k | Some((_, Ok(Comma))) => Ok(()), |
332 | 228k | Some((pos, Ok(t))) => Err(ParseError::new(*pos, UnexpectedToken(*t))), |
333 | 1.28k | Some((pos, Err(e))) => Err(ParseError::new(*pos, LexError(*e))), |
334 | 823 | None => Err(ParseError::new(self.tokens.input.len(), UnexpectedEof)), |
335 | | }; |
336 | 287k | if result.is_ok() { |
337 | 57.2k | self.current_pos_and_token = self.tokens.next(); |
338 | 230k | } |
339 | 287k | result |
340 | 287k | } |
341 | | |
342 | 144k | fn eat_optional_comma(&mut self) { |
343 | 144k | let _ = self.match_comma(); |
344 | 144k | } |
345 | | |
346 | | // Convenience function; like match_number, but eats a leading comma if present. |
347 | 110k | fn match_comma_number(&mut self) -> Result<f64, ParseError> { |
348 | 110k | self.eat_optional_comma(); |
349 | 110k | self.match_number() |
350 | 110k | } |
351 | | |
352 | 42 | fn match_flag(&mut self) -> Result<bool, ParseError> { |
353 | 42 | let result = match self.current_pos_and_token { |
354 | 42 | Some((_, Ok(Flag(f)))) => Ok(f), |
355 | 0 | Some((pos, Ok(t))) => Err(ParseError::new(pos, UnexpectedToken(t))), |
356 | 0 | Some((pos, Err(e))) => Err(ParseError::new(pos, LexError(e))), |
357 | 0 | None => Err(ParseError::new(self.tokens.input.len(), UnexpectedEof)), |
358 | | }; |
359 | 42 | if result.is_ok() { |
360 | 42 | self.current_pos_and_token = self.tokens.next(); |
361 | 42 | } |
362 | 42 | result |
363 | 42 | } |
364 | | |
365 | | // peek_* methods are the twins of match_*, but don't consume the token, and so |
366 | | // can't return ParseError |
367 | | |
368 | 142k | fn peek_command(&mut self) -> Option<u8> { |
369 | 123k | match &self.current_pos_and_token { |
370 | 123k | Some((_, Ok(Command(c)))) => Some(*c), |
371 | 18.3k | _ => None, |
372 | | } |
373 | 142k | } |
374 | | |
375 | 141k | fn peek_number(&mut self) -> Option<f64> { |
376 | 141k | match &self.current_pos_and_token { |
377 | 24.4k | Some((_, Ok(Number(n)))) => Some(*n), |
378 | 117k | _ => None, |
379 | | } |
380 | 141k | } |
381 | | |
382 | | // This is the entry point for parsing a given blob of path data. |
383 | | // All the parsing just uses various match_* methods to consume tokens |
384 | | // and retrieve the values. |
385 | 22.8k | pub fn parse(&mut self) -> Result<(), ParseError> { |
386 | 22.8k | if self.current_pos_and_token.is_none() { |
387 | 0 | return Ok(()); |
388 | 22.8k | } |
389 | | |
390 | 22.8k | self.moveto_drawto_command_groups() |
391 | 22.8k | } |
392 | | |
393 | 629 | fn error(&self, kind: ErrorKind) -> ParseError { |
394 | 629 | match self.current_pos_and_token { |
395 | 629 | Some((pos, _)) => ParseError { |
396 | 629 | position: pos, |
397 | 629 | kind, |
398 | 629 | }, |
399 | 0 | None => ParseError { position: 0, kind }, // FIXME: ??? |
400 | | } |
401 | 629 | } |
402 | | |
403 | 110k | fn coordinate_pair(&mut self) -> Result<(f64, f64), ParseError> { |
404 | 110k | Ok((self.match_number()?, self.match_comma_number()?)) |
405 | 110k | } |
406 | | |
407 | 149k | fn set_current_point(&mut self, x: f64, y: f64) { |
408 | 149k | self.current_x = x; |
409 | 149k | self.current_y = y; |
410 | | |
411 | 149k | self.cubic_reflection_x = self.current_x; |
412 | 149k | self.cubic_reflection_y = self.current_y; |
413 | | |
414 | 149k | self.quadratic_reflection_x = self.current_x; |
415 | 149k | self.quadratic_reflection_y = self.current_y; |
416 | 149k | } |
417 | | |
418 | 16.4k | fn set_cubic_reflection_and_current_point(&mut self, x3: f64, y3: f64, x4: f64, y4: f64) { |
419 | 16.4k | self.cubic_reflection_x = x3; |
420 | 16.4k | self.cubic_reflection_y = y3; |
421 | | |
422 | 16.4k | self.current_x = x4; |
423 | 16.4k | self.current_y = y4; |
424 | | |
425 | 16.4k | self.quadratic_reflection_x = self.current_x; |
426 | 16.4k | self.quadratic_reflection_y = self.current_y; |
427 | 16.4k | } |
428 | | |
429 | 2.42k | fn set_quadratic_reflection_and_current_point(&mut self, a: f64, b: f64, c: f64, d: f64) { |
430 | 2.42k | self.quadratic_reflection_x = a; |
431 | 2.42k | self.quadratic_reflection_y = b; |
432 | | |
433 | 2.42k | self.current_x = c; |
434 | 2.42k | self.current_y = d; |
435 | | |
436 | 2.42k | self.cubic_reflection_x = self.current_x; |
437 | 2.42k | self.cubic_reflection_y = self.current_y; |
438 | 2.42k | } |
439 | | |
440 | 29.1k | fn emit_move_to(&mut self, x: f64, y: f64) { |
441 | 29.1k | self.set_current_point(x, y); |
442 | | |
443 | 29.1k | self.subpath_start_x = self.current_x; |
444 | 29.1k | self.subpath_start_y = self.current_y; |
445 | | |
446 | 29.1k | self.builder.move_to(self.current_x, self.current_y); |
447 | 29.1k | } |
448 | | |
449 | 95.9k | fn emit_line_to(&mut self, x: f64, y: f64) { |
450 | 95.9k | self.set_current_point(x, y); |
451 | | |
452 | 95.9k | self.builder.line_to(self.current_x, self.current_y); |
453 | 95.9k | } |
454 | | |
455 | 16.4k | fn emit_curve_to(&mut self, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) { |
456 | 16.4k | self.set_cubic_reflection_and_current_point(x3, y3, x4, y4); |
457 | | |
458 | 16.4k | self.builder.curve_to(x2, y2, x3, y3, x4, y4); |
459 | 16.4k | } |
460 | | |
461 | 2.42k | fn emit_quadratic_curve_to(&mut self, a: f64, b: f64, c: f64, d: f64) { |
462 | | // raise quadratic Bézier to cubic |
463 | 2.42k | let x2 = (self.current_x + 2.0 * a) / 3.0; |
464 | 2.42k | let y2 = (self.current_y + 2.0 * b) / 3.0; |
465 | 2.42k | let x4 = c; |
466 | 2.42k | let y4 = d; |
467 | 2.42k | let x3 = (x4 + 2.0 * a) / 3.0; |
468 | 2.42k | let y3 = (y4 + 2.0 * b) / 3.0; |
469 | | |
470 | 2.42k | self.set_quadratic_reflection_and_current_point(a, b, c, d); |
471 | | |
472 | 2.42k | self.builder.curve_to(x2, y2, x3, y3, x4, y4); |
473 | 2.42k | } |
474 | | |
475 | 21 | fn emit_arc( |
476 | 21 | &mut self, |
477 | 21 | rx: f64, |
478 | 21 | ry: f64, |
479 | 21 | x_axis_rotation: f64, |
480 | 21 | large_arc: LargeArc, |
481 | 21 | sweep: Sweep, |
482 | 21 | x: f64, |
483 | 21 | y: f64, |
484 | 21 | ) { |
485 | 21 | let (start_x, start_y) = (self.current_x, self.current_y); |
486 | | |
487 | 21 | self.set_current_point(x, y); |
488 | | |
489 | 21 | self.builder.arc( |
490 | 21 | start_x, |
491 | 21 | start_y, |
492 | 21 | rx, |
493 | 21 | ry, |
494 | 21 | x_axis_rotation, |
495 | 21 | large_arc, |
496 | 21 | sweep, |
497 | 21 | self.current_x, |
498 | 21 | self.current_y, |
499 | | ); |
500 | 21 | } |
501 | | |
502 | 29.9k | fn moveto_argument_sequence(&mut self, absolute: bool) -> Result<(), ParseError> { |
503 | 29.9k | let (mut x, mut y) = self.coordinate_pair()?; |
504 | | |
505 | 29.1k | if !absolute { |
506 | 25.5k | x += self.current_x; |
507 | 25.5k | y += self.current_y; |
508 | 25.5k | } |
509 | | |
510 | 29.1k | self.emit_move_to(x, y); |
511 | | |
512 | 29.1k | if self.match_comma().is_ok() || self.peek_number().is_some() { |
513 | 4.32k | self.lineto_argument_sequence(absolute) |
514 | | } else { |
515 | 24.8k | Ok(()) |
516 | | } |
517 | 29.9k | } |
518 | | |
519 | 31.1k | fn moveto(&mut self) -> Result<(), ParseError> { |
520 | 31.1k | match self.match_command()? { |
521 | 3.72k | b'M' => self.moveto_argument_sequence(true), |
522 | 26.2k | b'm' => self.moveto_argument_sequence(false), |
523 | 629 | c => Err(self.error(ErrorKind::UnexpectedCommand(c))), |
524 | | } |
525 | 31.1k | } |
526 | | |
527 | 31.1k | fn moveto_drawto_command_group(&mut self) -> Result<(), ParseError> { |
528 | 31.1k | self.moveto()?; |
529 | 28.4k | self.optional_drawto_commands().map(|_| ()) |
530 | 31.1k | } |
531 | | |
532 | 22.8k | fn moveto_drawto_command_groups(&mut self) -> Result<(), ParseError> { |
533 | | loop { |
534 | 31.1k | self.moveto_drawto_command_group()?; |
535 | | |
536 | 26.1k | if self.current_pos_and_token.is_none() { |
537 | 17.8k | break; |
538 | 8.29k | } |
539 | | } |
540 | | |
541 | 17.8k | Ok(()) |
542 | 22.8k | } |
543 | | |
544 | 28.4k | fn optional_drawto_commands(&mut self) -> Result<bool, ParseError> { |
545 | 142k | while self.drawto_command()? { |
546 | 113k | // everything happens in the drawto_command() calls. |
547 | 113k | } |
548 | | |
549 | 26.1k | Ok(false) |
550 | 28.4k | } |
551 | | |
552 | | // FIXME: This should not just fail to match 'M' and 'm', but make sure the |
553 | | // command is in the set of drawto command characters. |
554 | 142k | fn match_if_drawto_command_with_absolute(&mut self) -> Option<(u8, bool)> { |
555 | 142k | let cmd = self.peek_command(); |
556 | 142k | let result = match cmd { |
557 | 692 | Some(b'M') => None, |
558 | 6.43k | Some(b'm') => None, |
559 | 116k | Some(c) => { |
560 | 116k | let c_up = c.to_ascii_uppercase(); |
561 | 116k | if c == c_up { |
562 | 7.11k | Some((c_up, true)) |
563 | | } else { |
564 | 109k | Some((c_up, false)) |
565 | | } |
566 | | } |
567 | 18.3k | _ => None, |
568 | | }; |
569 | 142k | if result.is_some() { |
570 | 116k | let _ = self.match_command(); |
571 | 116k | } |
572 | 142k | result |
573 | 142k | } |
574 | | |
575 | 142k | fn drawto_command(&mut self) -> Result<bool, ParseError> { |
576 | 142k | match self.match_if_drawto_command_with_absolute() { |
577 | | Some((b'Z', _)) => { |
578 | 24.5k | self.emit_close_path(); |
579 | 24.5k | Ok(true) |
580 | | } |
581 | 10.0k | Some((b'L', abs)) => { |
582 | 10.0k | self.lineto_argument_sequence(abs)?; |
583 | 9.89k | Ok(true) |
584 | | } |
585 | 38.9k | Some((b'H', abs)) => { |
586 | 38.9k | self.horizontal_lineto_argument_sequence(abs)?; |
587 | 38.9k | Ok(true) |
588 | | } |
589 | 29.9k | Some((b'V', abs)) => { |
590 | 29.9k | self.vertical_lineto_argument_sequence(abs)?; |
591 | 29.9k | Ok(true) |
592 | | } |
593 | 10.8k | Some((b'C', abs)) => { |
594 | 10.8k | self.curveto_argument_sequence(abs)?; |
595 | 10.0k | Ok(true) |
596 | | } |
597 | 123 | Some((b'S', abs)) => { |
598 | 123 | self.smooth_curveto_argument_sequence(abs)?; |
599 | 90 | Ok(true) |
600 | | } |
601 | 2 | Some((b'Q', abs)) => { |
602 | 2 | self.quadratic_curveto_argument_sequence(abs)?; |
603 | 0 | Ok(true) |
604 | | } |
605 | 932 | Some((b'T', abs)) => { |
606 | 932 | self.smooth_quadratic_curveto_argument_sequence(abs)?; |
607 | 122 | Ok(true) |
608 | | } |
609 | 564 | Some((b'A', abs)) => { |
610 | 564 | self.elliptical_arc_argument_sequence(abs)?; |
611 | 21 | Ok(true) |
612 | | } |
613 | 26.1k | _ => Ok(false), |
614 | | } |
615 | 142k | } |
616 | | |
617 | 24.5k | fn emit_close_path(&mut self) { |
618 | 24.5k | let (x, y) = (self.subpath_start_x, self.subpath_start_y); |
619 | 24.5k | self.set_current_point(x, y); |
620 | | |
621 | 24.5k | self.builder.close_path(); |
622 | 24.5k | } |
623 | | |
624 | 114k | fn should_break_arg_sequence(&mut self) -> bool { |
625 | 114k | if self.match_comma().is_ok() { |
626 | | // if there is a comma (indicating we should continue to loop), eat the comma |
627 | | // so we're ready at the next start of the loop to process the next token. |
628 | 1.30k | false |
629 | | } else { |
630 | | // continue to process args in the sequence unless the next token is a comma |
631 | 113k | self.peek_number().is_none() |
632 | | } |
633 | 114k | } |
634 | | |
635 | 14.3k | fn lineto_argument_sequence(&mut self, absolute: bool) -> Result<(), ParseError> { |
636 | | loop { |
637 | 26.8k | let (mut x, mut y) = self.coordinate_pair()?; |
638 | | |
639 | 26.0k | if !absolute { |
640 | 20.3k | x += self.current_x; |
641 | 20.3k | y += self.current_y; |
642 | 20.3k | } |
643 | | |
644 | 26.0k | self.emit_line_to(x, y); |
645 | | |
646 | 26.0k | if self.should_break_arg_sequence() { |
647 | 13.5k | break; |
648 | 12.4k | } |
649 | | } |
650 | | |
651 | 13.5k | Ok(()) |
652 | 14.3k | } |
653 | | |
654 | 38.9k | fn horizontal_lineto_argument_sequence(&mut self, absolute: bool) -> Result<(), ParseError> { |
655 | | loop { |
656 | 39.5k | let mut x = self.match_number()?; |
657 | | |
658 | 39.5k | if !absolute { |
659 | 39.5k | x += self.current_x; |
660 | 39.5k | } |
661 | | |
662 | 39.5k | let y = self.current_y; |
663 | | |
664 | 39.5k | self.emit_line_to(x, y); |
665 | | |
666 | 39.5k | if self.should_break_arg_sequence() { |
667 | 38.9k | break; |
668 | 587 | } |
669 | | } |
670 | | |
671 | 38.9k | Ok(()) |
672 | 38.9k | } |
673 | | |
674 | 29.9k | fn vertical_lineto_argument_sequence(&mut self, absolute: bool) -> Result<(), ParseError> { |
675 | | loop { |
676 | 30.3k | let mut y = self.match_number()?; |
677 | | |
678 | 30.3k | if !absolute { |
679 | 30.3k | y += self.current_y; |
680 | 30.3k | } |
681 | | |
682 | 30.3k | let x = self.current_x; |
683 | | |
684 | 30.3k | self.emit_line_to(x, y); |
685 | | |
686 | 30.3k | if self.should_break_arg_sequence() { |
687 | 29.9k | break; |
688 | 439 | } |
689 | | } |
690 | | |
691 | 29.9k | Ok(()) |
692 | 29.9k | } |
693 | | |
694 | 10.8k | fn curveto_argument_sequence(&mut self, absolute: bool) -> Result<(), ParseError> { |
695 | | loop { |
696 | 17.0k | let (mut x2, mut y2) = self.coordinate_pair()?; |
697 | | |
698 | 16.9k | self.eat_optional_comma(); |
699 | 16.9k | let (mut x3, mut y3) = self.coordinate_pair()?; |
700 | | |
701 | 16.6k | self.eat_optional_comma(); |
702 | 16.6k | let (mut x4, mut y4) = self.coordinate_pair()?; |
703 | | |
704 | 16.3k | if !absolute { |
705 | 13.0k | x2 += self.current_x; |
706 | 13.0k | y2 += self.current_y; |
707 | 13.0k | x3 += self.current_x; |
708 | 13.0k | y3 += self.current_y; |
709 | 13.0k | x4 += self.current_x; |
710 | 13.0k | y4 += self.current_y; |
711 | 13.0k | } |
712 | | |
713 | 16.3k | self.emit_curve_to(x2, y2, x3, y3, x4, y4); |
714 | | |
715 | 16.3k | if self.should_break_arg_sequence() { |
716 | 10.0k | break; |
717 | 6.25k | } |
718 | | } |
719 | | |
720 | 10.0k | Ok(()) |
721 | 10.8k | } |
722 | | |
723 | 123 | fn smooth_curveto_argument_sequence(&mut self, absolute: bool) -> Result<(), ParseError> { |
724 | | loop { |
725 | 123 | let (mut x3, mut y3) = self.coordinate_pair()?; |
726 | 120 | self.eat_optional_comma(); |
727 | 120 | let (mut x4, mut y4) = self.coordinate_pair()?; |
728 | | |
729 | 90 | if !absolute { |
730 | 90 | x3 += self.current_x; |
731 | 90 | y3 += self.current_y; |
732 | 90 | x4 += self.current_x; |
733 | 90 | y4 += self.current_y; |
734 | 90 | } |
735 | | |
736 | 90 | let (x2, y2) = ( |
737 | 90 | self.current_x + self.current_x - self.cubic_reflection_x, |
738 | 90 | self.current_y + self.current_y - self.cubic_reflection_y, |
739 | 90 | ); |
740 | | |
741 | 90 | self.emit_curve_to(x2, y2, x3, y3, x4, y4); |
742 | | |
743 | 90 | if self.should_break_arg_sequence() { |
744 | 90 | break; |
745 | 0 | } |
746 | | } |
747 | | |
748 | 90 | Ok(()) |
749 | 123 | } |
750 | | |
751 | 2 | fn quadratic_curveto_argument_sequence(&mut self, absolute: bool) -> Result<(), ParseError> { |
752 | | loop { |
753 | 2 | let (mut a, mut b) = self.coordinate_pair()?; |
754 | 2 | self.eat_optional_comma(); |
755 | 2 | let (mut c, mut d) = self.coordinate_pair()?; |
756 | | |
757 | 0 | if !absolute { |
758 | 0 | a += self.current_x; |
759 | 0 | b += self.current_y; |
760 | 0 | c += self.current_x; |
761 | 0 | d += self.current_y; |
762 | 0 | } |
763 | | |
764 | 0 | self.emit_quadratic_curve_to(a, b, c, d); |
765 | | |
766 | 0 | if self.should_break_arg_sequence() { |
767 | 0 | break; |
768 | 0 | } |
769 | | } |
770 | | |
771 | 0 | Ok(()) |
772 | 2 | } |
773 | | |
774 | 932 | fn smooth_quadratic_curveto_argument_sequence( |
775 | 932 | &mut self, |
776 | 932 | absolute: bool, |
777 | 932 | ) -> Result<(), ParseError> { |
778 | | loop { |
779 | 3.23k | let (mut c, mut d) = self.coordinate_pair()?; |
780 | | |
781 | 2.42k | if !absolute { |
782 | 2.42k | c += self.current_x; |
783 | 2.42k | d += self.current_y; |
784 | 2.42k | } |
785 | | |
786 | 2.42k | let (a, b) = ( |
787 | 2.42k | self.current_x + self.current_x - self.quadratic_reflection_x, |
788 | 2.42k | self.current_y + self.current_y - self.quadratic_reflection_y, |
789 | 2.42k | ); |
790 | | |
791 | 2.42k | self.emit_quadratic_curve_to(a, b, c, d); |
792 | | |
793 | 2.42k | if self.should_break_arg_sequence() { |
794 | 122 | break; |
795 | 2.30k | } |
796 | | } |
797 | | |
798 | 122 | Ok(()) |
799 | 932 | } |
800 | | |
801 | 564 | fn elliptical_arc_argument_sequence(&mut self, absolute: bool) -> Result<(), ParseError> { |
802 | | loop { |
803 | 564 | let rx = self.match_number()?.abs(); |
804 | 21 | let ry = self.match_comma_number()?.abs(); |
805 | | |
806 | 21 | self.eat_optional_comma(); |
807 | 21 | let (x_axis_rotation, f1, f2) = self.match_number_and_flags()?; |
808 | | |
809 | 21 | let large_arc = LargeArc(f1); |
810 | | |
811 | 21 | let sweep = if f2 { Sweep::Positive } else { Sweep::Negative }; |
812 | | |
813 | 21 | self.eat_optional_comma(); |
814 | | |
815 | 21 | let (mut x, mut y) = self.coordinate_pair()?; |
816 | | |
817 | 21 | if !absolute { |
818 | 14 | x += self.current_x; |
819 | 14 | y += self.current_y; |
820 | 14 | } |
821 | | |
822 | 21 | self.emit_arc(rx, ry, x_axis_rotation, large_arc, sweep, x, y); |
823 | | |
824 | 21 | if self.should_break_arg_sequence() { |
825 | 21 | break; |
826 | 0 | } |
827 | | } |
828 | | |
829 | 21 | Ok(()) |
830 | 564 | } |
831 | | } |
832 | | |
833 | | #[derive(Debug, PartialEq)] |
834 | | pub enum ErrorKind { |
835 | | UnexpectedToken(Token), |
836 | | UnexpectedCommand(u8), |
837 | | UnexpectedEof, |
838 | | LexError(LexError), |
839 | | } |
840 | | |
841 | | #[derive(Debug, PartialEq)] |
842 | | pub struct ParseError { |
843 | | pub position: usize, |
844 | | pub kind: ErrorKind, |
845 | | } |
846 | | |
847 | | impl ParseError { |
848 | 235k | fn new(pos: usize, k: ErrorKind) -> ParseError { |
849 | 235k | ParseError { |
850 | 235k | position: pos, |
851 | 235k | kind: k, |
852 | 235k | } |
853 | 235k | } |
854 | | } |
855 | | |
856 | | use crate::path_parser::ErrorKind::*; |
857 | | |
858 | | impl fmt::Display for ParseError { |
859 | | // Skipped for mutation testing; this is just an error formatter. These errors are just |
860 | | // surfaced as log messages. |
861 | | #[mutants::skip] |
862 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
863 | 0 | let description = match self.kind { |
864 | 0 | UnexpectedToken(_t) => "unexpected token", |
865 | 0 | UnexpectedCommand(_c) => "unexpected command", |
866 | 0 | UnexpectedEof => "unexpected end of data", |
867 | 0 | LexError(_le) => "error processing token", |
868 | | }; |
869 | 0 | write!(f, "error at position {}: {}", self.position, description) |
870 | 0 | } |
871 | | } |
872 | | |
873 | | #[cfg(test)] |
874 | | #[rustfmt::skip] |
875 | | mod tests { |
876 | | use super::*; |
877 | | |
878 | | fn find_error_pos(s: &str) -> Option<usize> { |
879 | | s.find('^') |
880 | | } |
881 | | |
882 | | fn make_parse_result( |
883 | | error_pos_str: &str, |
884 | | error_kind: Option<ErrorKind>, |
885 | | ) -> Result<(), ParseError> { |
886 | | if let Some(pos) = find_error_pos(error_pos_str) { |
887 | | Err(ParseError { |
888 | | position: pos, |
889 | | kind: error_kind.unwrap(), |
890 | | }) |
891 | | } else { |
892 | | assert!(error_kind.is_none()); |
893 | | Ok(()) |
894 | | } |
895 | | } |
896 | | |
897 | | fn test_parser( |
898 | | path_str: &str, |
899 | | error_pos_str: &str, |
900 | | expected_commands: &[PathCommand], |
901 | | expected_error_kind: Option<ErrorKind>, |
902 | | ) { |
903 | | let expected_result = make_parse_result(error_pos_str, expected_error_kind); |
904 | | |
905 | | let mut builder = PathBuilder::default(); |
906 | | let result = builder.parse(path_str); |
907 | | |
908 | | let path = builder.into_path(); |
909 | | let commands = path.iter().collect::<Vec<_>>(); |
910 | | |
911 | | assert_eq!(expected_commands, commands.as_slice()); |
912 | | assert_eq!(expected_result, result); |
913 | | } |
914 | | |
915 | | fn moveto(x: f64, y: f64) -> PathCommand { |
916 | | PathCommand::MoveTo(x, y) |
917 | | } |
918 | | |
919 | | fn lineto(x: f64, y: f64) -> PathCommand { |
920 | | PathCommand::LineTo(x, y) |
921 | | } |
922 | | |
923 | | fn curveto(x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> PathCommand { |
924 | | PathCommand::CurveTo(CubicBezierCurve { |
925 | | pt1: (x2, y2), |
926 | | pt2: (x3, y3), |
927 | | to: (x4, y4), |
928 | | }) |
929 | | } |
930 | | |
931 | | fn arc(x2: f64, y2: f64, xr: f64, large_arc: bool, sweep: bool, |
932 | | x3: f64, y3: f64, x4: f64, y4: f64) -> PathCommand { |
933 | | PathCommand::Arc(EllipticalArc { |
934 | | r: (x2, y2), |
935 | | x_axis_rotation: xr, |
936 | | large_arc: LargeArc(large_arc), |
937 | | sweep: match sweep { |
938 | | true => Sweep::Positive, |
939 | | false => Sweep::Negative, |
940 | | }, |
941 | | from: (x3, y3), |
942 | | to: (x4, y4), |
943 | | }) |
944 | | } |
945 | | |
946 | | fn closepath() -> PathCommand { |
947 | | PathCommand::ClosePath |
948 | | } |
949 | | |
950 | | #[test] |
951 | | fn handles_empty_data() { |
952 | | test_parser( |
953 | | "", |
954 | | "", |
955 | | &Vec::<PathCommand>::new(), |
956 | | None, |
957 | | ); |
958 | | } |
959 | | |
960 | | #[test] |
961 | | fn handles_numbers() { |
962 | | test_parser( |
963 | | "M 10 20", |
964 | | "", |
965 | | &[moveto(10.0, 20.0)], |
966 | | None, |
967 | | ); |
968 | | |
969 | | test_parser( |
970 | | "M -10 -20", |
971 | | "", |
972 | | &[moveto(-10.0, -20.0)], |
973 | | None, |
974 | | ); |
975 | | |
976 | | test_parser( |
977 | | "M .10 0.20", |
978 | | "", |
979 | | &[moveto(0.10, 0.20)], |
980 | | None, |
981 | | ); |
982 | | |
983 | | test_parser( |
984 | | "M -.10 -0.20", |
985 | | "", |
986 | | &[moveto(-0.10, -0.20)], |
987 | | None, |
988 | | ); |
989 | | |
990 | | test_parser( |
991 | | "M-.10-0.20", |
992 | | "", |
993 | | &[moveto(-0.10, -0.20)], |
994 | | None, |
995 | | ); |
996 | | |
997 | | test_parser( |
998 | | "M10.5.50", |
999 | | "", |
1000 | | &[moveto(10.5, 0.50)], |
1001 | | None, |
1002 | | ); |
1003 | | |
1004 | | test_parser( |
1005 | | "M.10.20", |
1006 | | "", |
1007 | | &[moveto(0.10, 0.20)], |
1008 | | None, |
1009 | | ); |
1010 | | |
1011 | | test_parser( |
1012 | | "M .10E1 .20e-4", |
1013 | | "", |
1014 | | &[moveto(1.0, 0.000020)], |
1015 | | None, |
1016 | | ); |
1017 | | |
1018 | | test_parser( |
1019 | | "M-.10E1-.20", |
1020 | | "", |
1021 | | &[moveto(-1.0, -0.20)], |
1022 | | None, |
1023 | | ); |
1024 | | |
1025 | | test_parser( |
1026 | | "M10.10E2 -0.20e3", |
1027 | | "", |
1028 | | &[moveto(1010.0, -200.0)], |
1029 | | None, |
1030 | | ); |
1031 | | |
1032 | | test_parser( |
1033 | | "M-10.10E2-0.20e-3", |
1034 | | "", |
1035 | | &[moveto(-1010.0, -0.00020)], |
1036 | | None, |
1037 | | ); |
1038 | | |
1039 | | test_parser( |
1040 | | "M1e2.5", // a decimal after exponent start the next number |
1041 | | "", |
1042 | | &[moveto(100.0, 0.5)], |
1043 | | None, |
1044 | | ); |
1045 | | |
1046 | | test_parser( |
1047 | | "M1e-2.5", // but we are allowed a sign after exponent |
1048 | | "", |
1049 | | &[moveto(0.01, 0.5)], |
1050 | | None, |
1051 | | ); |
1052 | | |
1053 | | test_parser( |
1054 | | "M1e+2.5", // but we are allowed a sign after exponent |
1055 | | "", |
1056 | | &[moveto(100.0, 0.5)], |
1057 | | None, |
1058 | | ); |
1059 | | } |
1060 | | |
1061 | | #[test] |
1062 | | fn detects_bogus_numbers() { |
1063 | | test_parser( |
1064 | | "M+", |
1065 | | " ^", |
1066 | | &[], |
1067 | | Some(ErrorKind::LexError(LexError::UnexpectedEof)), |
1068 | | ); |
1069 | | |
1070 | | test_parser( |
1071 | | "M-", |
1072 | | " ^", |
1073 | | &[], |
1074 | | Some(ErrorKind::LexError(LexError::UnexpectedEof)), |
1075 | | ); |
1076 | | |
1077 | | test_parser( |
1078 | | "M+x", |
1079 | | " ^", |
1080 | | &[], |
1081 | | Some(ErrorKind::LexError(LexError::UnexpectedByte(b'x'))), |
1082 | | ); |
1083 | | |
1084 | | test_parser( |
1085 | | "M10e", |
1086 | | " ^", |
1087 | | &[], |
1088 | | Some(ErrorKind::LexError(LexError::ParseFloatError)), |
1089 | | ); |
1090 | | |
1091 | | test_parser( |
1092 | | "M10ex", |
1093 | | " ^", |
1094 | | &[], |
1095 | | Some(ErrorKind::LexError(LexError::ParseFloatError)), |
1096 | | ); |
1097 | | |
1098 | | test_parser( |
1099 | | "M10e-", |
1100 | | " ^", |
1101 | | &[], |
1102 | | Some(ErrorKind::LexError(LexError::ParseFloatError)), |
1103 | | ); |
1104 | | |
1105 | | test_parser( |
1106 | | "M10e+x", |
1107 | | " ^", |
1108 | | &[], |
1109 | | Some(ErrorKind::LexError(LexError::ParseFloatError)), |
1110 | | ); |
1111 | | |
1112 | | test_parser( |
1113 | | "M 10 ;", |
1114 | | " ^", |
1115 | | &[], |
1116 | | Some(ErrorKind::LexError(LexError::UnexpectedByte(b';'))), |
1117 | | ); |
1118 | | } |
1119 | | |
1120 | | #[test] |
1121 | | fn handles_numbers_with_comma() { |
1122 | | test_parser( |
1123 | | "M 10, 20", |
1124 | | "", |
1125 | | &[moveto(10.0, 20.0)], |
1126 | | None, |
1127 | | ); |
1128 | | |
1129 | | test_parser( |
1130 | | "M -10,-20", |
1131 | | "", |
1132 | | &[moveto(-10.0, -20.0)], |
1133 | | None, |
1134 | | ); |
1135 | | |
1136 | | test_parser( |
1137 | | "M.10 , 0.20", |
1138 | | "", |
1139 | | &[moveto(0.10, 0.20)], |
1140 | | None, |
1141 | | ); |
1142 | | |
1143 | | test_parser( |
1144 | | "M -.10, -0.20 ", |
1145 | | "", |
1146 | | &[moveto(-0.10, -0.20)], |
1147 | | None, |
1148 | | ); |
1149 | | |
1150 | | test_parser( |
1151 | | "M-.10-0.20", |
1152 | | "", |
1153 | | &[moveto(-0.10, -0.20)], |
1154 | | None, |
1155 | | ); |
1156 | | |
1157 | | test_parser( |
1158 | | "M.10.20", |
1159 | | "", |
1160 | | &[moveto(0.10, 0.20)], |
1161 | | None, |
1162 | | ); |
1163 | | |
1164 | | test_parser( |
1165 | | "M .10E1,.20e-4", |
1166 | | "", |
1167 | | &[moveto(1.0, 0.000020)], |
1168 | | None, |
1169 | | ); |
1170 | | |
1171 | | test_parser( |
1172 | | "M-.10E-2,-.20", |
1173 | | "", |
1174 | | &[moveto(-0.0010, -0.20)], |
1175 | | None, |
1176 | | ); |
1177 | | |
1178 | | test_parser( |
1179 | | "M10.10E2,-0.20e3", |
1180 | | "", |
1181 | | &[moveto(1010.0, -200.0)], |
1182 | | None, |
1183 | | ); |
1184 | | |
1185 | | test_parser( |
1186 | | "M-10.10E2,-0.20e-3", |
1187 | | "", |
1188 | | &[moveto(-1010.0, -0.00020)], |
1189 | | None, |
1190 | | ); |
1191 | | } |
1192 | | |
1193 | | #[test] |
1194 | | fn handles_single_moveto() { |
1195 | | test_parser( |
1196 | | "M 10 20 ", |
1197 | | "", |
1198 | | &[moveto(10.0, 20.0)], |
1199 | | None, |
1200 | | ); |
1201 | | |
1202 | | test_parser( |
1203 | | "M10,20 ", |
1204 | | "", |
1205 | | &[moveto(10.0, 20.0)], |
1206 | | None, |
1207 | | ); |
1208 | | |
1209 | | test_parser( |
1210 | | "M10 20 ", |
1211 | | "", |
1212 | | &[moveto(10.0, 20.0)], |
1213 | | None, |
1214 | | ); |
1215 | | |
1216 | | test_parser( |
1217 | | " M10,20 ", |
1218 | | "", |
1219 | | &[moveto(10.0, 20.0)], |
1220 | | None, |
1221 | | ); |
1222 | | } |
1223 | | |
1224 | | #[test] |
1225 | | fn handles_relative_moveto() { |
1226 | | test_parser( |
1227 | | "m10 20", |
1228 | | "", |
1229 | | &[moveto(10.0, 20.0)], |
1230 | | None, |
1231 | | ); |
1232 | | } |
1233 | | |
1234 | | #[test] |
1235 | | fn handles_absolute_moveto_with_implicit_lineto() { |
1236 | | test_parser( |
1237 | | "M10 20 30 40", |
1238 | | "", |
1239 | | &[moveto(10.0, 20.0), lineto(30.0, 40.0)], |
1240 | | None, |
1241 | | ); |
1242 | | |
1243 | | test_parser( |
1244 | | "M10,20,30,40", |
1245 | | "", |
1246 | | &[moveto(10.0, 20.0), lineto(30.0, 40.0)], |
1247 | | None, |
1248 | | ); |
1249 | | |
1250 | | test_parser( |
1251 | | "M.1-2,3E2-4", |
1252 | | "", |
1253 | | &[moveto(0.1, -2.0), lineto(300.0, -4.0)], |
1254 | | None, |
1255 | | ); |
1256 | | } |
1257 | | |
1258 | | #[test] |
1259 | | fn handles_relative_moveto_with_implicit_lineto() { |
1260 | | test_parser( |
1261 | | "m10 20 30 40", |
1262 | | "", |
1263 | | &[moveto(10.0, 20.0), lineto(40.0, 60.0)], |
1264 | | None, |
1265 | | ); |
1266 | | } |
1267 | | |
1268 | | #[test] |
1269 | | fn handles_relative_moveto_with_relative_lineto_sequence() { |
1270 | | test_parser( |
1271 | | // 1 2 3 4 5 |
1272 | | "m 46,447 l 0,0.5 -1,0 -1,0 0,1 0,12", |
1273 | | "", |
1274 | | &vec![moveto(46.0, 447.0), lineto(46.0, 447.5), lineto(45.0, 447.5), |
1275 | | lineto(44.0, 447.5), lineto(44.0, 448.5), lineto(44.0, 460.5)], |
1276 | | None, |
1277 | | ); |
1278 | | } |
1279 | | |
1280 | | #[test] |
1281 | | fn handles_absolute_moveto_with_implicit_linetos() { |
1282 | | test_parser( |
1283 | | "M10,20 30,40,50 60", |
1284 | | "", |
1285 | | &[moveto(10.0, 20.0), lineto(30.0, 40.0), lineto(50.0, 60.0)], |
1286 | | None, |
1287 | | ); |
1288 | | } |
1289 | | |
1290 | | #[test] |
1291 | | fn handles_relative_moveto_with_implicit_linetos() { |
1292 | | test_parser( |
1293 | | "m10 20 30 40 50 60", |
1294 | | "", |
1295 | | &[moveto(10.0, 20.0), lineto(40.0, 60.0), lineto(90.0, 120.0)], |
1296 | | None, |
1297 | | ); |
1298 | | } |
1299 | | |
1300 | | #[test] |
1301 | | fn handles_absolute_moveto_moveto() { |
1302 | | test_parser( |
1303 | | "M10 20 M 30 40", |
1304 | | "", |
1305 | | &[moveto(10.0, 20.0), moveto(30.0, 40.0)], |
1306 | | None, |
1307 | | ); |
1308 | | } |
1309 | | |
1310 | | #[test] |
1311 | | fn handles_relative_moveto_moveto() { |
1312 | | test_parser( |
1313 | | "m10 20 m 30 40", |
1314 | | "", |
1315 | | &[moveto(10.0, 20.0), moveto(40.0, 60.0)], |
1316 | | None, |
1317 | | ); |
1318 | | } |
1319 | | |
1320 | | #[test] |
1321 | | fn handles_relative_moveto_lineto_moveto() { |
1322 | | test_parser( |
1323 | | "m10 20 30 40 m 50 60", |
1324 | | "", |
1325 | | &[moveto(10.0, 20.0), lineto(40.0, 60.0), moveto(90.0, 120.0)], |
1326 | | None, |
1327 | | ); |
1328 | | } |
1329 | | |
1330 | | #[test] |
1331 | | fn handles_absolute_moveto_lineto() { |
1332 | | test_parser( |
1333 | | "M10 20 L30,40", |
1334 | | "", |
1335 | | &[moveto(10.0, 20.0), lineto(30.0, 40.0)], |
1336 | | None, |
1337 | | ); |
1338 | | } |
1339 | | |
1340 | | #[test] |
1341 | | fn handles_relative_moveto_lineto() { |
1342 | | test_parser( |
1343 | | "m10 20 l30,40", |
1344 | | "", |
1345 | | &[moveto(10.0, 20.0), lineto(40.0, 60.0)], |
1346 | | None, |
1347 | | ); |
1348 | | } |
1349 | | |
1350 | | #[test] |
1351 | | fn handles_relative_moveto_lineto_lineto_abs_lineto() { |
1352 | | test_parser( |
1353 | | "m10 20 30 40l30,40,50 60L200,300", |
1354 | | "", |
1355 | | &vec![ |
1356 | | moveto(10.0, 20.0), |
1357 | | lineto(40.0, 60.0), |
1358 | | lineto(70.0, 100.0), |
1359 | | lineto(120.0, 160.0), |
1360 | | lineto(200.0, 300.0), |
1361 | | ], |
1362 | | None, |
1363 | | ); |
1364 | | } |
1365 | | |
1366 | | #[test] |
1367 | | fn handles_horizontal_lineto() { |
1368 | | test_parser( |
1369 | | "M10 20 H30", |
1370 | | "", |
1371 | | &[moveto(10.0, 20.0), lineto(30.0, 20.0)], |
1372 | | None, |
1373 | | ); |
1374 | | |
1375 | | test_parser( |
1376 | | "M10 20 H30 40", |
1377 | | "", |
1378 | | &[moveto(10.0, 20.0), lineto(30.0, 20.0), lineto(40.0, 20.0)], |
1379 | | None, |
1380 | | ); |
1381 | | |
1382 | | test_parser( |
1383 | | "M10 20 H30,40-50", |
1384 | | "", |
1385 | | &vec![ |
1386 | | moveto(10.0, 20.0), |
1387 | | lineto(30.0, 20.0), |
1388 | | lineto(40.0, 20.0), |
1389 | | lineto(-50.0, 20.0), |
1390 | | ], |
1391 | | None, |
1392 | | ); |
1393 | | |
1394 | | test_parser( |
1395 | | "m10 20 h30,40-50", |
1396 | | "", |
1397 | | &vec![ |
1398 | | moveto(10.0, 20.0), |
1399 | | lineto(40.0, 20.0), |
1400 | | lineto(80.0, 20.0), |
1401 | | lineto(30.0, 20.0), |
1402 | | ], |
1403 | | None, |
1404 | | ); |
1405 | | } |
1406 | | |
1407 | | #[test] |
1408 | | fn handles_vertical_lineto() { |
1409 | | test_parser( |
1410 | | "M10 20 V30", |
1411 | | "", |
1412 | | &[moveto(10.0, 20.0), lineto(10.0, 30.0)], |
1413 | | None, |
1414 | | ); |
1415 | | |
1416 | | test_parser( |
1417 | | "M10 20 V30 40", |
1418 | | "", |
1419 | | &[moveto(10.0, 20.0), lineto(10.0, 30.0), lineto(10.0, 40.0)], |
1420 | | None, |
1421 | | ); |
1422 | | |
1423 | | test_parser( |
1424 | | "M10 20 V30,40-50", |
1425 | | "", |
1426 | | &vec![ |
1427 | | moveto(10.0, 20.0), |
1428 | | lineto(10.0, 30.0), |
1429 | | lineto(10.0, 40.0), |
1430 | | lineto(10.0, -50.0), |
1431 | | ], |
1432 | | None, |
1433 | | ); |
1434 | | |
1435 | | test_parser( |
1436 | | "m10 20 v30,40-50", |
1437 | | "", |
1438 | | &vec![ |
1439 | | moveto(10.0, 20.0), |
1440 | | lineto(10.0, 50.0), |
1441 | | lineto(10.0, 90.0), |
1442 | | lineto(10.0, 40.0), |
1443 | | ], |
1444 | | None, |
1445 | | ); |
1446 | | } |
1447 | | |
1448 | | #[test] |
1449 | | fn handles_curveto() { |
1450 | | test_parser( |
1451 | | "M10 20 C 30,40 50 60-70,80", |
1452 | | "", |
1453 | | &[moveto(10.0, 20.0), |
1454 | | curveto(30.0, 40.0, 50.0, 60.0, -70.0, 80.0)], |
1455 | | None, |
1456 | | ); |
1457 | | |
1458 | | test_parser( |
1459 | | "M10 20 C 30,40 50 60-70,80,90 100,110 120,130,140", |
1460 | | "", |
1461 | | &[moveto(10.0, 20.0), |
1462 | | curveto(30.0, 40.0, 50.0, 60.0, -70.0, 80.0), |
1463 | | curveto(90.0, 100.0, 110.0, 120.0, 130.0, 140.0)], |
1464 | | None, |
1465 | | ); |
1466 | | |
1467 | | test_parser( |
1468 | | "m10 20 c 30,40 50 60-70,80,90 100,110 120,130,140", |
1469 | | "", |
1470 | | &[moveto(10.0, 20.0), |
1471 | | curveto(40.0, 60.0, 60.0, 80.0, -60.0, 100.0), |
1472 | | curveto(30.0, 200.0, 50.0, 220.0, 70.0, 240.0)], |
1473 | | None, |
1474 | | ); |
1475 | | |
1476 | | test_parser( |
1477 | | "m10 20 c 30,40 50 60-70,80,90 100,110 120,130,140", |
1478 | | "", |
1479 | | &[moveto(10.0, 20.0), |
1480 | | curveto(40.0, 60.0, 60.0, 80.0, -60.0, 100.0), |
1481 | | curveto(30.0, 200.0, 50.0, 220.0, 70.0, 240.0)], |
1482 | | None, |
1483 | | ); |
1484 | | } |
1485 | | |
1486 | | #[test] |
1487 | | fn handles_smooth_curveto() { |
1488 | | test_parser( |
1489 | | "M10 20 S 30,40-50,60", |
1490 | | "", |
1491 | | &[moveto(10.0, 20.0), |
1492 | | curveto(10.0, 20.0, 30.0, 40.0, -50.0, 60.0)], |
1493 | | None, |
1494 | | ); |
1495 | | |
1496 | | test_parser( |
1497 | | "M10 20 S 30,40 50 60-70,80,90 100", |
1498 | | "", |
1499 | | &[moveto(10.0, 20.0), |
1500 | | curveto(10.0, 20.0, 30.0, 40.0, 50.0, 60.0), |
1501 | | curveto(70.0, 80.0, -70.0, 80.0, 90.0, 100.0)], |
1502 | | None, |
1503 | | ); |
1504 | | |
1505 | | test_parser( |
1506 | | "m10 20 s 30,40 50 60-70,80,90 100", |
1507 | | "", |
1508 | | &[moveto(10.0, 20.0), |
1509 | | curveto(10.0, 20.0, 40.0, 60.0, 60.0, 80.0), |
1510 | | curveto(80.0, 100.0, -10.0, 160.0, 150.0, 180.0)], |
1511 | | None, |
1512 | | ); |
1513 | | } |
1514 | | |
1515 | | #[test] |
1516 | | fn handles_quadratic_curveto() { |
1517 | | test_parser( |
1518 | | "M10 20 Q30 40 50 60", |
1519 | | "", |
1520 | | &[moveto(10.0, 20.0), |
1521 | | curveto( |
1522 | | 70.0 / 3.0, |
1523 | | 100.0 / 3.0, |
1524 | | 110.0 / 3.0, |
1525 | | 140.0 / 3.0, |
1526 | | 50.0, |
1527 | | 60.0, |
1528 | | )], |
1529 | | None, |
1530 | | ); |
1531 | | |
1532 | | test_parser( |
1533 | | "M10 20 Q30 40 50 60,70,80-90 100", |
1534 | | "", |
1535 | | &[moveto(10.0, 20.0), |
1536 | | curveto( |
1537 | | 70.0 / 3.0, |
1538 | | 100.0 / 3.0, |
1539 | | 110.0 / 3.0, |
1540 | | 140.0 / 3.0, |
1541 | | 50.0, |
1542 | | 60.0, |
1543 | | ), |
1544 | | curveto( |
1545 | | 190.0 / 3.0, |
1546 | | 220.0 / 3.0, |
1547 | | 50.0 / 3.0, |
1548 | | 260.0 / 3.0, |
1549 | | -90.0, |
1550 | | 100.0, |
1551 | | )], |
1552 | | None, |
1553 | | ); |
1554 | | |
1555 | | test_parser( |
1556 | | "m10 20 q 30,40 50 60-70,80 90 100", |
1557 | | "", |
1558 | | &[moveto(10.0, 20.0), |
1559 | | curveto( |
1560 | | 90.0 / 3.0, |
1561 | | 140.0 / 3.0, |
1562 | | 140.0 / 3.0, |
1563 | | 200.0 / 3.0, |
1564 | | 60.0, |
1565 | | 80.0, |
1566 | | ), |
1567 | | curveto( |
1568 | | 40.0 / 3.0, |
1569 | | 400.0 / 3.0, |
1570 | | 130.0 / 3.0, |
1571 | | 500.0 / 3.0, |
1572 | | 150.0, |
1573 | | 180.0, |
1574 | | )], |
1575 | | None, |
1576 | | ); |
1577 | | } |
1578 | | |
1579 | | #[test] |
1580 | | fn handles_smooth_quadratic_curveto() { |
1581 | | test_parser( |
1582 | | "M10 20 T30 40", |
1583 | | "", |
1584 | | &[moveto(10.0, 20.0), |
1585 | | curveto(10.0, 20.0, 50.0 / 3.0, 80.0 / 3.0, 30.0, 40.0)], |
1586 | | None, |
1587 | | ); |
1588 | | |
1589 | | test_parser( |
1590 | | "M10 20 Q30 40 50 60 T70 80", |
1591 | | "", |
1592 | | &[moveto(10.0, 20.0), |
1593 | | curveto( |
1594 | | 70.0 / 3.0, |
1595 | | 100.0 / 3.0, |
1596 | | 110.0 / 3.0, |
1597 | | 140.0 / 3.0, |
1598 | | 50.0, |
1599 | | 60.0, |
1600 | | ), |
1601 | | curveto(190.0 / 3.0, 220.0 / 3.0, 70.0, 80.0, 70.0, 80.0)], |
1602 | | None, |
1603 | | ); |
1604 | | |
1605 | | test_parser( |
1606 | | "m10 20 q 30,40 50 60t-70,80", |
1607 | | "", |
1608 | | &[moveto(10.0, 20.0), |
1609 | | curveto( |
1610 | | 90.0 / 3.0, |
1611 | | 140.0 / 3.0, |
1612 | | 140.0 / 3.0, |
1613 | | 200.0 / 3.0, |
1614 | | 60.0, |
1615 | | 80.0, |
1616 | | ), |
1617 | | curveto(220.0 / 3.0, 280.0 / 3.0, 50.0, 120.0, -10.0, 160.0)], |
1618 | | None, |
1619 | | ); |
1620 | | } |
1621 | | |
1622 | | #[test] |
1623 | | fn handles_elliptical_arc() { |
1624 | | // no space required between arc flags |
1625 | | test_parser("M 1 2 A 1 2 3 00 6 7", |
1626 | | "", |
1627 | | &[moveto(1.0, 2.0), |
1628 | | arc(1.0, 2.0, 3.0, false, false, 1.0, 2.0, 6.0, 7.0)], |
1629 | | None); |
1630 | | // or after... |
1631 | | test_parser("M 1 2 A 1 2 3 016 7", |
1632 | | "", |
1633 | | &[moveto(1.0, 2.0), |
1634 | | arc(1.0, 2.0, 3.0, false, true, 1.0, 2.0, 6.0, 7.0)], |
1635 | | None); |
1636 | | // commas and whitespace are optionally allowed |
1637 | | test_parser("M 1 2 A 1 2 3 10,6 7", |
1638 | | "", |
1639 | | &[moveto(1.0, 2.0), |
1640 | | arc(1.0, 2.0, 3.0, true, false, 1.0, 2.0, 6.0, 7.0)], |
1641 | | None); |
1642 | | test_parser("M 1 2 A 1 2 3 1,16, 7", |
1643 | | "", |
1644 | | &[moveto(1.0, 2.0), |
1645 | | arc(1.0, 2.0, 3.0, true, true, 1.0, 2.0, 6.0, 7.0)], |
1646 | | None); |
1647 | | test_parser("M 1 2 A 1 2 3 1,1 6 7", |
1648 | | "", |
1649 | | &[moveto(1.0, 2.0), |
1650 | | arc(1.0, 2.0, 3.0, true, true, 1.0, 2.0, 6.0, 7.0)], |
1651 | | None); |
1652 | | test_parser("M 1 2 A 1 2 3 1 1 6 7", |
1653 | | "", |
1654 | | &[moveto(1.0, 2.0), |
1655 | | arc(1.0, 2.0, 3.0, true, true, 1.0, 2.0, 6.0, 7.0)], |
1656 | | None); |
1657 | | test_parser("M 1 2 A 1 2 3 1 16 7", |
1658 | | "", |
1659 | | &[moveto(1.0, 2.0), |
1660 | | arc(1.0, 2.0, 3.0, true, true, 1.0, 2.0, 6.0, 7.0)], |
1661 | | None); |
1662 | | } |
1663 | | |
1664 | | #[test] |
1665 | | fn handles_close_path() { |
1666 | | test_parser("M10 20 Z", "", &[moveto(10.0, 20.0), closepath()], None); |
1667 | | |
1668 | | test_parser( |
1669 | | "m10 20 30 40 m 50 60 70 80 90 100z", |
1670 | | "", |
1671 | | &vec![ |
1672 | | moveto(10.0, 20.0), |
1673 | | lineto(40.0, 60.0), |
1674 | | moveto(90.0, 120.0), |
1675 | | lineto(160.0, 200.0), |
1676 | | lineto(250.0, 300.0), |
1677 | | closepath(), |
1678 | | ], |
1679 | | None, |
1680 | | ); |
1681 | | } |
1682 | | |
1683 | | #[test] |
1684 | | fn first_command_must_be_moveto() { |
1685 | | test_parser( |
1686 | | " L10 20", |
1687 | | " ^", // FIXME: why is this not at position 2? |
1688 | | &[], |
1689 | | Some(ErrorKind::UnexpectedCommand(b'L')), |
1690 | | ); |
1691 | | } |
1692 | | |
1693 | | #[test] |
1694 | | fn moveto_args() { |
1695 | | test_parser( |
1696 | | "M", |
1697 | | " ^", |
1698 | | &[], |
1699 | | Some(ErrorKind::UnexpectedEof), |
1700 | | ); |
1701 | | |
1702 | | test_parser( |
1703 | | "M,", |
1704 | | " ^", |
1705 | | &[], |
1706 | | Some(ErrorKind::UnexpectedToken(Comma)), |
1707 | | ); |
1708 | | |
1709 | | test_parser( |
1710 | | "M10", |
1711 | | " ^", |
1712 | | &[], |
1713 | | Some(ErrorKind::UnexpectedEof), |
1714 | | ); |
1715 | | |
1716 | | test_parser( |
1717 | | "M10,", |
1718 | | " ^", |
1719 | | &[], |
1720 | | Some(ErrorKind::UnexpectedEof), |
1721 | | ); |
1722 | | |
1723 | | test_parser( |
1724 | | "M10x", |
1725 | | " ^", |
1726 | | &[], |
1727 | | Some(ErrorKind::UnexpectedToken(Command(b'x'))), |
1728 | | ); |
1729 | | |
1730 | | test_parser( |
1731 | | "M10,x", |
1732 | | " ^", |
1733 | | &[], |
1734 | | Some(ErrorKind::UnexpectedToken(Command(b'x'))), |
1735 | | ); |
1736 | | } |
1737 | | |
1738 | | #[test] |
1739 | | fn moveto_implicit_lineto_args() { |
1740 | | test_parser( |
1741 | | "M10-20,", |
1742 | | " ^", |
1743 | | &[moveto(10.0, -20.0)], |
1744 | | Some(ErrorKind::UnexpectedEof), |
1745 | | ); |
1746 | | |
1747 | | test_parser( |
1748 | | "M10-20-30", |
1749 | | " ^", |
1750 | | &[moveto(10.0, -20.0)], |
1751 | | Some(ErrorKind::UnexpectedEof), |
1752 | | ); |
1753 | | |
1754 | | test_parser( |
1755 | | "M10-20-30 x", |
1756 | | " ^", |
1757 | | &[moveto(10.0, -20.0)], |
1758 | | Some(ErrorKind::UnexpectedToken(Command(b'x'))), |
1759 | | ); |
1760 | | } |
1761 | | |
1762 | | #[test] |
1763 | | fn closepath_no_args() { |
1764 | | test_parser( |
1765 | | "M10-20z10", |
1766 | | " ^", |
1767 | | &[moveto(10.0, -20.0), closepath()], |
1768 | | Some(ErrorKind::UnexpectedToken(Number(10.0))), |
1769 | | ); |
1770 | | |
1771 | | test_parser( |
1772 | | "M10-20z,", |
1773 | | " ^", |
1774 | | &[moveto(10.0, -20.0), closepath()], |
1775 | | Some(ErrorKind::UnexpectedToken(Comma)), |
1776 | | ); |
1777 | | } |
1778 | | |
1779 | | #[test] |
1780 | | fn lineto_args() { |
1781 | | test_parser( |
1782 | | "M10-20L10", |
1783 | | " ^", |
1784 | | &[moveto(10.0, -20.0)], |
1785 | | Some(ErrorKind::UnexpectedEof), |
1786 | | ); |
1787 | | |
1788 | | test_parser( |
1789 | | "M 10,10 L 20,20,30", |
1790 | | " ^", |
1791 | | &[moveto(10.0, 10.0), lineto(20.0, 20.0)], |
1792 | | Some(ErrorKind::UnexpectedEof), |
1793 | | ); |
1794 | | |
1795 | | test_parser( |
1796 | | "M 10,10 L 20,20,", |
1797 | | " ^", |
1798 | | &[moveto(10.0, 10.0), lineto(20.0, 20.0)], |
1799 | | Some(ErrorKind::UnexpectedEof), |
1800 | | ); |
1801 | | } |
1802 | | |
1803 | | #[test] |
1804 | | fn horizontal_lineto_args() { |
1805 | | test_parser( |
1806 | | "M10-20H", |
1807 | | " ^", |
1808 | | &[moveto(10.0, -20.0)], |
1809 | | Some(ErrorKind::UnexpectedEof), |
1810 | | ); |
1811 | | |
1812 | | test_parser( |
1813 | | "M10-20H,", |
1814 | | " ^", |
1815 | | &[moveto(10.0, -20.0)], |
1816 | | Some(ErrorKind::UnexpectedToken(Comma)), |
1817 | | ); |
1818 | | |
1819 | | test_parser( |
1820 | | "M10-20H30,", |
1821 | | " ^", |
1822 | | &[moveto(10.0, -20.0), lineto(30.0, -20.0)], |
1823 | | Some(ErrorKind::UnexpectedEof), |
1824 | | ); |
1825 | | } |
1826 | | |
1827 | | #[test] |
1828 | | fn vertical_lineto_args() { |
1829 | | test_parser( |
1830 | | "M10-20v", |
1831 | | " ^", |
1832 | | &[moveto(10.0, -20.0)], |
1833 | | Some(ErrorKind::UnexpectedEof), |
1834 | | ); |
1835 | | |
1836 | | test_parser( |
1837 | | "M10-20v,", |
1838 | | " ^", |
1839 | | &[moveto(10.0, -20.0)], |
1840 | | Some(ErrorKind::UnexpectedToken(Comma)), |
1841 | | ); |
1842 | | |
1843 | | test_parser( |
1844 | | "M10-20v30,", |
1845 | | " ^", |
1846 | | &[moveto(10.0, -20.0), lineto(10.0, 10.0)], |
1847 | | Some(ErrorKind::UnexpectedEof), |
1848 | | ); |
1849 | | } |
1850 | | |
1851 | | #[test] |
1852 | | fn curveto_args() { |
1853 | | test_parser( |
1854 | | "M10-20C1", |
1855 | | " ^", |
1856 | | &[moveto(10.0, -20.0)], |
1857 | | Some(ErrorKind::UnexpectedEof), |
1858 | | ); |
1859 | | test_parser( |
1860 | | "M10-20C1,", |
1861 | | " ^", |
1862 | | &[moveto(10.0, -20.0)], |
1863 | | Some(ErrorKind::UnexpectedEof), |
1864 | | ); |
1865 | | |
1866 | | test_parser( |
1867 | | "M10-20C1 2", |
1868 | | " ^", |
1869 | | &[moveto(10.0, -20.0)], |
1870 | | Some(ErrorKind::UnexpectedEof), |
1871 | | ); |
1872 | | test_parser( |
1873 | | "M10-20C1,2,", |
1874 | | " ^", |
1875 | | &[moveto(10.0, -20.0)], |
1876 | | Some(ErrorKind::UnexpectedEof), |
1877 | | ); |
1878 | | |
1879 | | test_parser( |
1880 | | "M10-20C1 2 3", |
1881 | | " ^", |
1882 | | &[moveto(10.0, -20.0)], |
1883 | | Some(ErrorKind::UnexpectedEof), |
1884 | | ); |
1885 | | test_parser( |
1886 | | "M10-20C1,2,3", |
1887 | | " ^", |
1888 | | &[moveto(10.0, -20.0)], |
1889 | | Some(ErrorKind::UnexpectedEof), |
1890 | | ); |
1891 | | test_parser( |
1892 | | "M10-20C1,2,3,", |
1893 | | " ^", |
1894 | | &[moveto(10.0, -20.0)], |
1895 | | Some(ErrorKind::UnexpectedEof), |
1896 | | ); |
1897 | | |
1898 | | test_parser( |
1899 | | "M10-20C1 2 3 4", |
1900 | | " ^", |
1901 | | &[moveto(10.0, -20.0)], |
1902 | | Some(ErrorKind::UnexpectedEof), |
1903 | | ); |
1904 | | test_parser( |
1905 | | "M10-20C1,2,3,4", |
1906 | | " ^", |
1907 | | &[moveto(10.0, -20.0)], |
1908 | | Some(ErrorKind::UnexpectedEof), |
1909 | | ); |
1910 | | test_parser( |
1911 | | "M10-20C1,2,3,4,", |
1912 | | " ^", |
1913 | | &[moveto(10.0, -20.0)], |
1914 | | Some(ErrorKind::UnexpectedEof), |
1915 | | ); |
1916 | | |
1917 | | test_parser( |
1918 | | "M10-20C1 2 3 4 5", |
1919 | | " ^", |
1920 | | &[moveto(10.0, -20.0)], |
1921 | | Some(ErrorKind::UnexpectedEof), |
1922 | | ); |
1923 | | test_parser( |
1924 | | "M10-20C1,2,3,4,5", |
1925 | | " ^", |
1926 | | &[moveto(10.0, -20.0)], |
1927 | | Some(ErrorKind::UnexpectedEof), |
1928 | | ); |
1929 | | test_parser( |
1930 | | "M10-20C1,2,3,4,5,", |
1931 | | " ^", |
1932 | | &[moveto(10.0, -20.0)], |
1933 | | Some(ErrorKind::UnexpectedEof), |
1934 | | ); |
1935 | | |
1936 | | test_parser( |
1937 | | "M10-20C1,2,3,4,5,6,", |
1938 | | " ^", |
1939 | | &[moveto(10.0, -20.0), curveto(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)], |
1940 | | Some(ErrorKind::UnexpectedEof), |
1941 | | ); |
1942 | | } |
1943 | | |
1944 | | #[test] |
1945 | | fn smooth_curveto_args() { |
1946 | | test_parser( |
1947 | | "M10-20S1", |
1948 | | " ^", |
1949 | | &[moveto(10.0, -20.0)], |
1950 | | Some(ErrorKind::UnexpectedEof), |
1951 | | ); |
1952 | | test_parser( |
1953 | | "M10-20S1,", |
1954 | | " ^", |
1955 | | &[moveto(10.0, -20.0)], |
1956 | | Some(ErrorKind::UnexpectedEof), |
1957 | | ); |
1958 | | |
1959 | | test_parser( |
1960 | | "M10-20S1 2", |
1961 | | " ^", |
1962 | | &[moveto(10.0, -20.0)], |
1963 | | Some(ErrorKind::UnexpectedEof), |
1964 | | ); |
1965 | | test_parser( |
1966 | | "M10-20S1,2,", |
1967 | | " ^", |
1968 | | &[moveto(10.0, -20.0)], |
1969 | | Some(ErrorKind::UnexpectedEof), |
1970 | | ); |
1971 | | |
1972 | | test_parser( |
1973 | | "M10-20S1 2 3", |
1974 | | " ^", |
1975 | | &[moveto(10.0, -20.0)], |
1976 | | Some(ErrorKind::UnexpectedEof), |
1977 | | ); |
1978 | | test_parser( |
1979 | | "M10-20S1,2,3", |
1980 | | " ^", |
1981 | | &[moveto(10.0, -20.0)], |
1982 | | Some(ErrorKind::UnexpectedEof), |
1983 | | ); |
1984 | | test_parser( |
1985 | | "M10-20S1,2,3,", |
1986 | | " ^", |
1987 | | &[moveto(10.0, -20.0)], |
1988 | | Some(ErrorKind::UnexpectedEof), |
1989 | | ); |
1990 | | |
1991 | | test_parser( |
1992 | | "M10-20S1,2,3,4,", |
1993 | | " ^", |
1994 | | &[moveto(10.0, -20.0), |
1995 | | curveto(10.0, -20.0, 1.0, 2.0, 3.0, 4.0)], |
1996 | | Some(ErrorKind::UnexpectedEof), |
1997 | | ); |
1998 | | } |
1999 | | |
2000 | | #[test] |
2001 | | fn quadratic_bezier_curveto_args() { |
2002 | | test_parser( |
2003 | | "M10-20Q1", |
2004 | | " ^", |
2005 | | &[moveto(10.0, -20.0)], |
2006 | | Some(ErrorKind::UnexpectedEof), |
2007 | | ); |
2008 | | test_parser( |
2009 | | "M10-20Q1,", |
2010 | | " ^", |
2011 | | &[moveto(10.0, -20.0)], |
2012 | | Some(ErrorKind::UnexpectedEof), |
2013 | | ); |
2014 | | |
2015 | | test_parser( |
2016 | | "M10-20Q1 2", |
2017 | | " ^", |
2018 | | &[moveto(10.0, -20.0)], |
2019 | | Some(ErrorKind::UnexpectedEof), |
2020 | | ); |
2021 | | test_parser( |
2022 | | "M10-20Q1,2,", |
2023 | | " ^", |
2024 | | &[moveto(10.0, -20.0)], |
2025 | | Some(ErrorKind::UnexpectedEof), |
2026 | | ); |
2027 | | |
2028 | | test_parser( |
2029 | | "M10-20Q1 2 3", |
2030 | | " ^", |
2031 | | &[moveto(10.0, -20.0)], |
2032 | | Some(ErrorKind::UnexpectedEof), |
2033 | | ); |
2034 | | test_parser( |
2035 | | "M10-20Q1,2,3", |
2036 | | " ^", |
2037 | | &[moveto(10.0, -20.0)], |
2038 | | Some(ErrorKind::UnexpectedEof), |
2039 | | ); |
2040 | | test_parser( |
2041 | | "M10-20Q1,2,3,", |
2042 | | " ^", |
2043 | | &[moveto(10.0, -20.0)], |
2044 | | Some(ErrorKind::UnexpectedEof), |
2045 | | ); |
2046 | | |
2047 | | test_parser( |
2048 | | "M10 20 Q30 40 50 60,", |
2049 | | " ^", |
2050 | | &[moveto(10.0, 20.0), |
2051 | | curveto( |
2052 | | 70.0 / 3.0, |
2053 | | 100.0 / 3.0, |
2054 | | 110.0 / 3.0, |
2055 | | 140.0 / 3.0, |
2056 | | 50.0, |
2057 | | 60.0, |
2058 | | )], |
2059 | | Some(ErrorKind::UnexpectedEof), |
2060 | | ); |
2061 | | } |
2062 | | |
2063 | | #[test] |
2064 | | fn smooth_quadratic_bezier_curveto_args() { |
2065 | | test_parser( |
2066 | | "M10-20T1", |
2067 | | " ^", |
2068 | | &[moveto(10.0, -20.0)], |
2069 | | Some(ErrorKind::UnexpectedEof), |
2070 | | ); |
2071 | | test_parser( |
2072 | | "M10-20T1,", |
2073 | | " ^", |
2074 | | &[moveto(10.0, -20.0)], |
2075 | | Some(ErrorKind::UnexpectedEof), |
2076 | | ); |
2077 | | |
2078 | | test_parser( |
2079 | | "M10 20 T30 40,", |
2080 | | " ^", |
2081 | | &[moveto(10.0, 20.0), |
2082 | | curveto(10.0, 20.0, 50.0 / 3.0, 80.0 / 3.0, 30.0, 40.0)], |
2083 | | Some(ErrorKind::UnexpectedEof), |
2084 | | ); |
2085 | | } |
2086 | | |
2087 | | #[test] |
2088 | | fn elliptical_arc_args() { |
2089 | | test_parser( |
2090 | | "M10-20A1", |
2091 | | " ^", |
2092 | | &[moveto(10.0, -20.0)], |
2093 | | Some(ErrorKind::UnexpectedEof), |
2094 | | ); |
2095 | | test_parser( |
2096 | | "M10-20A1,", |
2097 | | " ^", |
2098 | | &[moveto(10.0, -20.0)], |
2099 | | Some(ErrorKind::UnexpectedEof), |
2100 | | ); |
2101 | | |
2102 | | test_parser( |
2103 | | "M10-20A1 2", |
2104 | | " ^", |
2105 | | &[moveto(10.0, -20.0)], |
2106 | | Some(ErrorKind::UnexpectedEof), |
2107 | | ); |
2108 | | test_parser( |
2109 | | "M10-20A1 2,", |
2110 | | " ^", |
2111 | | &[moveto(10.0, -20.0)], |
2112 | | Some(ErrorKind::UnexpectedEof), |
2113 | | ); |
2114 | | |
2115 | | test_parser( |
2116 | | "M10-20A1 2 3", |
2117 | | " ^", |
2118 | | &[moveto(10.0, -20.0)], |
2119 | | Some(ErrorKind::UnexpectedEof), |
2120 | | ); |
2121 | | test_parser( |
2122 | | "M10-20A1 2 3,", |
2123 | | " ^", |
2124 | | &[moveto(10.0, -20.0)], |
2125 | | Some(ErrorKind::UnexpectedEof), |
2126 | | ); |
2127 | | |
2128 | | test_parser( |
2129 | | "M10-20A1 2 3 4", |
2130 | | " ^", |
2131 | | &[moveto(10.0, -20.0)], |
2132 | | Some(ErrorKind::LexError(LexError::UnexpectedByte(b'4'))), |
2133 | | ); |
2134 | | |
2135 | | test_parser( |
2136 | | "M10-20A1 2 3 1", |
2137 | | " ^", |
2138 | | &[moveto(10.0, -20.0)], |
2139 | | Some(ErrorKind::UnexpectedEof), |
2140 | | ); |
2141 | | test_parser( |
2142 | | "M10-20A1 2 3,1,", |
2143 | | " ^", |
2144 | | &[moveto(10.0, -20.0)], |
2145 | | Some(ErrorKind::UnexpectedEof), |
2146 | | ); |
2147 | | |
2148 | | test_parser( |
2149 | | "M10-20A1 2 3 1 5", |
2150 | | " ^", |
2151 | | &[moveto(10.0, -20.0)], |
2152 | | Some(ErrorKind::LexError(LexError::UnexpectedByte(b'5'))), |
2153 | | ); |
2154 | | |
2155 | | test_parser( |
2156 | | "M10-20A1 2 3 1 1", |
2157 | | " ^", |
2158 | | &[moveto(10.0, -20.0)], |
2159 | | Some(ErrorKind::UnexpectedEof), |
2160 | | ); |
2161 | | test_parser( |
2162 | | "M10-20A1 2 3,1,1,", |
2163 | | " ^", |
2164 | | &[moveto(10.0, -20.0)], |
2165 | | Some(ErrorKind::UnexpectedEof), |
2166 | | ); |
2167 | | |
2168 | | test_parser( |
2169 | | "M10-20A1 2 3 1 1 6", |
2170 | | " ^", |
2171 | | &[moveto(10.0, -20.0)], |
2172 | | Some(ErrorKind::UnexpectedEof), |
2173 | | ); |
2174 | | test_parser( |
2175 | | "M10-20A1 2 3,1,1,6,", |
2176 | | " ^", |
2177 | | &[moveto(10.0, -20.0)], |
2178 | | Some(ErrorKind::UnexpectedEof), |
2179 | | ); |
2180 | | |
2181 | | // no non 0|1 chars allowed for flags |
2182 | | test_parser("M 1 2 A 1 2 3 1.0 0.0 6 7", |
2183 | | " ^", |
2184 | | &[moveto(1.0, 2.0)], |
2185 | | Some(ErrorKind::UnexpectedToken(Number(0.0)))); |
2186 | | |
2187 | | test_parser("M10-20A1 2 3,1,1,6,7,", |
2188 | | " ^", |
2189 | | &[moveto(10.0, -20.0), |
2190 | | arc(1.0, 2.0, 3.0, true, true, 10.0, -20.0, 6.0, 7.0)], |
2191 | | Some(ErrorKind::UnexpectedEof)); |
2192 | | } |
2193 | | |
2194 | | #[test] |
2195 | | fn bugs() { |
2196 | | // https://gitlab.gnome.org/GNOME/librsvg/issues/345 |
2197 | | test_parser( |
2198 | | "M.. 1,0 0,100000", |
2199 | | " ^", // FIXME: we have to report position of error in lexer errors to make this right |
2200 | | &[], |
2201 | | Some(ErrorKind::LexError(LexError::UnexpectedByte(b'.'))), |
2202 | | ); |
2203 | | } |
2204 | | } |