Coverage Report

Created: 2026-03-28 07:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}