1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5 //! Specified types for SVG Path.
6
7 use crate::parser::{Parse, ParserContext};
8 use crate::values::animated::{Animate, Procedure, ToAnimatedZero};
9 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
10 use crate::values::CSSFloat;
11 use cssparser::Parser;
12 use std::fmt::{self, Write};
13 use std::iter::{Cloned, Peekable};
14 use std::slice;
15 use style_traits::values::SequenceWriter;
16 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
17
18 /// The SVG path data.
19 ///
20 /// https://www.w3.org/TR/SVG11/paths.html#PathData
21 #[derive(
22 Clone,
23 Debug,
24 Deserialize,
25 MallocSizeOf,
26 PartialEq,
27 Serialize,
28 SpecifiedValueInfo,
29 ToAnimatedZero,
30 ToComputedValue,
31 ToResolvedValue,
32 ToShmem,
33 )]
34 #[repr(C)]
35 pub struct SVGPathData(
36 // TODO(emilio): Should probably measure this somehow only from the
37 // specified values.
38 #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>,
39 );
40
41 impl SVGPathData {
42 /// Get the array of PathCommand.
43 #[inline]
commands(&self) -> &[PathCommand]44 pub fn commands(&self) -> &[PathCommand] {
45 &self.0
46 }
47
48 /// Create a normalized copy of this path by converting each relative
49 /// command to an absolute command.
normalize(&self) -> Self50 pub fn normalize(&self) -> Self {
51 let mut state = PathTraversalState {
52 subpath_start: CoordPair::new(0.0, 0.0),
53 pos: CoordPair::new(0.0, 0.0),
54 };
55 let result = self
56 .0
57 .iter()
58 .map(|seg| seg.normalize(&mut state))
59 .collect::<Vec<_>>();
60
61 SVGPathData(crate::ArcSlice::from_iter(result.into_iter()))
62 }
63
64 // FIXME: Bug 1714238, we may drop this once we use the same data structure for both SVG and
65 // CSS.
66 /// Decode the svg path raw data from Gecko.
67 #[cfg(feature = "gecko")]
decode_from_f32_array(path: &[f32]) -> Result<Self, ()>68 pub fn decode_from_f32_array(path: &[f32]) -> Result<Self, ()> {
69 use crate::gecko_bindings::structs::dom::SVGPathSeg_Binding::*;
70
71 let mut result: Vec<PathCommand> = Vec::new();
72 let mut i: usize = 0;
73 while i < path.len() {
74 // See EncodeType() and DecodeType() in SVGPathSegUtils.h.
75 // We are using reinterpret_cast<> to encode and decode between u32 and f32, so here we
76 // use to_bits() to decode the type.
77 let seg_type = path[i].to_bits() as u16;
78 i = i + 1;
79 match seg_type {
80 PATHSEG_CLOSEPATH => result.push(PathCommand::ClosePath),
81 PATHSEG_MOVETO_ABS | PATHSEG_MOVETO_REL => {
82 debug_assert!(i + 1 < path.len());
83 result.push(PathCommand::MoveTo {
84 point: CoordPair::new(path[i], path[i + 1]),
85 absolute: IsAbsolute::new(seg_type == PATHSEG_MOVETO_ABS),
86 });
87 i = i + 2;
88 },
89 PATHSEG_LINETO_ABS | PATHSEG_LINETO_REL => {
90 debug_assert!(i + 1 < path.len());
91 result.push(PathCommand::LineTo {
92 point: CoordPair::new(path[i], path[i + 1]),
93 absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_ABS),
94 });
95 i = i + 2;
96 },
97 PATHSEG_CURVETO_CUBIC_ABS | PATHSEG_CURVETO_CUBIC_REL => {
98 debug_assert!(i + 5 < path.len());
99 result.push(PathCommand::CurveTo {
100 control1: CoordPair::new(path[i], path[i + 1]),
101 control2: CoordPair::new(path[i + 2], path[i + 3]),
102 point: CoordPair::new(path[i + 4], path[i + 5]),
103 absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS),
104 });
105 i = i + 6;
106 },
107 PATHSEG_CURVETO_QUADRATIC_ABS | PATHSEG_CURVETO_QUADRATIC_REL => {
108 debug_assert!(i + 3 < path.len());
109 result.push(PathCommand::QuadBezierCurveTo {
110 control1: CoordPair::new(path[i], path[i + 1]),
111 point: CoordPair::new(path[i + 2], path[i + 3]),
112 absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS),
113 });
114 i = i + 4;
115 },
116 PATHSEG_ARC_ABS | PATHSEG_ARC_REL => {
117 debug_assert!(i + 6 < path.len());
118 result.push(PathCommand::EllipticalArc {
119 rx: path[i],
120 ry: path[i + 1],
121 angle: path[i + 2],
122 large_arc_flag: ArcFlag(path[i + 3] != 0.0f32),
123 sweep_flag: ArcFlag(path[i + 4] != 0.0f32),
124 point: CoordPair::new(path[i + 5], path[i + 6]),
125 absolute: IsAbsolute::new(seg_type == PATHSEG_ARC_ABS),
126 });
127 i = i + 7;
128 },
129 PATHSEG_LINETO_HORIZONTAL_ABS | PATHSEG_LINETO_HORIZONTAL_REL => {
130 debug_assert!(i < path.len());
131 result.push(PathCommand::HorizontalLineTo {
132 x: path[i],
133 absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS),
134 });
135 i = i + 1;
136 },
137 PATHSEG_LINETO_VERTICAL_ABS | PATHSEG_LINETO_VERTICAL_REL => {
138 debug_assert!(i < path.len());
139 result.push(PathCommand::VerticalLineTo {
140 y: path[i],
141 absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS),
142 });
143 i = i + 1;
144 },
145 PATHSEG_CURVETO_CUBIC_SMOOTH_ABS | PATHSEG_CURVETO_CUBIC_SMOOTH_REL => {
146 debug_assert!(i + 3 < path.len());
147 result.push(PathCommand::SmoothCurveTo {
148 control2: CoordPair::new(path[i], path[i + 1]),
149 point: CoordPair::new(path[i + 2], path[i + 3]),
150 absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS),
151 });
152 i = i + 4;
153 },
154 PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS | PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL => {
155 debug_assert!(i + 1 < path.len());
156 result.push(PathCommand::SmoothQuadBezierCurveTo {
157 point: CoordPair::new(path[i], path[i + 1]),
158 absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS),
159 });
160 i = i + 2;
161 },
162 PATHSEG_UNKNOWN | _ => return Err(()),
163 }
164 }
165
166 Ok(SVGPathData(crate::ArcSlice::from_iter(result.into_iter())))
167 }
168 }
169
170 impl ToCss for SVGPathData {
171 #[inline]
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,172 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
173 where
174 W: fmt::Write,
175 {
176 dest.write_char('"')?;
177 {
178 let mut writer = SequenceWriter::new(dest, " ");
179 for command in self.commands() {
180 writer.item(command)?;
181 }
182 }
183 dest.write_char('"')
184 }
185 }
186
187 impl Parse for SVGPathData {
188 // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make
189 // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.)
190 // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident
191 // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable
192 // str::Char iterator to check each character.
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>193 fn parse<'i, 't>(
194 _context: &ParserContext,
195 input: &mut Parser<'i, 't>,
196 ) -> Result<Self, ParseError<'i>> {
197 let location = input.current_source_location();
198 let path_string = input.expect_string()?.as_ref();
199
200 // Parse the svg path string as multiple sub-paths.
201 let mut path_parser = PathParser::new(path_string);
202 while skip_wsp(&mut path_parser.chars) {
203 if path_parser.parse_subpath().is_err() {
204 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
205 }
206 }
207
208 Ok(SVGPathData(crate::ArcSlice::from_iter(
209 path_parser.path.into_iter(),
210 )))
211 }
212 }
213
214 impl Animate for SVGPathData {
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>215 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
216 if self.0.len() != other.0.len() {
217 return Err(());
218 }
219
220 // FIXME(emilio): This allocates three copies of the path, that's not
221 // great! Specially, once we're normalized once, we don't need to
222 // re-normalize again.
223 let result = self
224 .normalize()
225 .0
226 .iter()
227 .zip(other.normalize().0.iter())
228 .map(|(a, b)| a.animate(&b, procedure))
229 .collect::<Result<Vec<_>, _>>()?;
230
231 Ok(SVGPathData(crate::ArcSlice::from_iter(result.into_iter())))
232 }
233 }
234
235 impl ComputeSquaredDistance for SVGPathData {
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>236 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
237 if self.0.len() != other.0.len() {
238 return Err(());
239 }
240 self.normalize()
241 .0
242 .iter()
243 .zip(other.normalize().0.iter())
244 .map(|(this, other)| this.compute_squared_distance(&other))
245 .sum()
246 }
247 }
248
249 /// The SVG path command.
250 /// The fields of these commands are self-explanatory, so we skip the documents.
251 /// Note: the index of the control points, e.g. control1, control2, are mapping to the control
252 /// points of the Bézier curve in the spec.
253 ///
254 /// https://www.w3.org/TR/SVG11/paths.html#PathData
255 #[derive(
256 Animate,
257 Clone,
258 ComputeSquaredDistance,
259 Copy,
260 Debug,
261 Deserialize,
262 MallocSizeOf,
263 PartialEq,
264 Serialize,
265 SpecifiedValueInfo,
266 ToAnimatedZero,
267 ToComputedValue,
268 ToResolvedValue,
269 ToShmem,
270 )]
271 #[allow(missing_docs)]
272 #[repr(C, u8)]
273 pub enum PathCommand {
274 /// The unknown type.
275 /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN
276 Unknown,
277 /// The "moveto" command.
278 MoveTo {
279 point: CoordPair,
280 absolute: IsAbsolute,
281 },
282 /// The "lineto" command.
283 LineTo {
284 point: CoordPair,
285 absolute: IsAbsolute,
286 },
287 /// The horizontal "lineto" command.
288 HorizontalLineTo { x: CSSFloat, absolute: IsAbsolute },
289 /// The vertical "lineto" command.
290 VerticalLineTo { y: CSSFloat, absolute: IsAbsolute },
291 /// The cubic Bézier curve command.
292 CurveTo {
293 control1: CoordPair,
294 control2: CoordPair,
295 point: CoordPair,
296 absolute: IsAbsolute,
297 },
298 /// The smooth curve command.
299 SmoothCurveTo {
300 control2: CoordPair,
301 point: CoordPair,
302 absolute: IsAbsolute,
303 },
304 /// The quadratic Bézier curve command.
305 QuadBezierCurveTo {
306 control1: CoordPair,
307 point: CoordPair,
308 absolute: IsAbsolute,
309 },
310 /// The smooth quadratic Bézier curve command.
311 SmoothQuadBezierCurveTo {
312 point: CoordPair,
313 absolute: IsAbsolute,
314 },
315 /// The elliptical arc curve command.
316 EllipticalArc {
317 rx: CSSFloat,
318 ry: CSSFloat,
319 angle: CSSFloat,
320 large_arc_flag: ArcFlag,
321 sweep_flag: ArcFlag,
322 point: CoordPair,
323 absolute: IsAbsolute,
324 },
325 /// The "closepath" command.
326 ClosePath,
327 }
328
329 /// For internal SVGPath normalization.
330 #[allow(missing_docs)]
331 struct PathTraversalState {
332 subpath_start: CoordPair,
333 pos: CoordPair,
334 }
335
336 impl PathCommand {
337 /// Create a normalized copy of this PathCommand. Absolute commands will be copied as-is while
338 /// for relative commands an equivalent absolute command will be returned.
339 ///
340 /// See discussion: https://github.com/w3c/svgwg/issues/321
normalize(&self, state: &mut PathTraversalState) -> Self341 fn normalize(&self, state: &mut PathTraversalState) -> Self {
342 use self::PathCommand::*;
343 match *self {
344 Unknown => Unknown,
345 ClosePath => {
346 state.pos = state.subpath_start;
347 ClosePath
348 },
349 MoveTo {
350 mut point,
351 absolute,
352 } => {
353 if !absolute.is_yes() {
354 point += state.pos;
355 }
356 state.pos = point;
357 state.subpath_start = point;
358 MoveTo {
359 point,
360 absolute: IsAbsolute::Yes,
361 }
362 },
363 LineTo {
364 mut point,
365 absolute,
366 } => {
367 if !absolute.is_yes() {
368 point += state.pos;
369 }
370 state.pos = point;
371 LineTo {
372 point,
373 absolute: IsAbsolute::Yes,
374 }
375 },
376 HorizontalLineTo { mut x, absolute } => {
377 if !absolute.is_yes() {
378 x += state.pos.0;
379 }
380 state.pos.0 = x;
381 HorizontalLineTo {
382 x,
383 absolute: IsAbsolute::Yes,
384 }
385 },
386 VerticalLineTo { mut y, absolute } => {
387 if !absolute.is_yes() {
388 y += state.pos.1;
389 }
390 state.pos.1 = y;
391 VerticalLineTo {
392 y,
393 absolute: IsAbsolute::Yes,
394 }
395 },
396 CurveTo {
397 mut control1,
398 mut control2,
399 mut point,
400 absolute,
401 } => {
402 if !absolute.is_yes() {
403 control1 += state.pos;
404 control2 += state.pos;
405 point += state.pos;
406 }
407 state.pos = point;
408 CurveTo {
409 control1,
410 control2,
411 point,
412 absolute: IsAbsolute::Yes,
413 }
414 },
415 SmoothCurveTo {
416 mut control2,
417 mut point,
418 absolute,
419 } => {
420 if !absolute.is_yes() {
421 control2 += state.pos;
422 point += state.pos;
423 }
424 state.pos = point;
425 SmoothCurveTo {
426 control2,
427 point,
428 absolute: IsAbsolute::Yes,
429 }
430 },
431 QuadBezierCurveTo {
432 mut control1,
433 mut point,
434 absolute,
435 } => {
436 if !absolute.is_yes() {
437 control1 += state.pos;
438 point += state.pos;
439 }
440 state.pos = point;
441 QuadBezierCurveTo {
442 control1,
443 point,
444 absolute: IsAbsolute::Yes,
445 }
446 },
447 SmoothQuadBezierCurveTo {
448 mut point,
449 absolute,
450 } => {
451 if !absolute.is_yes() {
452 point += state.pos;
453 }
454 state.pos = point;
455 SmoothQuadBezierCurveTo {
456 point,
457 absolute: IsAbsolute::Yes,
458 }
459 },
460 EllipticalArc {
461 rx,
462 ry,
463 angle,
464 large_arc_flag,
465 sweep_flag,
466 mut point,
467 absolute,
468 } => {
469 if !absolute.is_yes() {
470 point += state.pos;
471 }
472 state.pos = point;
473 EllipticalArc {
474 rx,
475 ry,
476 angle,
477 large_arc_flag,
478 sweep_flag,
479 point,
480 absolute: IsAbsolute::Yes,
481 }
482 },
483 }
484 }
485 }
486
487 impl ToCss for PathCommand {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,488 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
489 where
490 W: fmt::Write,
491 {
492 use self::PathCommand::*;
493 match *self {
494 Unknown => dest.write_char('X'),
495 ClosePath => dest.write_char('Z'),
496 MoveTo { point, absolute } => {
497 dest.write_char(if absolute.is_yes() { 'M' } else { 'm' })?;
498 dest.write_char(' ')?;
499 point.to_css(dest)
500 },
501 LineTo { point, absolute } => {
502 dest.write_char(if absolute.is_yes() { 'L' } else { 'l' })?;
503 dest.write_char(' ')?;
504 point.to_css(dest)
505 },
506 CurveTo {
507 control1,
508 control2,
509 point,
510 absolute,
511 } => {
512 dest.write_char(if absolute.is_yes() { 'C' } else { 'c' })?;
513 dest.write_char(' ')?;
514 control1.to_css(dest)?;
515 dest.write_char(' ')?;
516 control2.to_css(dest)?;
517 dest.write_char(' ')?;
518 point.to_css(dest)
519 },
520 QuadBezierCurveTo {
521 control1,
522 point,
523 absolute,
524 } => {
525 dest.write_char(if absolute.is_yes() { 'Q' } else { 'q' })?;
526 dest.write_char(' ')?;
527 control1.to_css(dest)?;
528 dest.write_char(' ')?;
529 point.to_css(dest)
530 },
531 EllipticalArc {
532 rx,
533 ry,
534 angle,
535 large_arc_flag,
536 sweep_flag,
537 point,
538 absolute,
539 } => {
540 dest.write_char(if absolute.is_yes() { 'A' } else { 'a' })?;
541 dest.write_char(' ')?;
542 rx.to_css(dest)?;
543 dest.write_char(' ')?;
544 ry.to_css(dest)?;
545 dest.write_char(' ')?;
546 angle.to_css(dest)?;
547 dest.write_char(' ')?;
548 large_arc_flag.to_css(dest)?;
549 dest.write_char(' ')?;
550 sweep_flag.to_css(dest)?;
551 dest.write_char(' ')?;
552 point.to_css(dest)
553 },
554 HorizontalLineTo { x, absolute } => {
555 dest.write_char(if absolute.is_yes() { 'H' } else { 'h' })?;
556 dest.write_char(' ')?;
557 x.to_css(dest)
558 },
559 VerticalLineTo { y, absolute } => {
560 dest.write_char(if absolute.is_yes() { 'V' } else { 'v' })?;
561 dest.write_char(' ')?;
562 y.to_css(dest)
563 },
564 SmoothCurveTo {
565 control2,
566 point,
567 absolute,
568 } => {
569 dest.write_char(if absolute.is_yes() { 'S' } else { 's' })?;
570 dest.write_char(' ')?;
571 control2.to_css(dest)?;
572 dest.write_char(' ')?;
573 point.to_css(dest)
574 },
575 SmoothQuadBezierCurveTo { point, absolute } => {
576 dest.write_char(if absolute.is_yes() { 'T' } else { 't' })?;
577 dest.write_char(' ')?;
578 point.to_css(dest)
579 },
580 }
581 }
582 }
583
584 /// The path command absolute type.
585 #[allow(missing_docs)]
586 #[derive(
587 Animate,
588 Clone,
589 ComputeSquaredDistance,
590 Copy,
591 Debug,
592 Deserialize,
593 MallocSizeOf,
594 PartialEq,
595 Serialize,
596 SpecifiedValueInfo,
597 ToAnimatedZero,
598 ToComputedValue,
599 ToResolvedValue,
600 ToShmem,
601 )]
602 #[repr(u8)]
603 pub enum IsAbsolute {
604 Yes,
605 No,
606 }
607
608 impl IsAbsolute {
609 /// Return true if this is IsAbsolute::Yes.
610 #[inline]
is_yes(&self) -> bool611 pub fn is_yes(&self) -> bool {
612 *self == IsAbsolute::Yes
613 }
614
615 /// Return Yes if value is true. Otherwise, return No.
616 #[inline]
new(value: bool) -> Self617 fn new(value: bool) -> Self {
618 if value {
619 IsAbsolute::Yes
620 } else {
621 IsAbsolute::No
622 }
623 }
624 }
625
626 /// The path coord type.
627 #[derive(
628 AddAssign,
629 Animate,
630 Clone,
631 ComputeSquaredDistance,
632 Copy,
633 Debug,
634 Deserialize,
635 MallocSizeOf,
636 PartialEq,
637 Serialize,
638 SpecifiedValueInfo,
639 ToAnimatedZero,
640 ToComputedValue,
641 ToCss,
642 ToResolvedValue,
643 ToShmem,
644 )]
645 #[repr(C)]
646 pub struct CoordPair(CSSFloat, CSSFloat);
647
648 impl CoordPair {
649 /// Create a CoordPair.
650 #[inline]
new(x: CSSFloat, y: CSSFloat) -> Self651 pub fn new(x: CSSFloat, y: CSSFloat) -> Self {
652 CoordPair(x, y)
653 }
654 }
655
656 /// The EllipticalArc flag type.
657 #[derive(
658 Clone,
659 Copy,
660 Debug,
661 Deserialize,
662 MallocSizeOf,
663 PartialEq,
664 Serialize,
665 SpecifiedValueInfo,
666 ToComputedValue,
667 ToResolvedValue,
668 ToShmem,
669 )]
670 #[repr(C)]
671 pub struct ArcFlag(bool);
672
673 impl ToCss for ArcFlag {
674 #[inline]
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,675 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
676 where
677 W: fmt::Write,
678 {
679 (self.0 as i32).to_css(dest)
680 }
681 }
682
683 impl Animate for ArcFlag {
684 #[inline]
animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>685 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
686 (self.0 as i32)
687 .animate(&(other.0 as i32), procedure)
688 .map(|v| ArcFlag(v > 0))
689 }
690 }
691
692 impl ComputeSquaredDistance for ArcFlag {
693 #[inline]
compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>694 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
695 (self.0 as i32).compute_squared_distance(&(other.0 as i32))
696 }
697 }
698
699 impl ToAnimatedZero for ArcFlag {
700 #[inline]
to_animated_zero(&self) -> Result<Self, ()>701 fn to_animated_zero(&self) -> Result<Self, ()> {
702 // The 2 ArcFlags in EllipticalArc determine which one of the 4 different arcs will be
703 // used. (i.e. From 4 combinations). In other words, if we change the flag, we get a
704 // different arc. Therefore, we return *self.
705 // https://svgwg.org/svg2-draft/paths.html#PathDataEllipticalArcCommands
706 Ok(*self)
707 }
708 }
709
710 /// SVG Path parser.
711 struct PathParser<'a> {
712 chars: Peekable<Cloned<slice::Iter<'a, u8>>>,
713 path: Vec<PathCommand>,
714 }
715
716 macro_rules! parse_arguments {
717 (
718 $parser:ident,
719 $abs:ident,
720 $enum:ident,
721 [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
722 ) => {
723 {
724 loop {
725 let $para = $func(&mut $parser.chars)?;
726 $(
727 skip_comma_wsp(&mut $parser.chars);
728 let $other_para = $other_func(&mut $parser.chars)?;
729 )*
730 $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs });
731
732 // End of string or the next character is a possible new command.
733 if !skip_wsp(&mut $parser.chars) ||
734 $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
735 break;
736 }
737 skip_comma_wsp(&mut $parser.chars);
738 }
739 Ok(())
740 }
741 }
742 }
743
744 impl<'a> PathParser<'a> {
745 /// Return a PathParser.
746 #[inline]
new(string: &'a str) -> Self747 fn new(string: &'a str) -> Self {
748 PathParser {
749 chars: string.as_bytes().iter().cloned().peekable(),
750 path: Vec::new(),
751 }
752 }
753
754 /// Parse a sub-path.
parse_subpath(&mut self) -> Result<(), ()>755 fn parse_subpath(&mut self) -> Result<(), ()> {
756 // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path
757 // (i.e. not a valid moveto-drawto-command-group).
758 self.parse_moveto()?;
759
760 // Handle other commands.
761 loop {
762 skip_wsp(&mut self.chars);
763 if self.chars.peek().map_or(true, |&m| m == b'M' || m == b'm') {
764 break;
765 }
766
767 let command = self.chars.next().unwrap();
768 let abs = if command.is_ascii_uppercase() {
769 IsAbsolute::Yes
770 } else {
771 IsAbsolute::No
772 };
773
774 skip_wsp(&mut self.chars);
775 match command {
776 b'Z' | b'z' => self.parse_closepath(),
777 b'L' | b'l' => self.parse_lineto(abs),
778 b'H' | b'h' => self.parse_h_lineto(abs),
779 b'V' | b'v' => self.parse_v_lineto(abs),
780 b'C' | b'c' => self.parse_curveto(abs),
781 b'S' | b's' => self.parse_smooth_curveto(abs),
782 b'Q' | b'q' => self.parse_quadratic_bezier_curveto(abs),
783 b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(abs),
784 b'A' | b'a' => self.parse_elliptical_arc(abs),
785 _ => return Err(()),
786 }?;
787 }
788 Ok(())
789 }
790
791 /// Parse "moveto" command.
parse_moveto(&mut self) -> Result<(), ()>792 fn parse_moveto(&mut self) -> Result<(), ()> {
793 let command = match self.chars.next() {
794 Some(c) if c == b'M' || c == b'm' => c,
795 _ => return Err(()),
796 };
797
798 skip_wsp(&mut self.chars);
799 let point = parse_coord(&mut self.chars)?;
800 let absolute = if command == b'M' {
801 IsAbsolute::Yes
802 } else {
803 IsAbsolute::No
804 };
805 self.path.push(PathCommand::MoveTo { point, absolute });
806
807 // End of string or the next character is a possible new command.
808 if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic())
809 {
810 return Ok(());
811 }
812 skip_comma_wsp(&mut self.chars);
813
814 // If a moveto is followed by multiple pairs of coordinates, the subsequent
815 // pairs are treated as implicit lineto commands.
816 self.parse_lineto(absolute)
817 }
818
819 /// Parse "closepath" command.
parse_closepath(&mut self) -> Result<(), ()>820 fn parse_closepath(&mut self) -> Result<(), ()> {
821 self.path.push(PathCommand::ClosePath);
822 Ok(())
823 }
824
825 /// Parse "lineto" command.
parse_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()>826 fn parse_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
827 parse_arguments!(self, absolute, LineTo, [ point => parse_coord ])
828 }
829
830 /// Parse horizontal "lineto" command.
parse_h_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()>831 fn parse_h_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
832 parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ])
833 }
834
835 /// Parse vertical "lineto" command.
parse_v_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()>836 fn parse_v_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
837 parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ])
838 }
839
840 /// Parse cubic Bézier curve command.
parse_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()>841 fn parse_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
842 parse_arguments!(self, absolute, CurveTo, [
843 control1 => parse_coord, control2 => parse_coord, point => parse_coord
844 ])
845 }
846
847 /// Parse smooth "curveto" command.
parse_smooth_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()>848 fn parse_smooth_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
849 parse_arguments!(self, absolute, SmoothCurveTo, [
850 control2 => parse_coord, point => parse_coord
851 ])
852 }
853
854 /// Parse quadratic Bézier curve command.
parse_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()>855 fn parse_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
856 parse_arguments!(self, absolute, QuadBezierCurveTo, [
857 control1 => parse_coord, point => parse_coord
858 ])
859 }
860
861 /// Parse smooth quadratic Bézier curveto command.
parse_smooth_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()>862 fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
863 parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ])
864 }
865
866 /// Parse elliptical arc curve command.
parse_elliptical_arc(&mut self, absolute: IsAbsolute) -> Result<(), ()>867 fn parse_elliptical_arc(&mut self, absolute: IsAbsolute) -> Result<(), ()> {
868 // Parse a flag whose value is '0' or '1'; otherwise, return Err(()).
869 let parse_flag = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
870 Some(c) if c == b'0' || c == b'1' => Ok(ArcFlag(c == b'1')),
871 _ => Err(()),
872 };
873 parse_arguments!(self, absolute, EllipticalArc, [
874 rx => parse_number,
875 ry => parse_number,
876 angle => parse_number,
877 large_arc_flag => parse_flag,
878 sweep_flag => parse_flag,
879 point => parse_coord
880 ])
881 }
882 }
883
884 /// Parse a pair of numbers into CoordPair.
parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()>885 fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> {
886 let x = parse_number(iter)?;
887 skip_comma_wsp(iter);
888 let y = parse_number(iter)?;
889 Ok(CoordPair::new(x, y))
890 }
891
892 /// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed
893 /// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating
894 /// point number. In other words, the logic here is similar with that of
895 /// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the
896 /// input is a Peekable and we only accept an integer of a floating point number.
897 ///
898 /// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF
parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()>899 fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> {
900 // 1. Check optional sign.
901 let sign = if iter
902 .peek()
903 .map_or(false, |&sign| sign == b'+' || sign == b'-')
904 {
905 if iter.next().unwrap() == b'-' {
906 -1.
907 } else {
908 1.
909 }
910 } else {
911 1.
912 };
913
914 // 2. Check integer part.
915 let mut integral_part: f64 = 0.;
916 let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') {
917 // If the first digit in integer part is neither a dot nor a digit, this is not a number.
918 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
919 return Err(());
920 }
921
922 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
923 integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64;
924 }
925
926 iter.peek().map_or(false, |&n| n == b'.')
927 } else {
928 true
929 };
930
931 // 3. Check fractional part.
932 let mut fractional_part: f64 = 0.;
933 if got_dot {
934 // Consume '.'.
935 iter.next();
936 // If the first digit in fractional part is not a digit, this is not a number.
937 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
938 return Err(());
939 }
940
941 let mut factor = 0.1;
942 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
943 fractional_part += (iter.next().unwrap() - b'0') as f64 * factor;
944 factor *= 0.1;
945 }
946 }
947
948 let mut value = sign * (integral_part + fractional_part);
949
950 // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to
951 // treat the numbers after 'E' or 'e' are in the exponential part.
952 if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') {
953 // Consume 'E' or 'e'.
954 iter.next();
955 let exp_sign = if iter
956 .peek()
957 .map_or(false, |&sign| sign == b'+' || sign == b'-')
958 {
959 if iter.next().unwrap() == b'-' {
960 -1.
961 } else {
962 1.
963 }
964 } else {
965 1.
966 };
967
968 let mut exp: f64 = 0.;
969 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
970 exp = exp * 10. + (iter.next().unwrap() - b'0') as f64;
971 }
972
973 value *= f64::powf(10., exp * exp_sign);
974 }
975
976 if value.is_finite() {
977 Ok(value
978 .min(::std::f32::MAX as f64)
979 .max(::std::f32::MIN as f64) as CSSFloat)
980 } else {
981 Err(())
982 }
983 }
984
985 /// Skip all svg whitespaces, and return true if |iter| hasn't finished.
986 #[inline]
skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool987 fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
988 // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}.
989 // However, SVG 2 has one extra whitespace: \u{C}.
990 // Therefore, we follow the newest spec for the definition of whitespace,
991 // i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}.
992 while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) {
993 iter.next();
994 }
995 iter.peek().is_some()
996 }
997
998 /// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished.
999 #[inline]
skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool1000 fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1001 if !skip_wsp(iter) {
1002 return false;
1003 }
1004
1005 if *iter.peek().unwrap() != b',' {
1006 return true;
1007 }
1008 iter.next();
1009
1010 skip_wsp(iter)
1011 }
1012