1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::values::animated::{lists, Animate, Procedure};
10use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
11use crate::values::generics::basic_shape::GenericShapeCommand;
12use crate::values::generics::basic_shape::{
13 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, AxisPosition, CommandEndPoint, ControlPoint,
14 ControlReference, CoordinatePair, RelativeControlPoint,
15};
16use crate::values::generics::position::GenericPosition;
17use crate::values::CSSFloat;
18use cssparser::Parser;
19use std::fmt::{self, Write};
20use std::iter::{Cloned, Peekable};
21use std::ops;
22use std::slice;
23use style_traits::values::SequenceWriter;
24use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
25
26#[derive(Clone, Debug, Eq, PartialEq)]
28#[allow(missing_docs)]
29pub enum AllowEmpty {
30 Yes,
31 No,
32}
33
34#[derive(
38 Clone,
39 Debug,
40 Deserialize,
41 MallocSizeOf,
42 PartialEq,
43 Serialize,
44 SpecifiedValueInfo,
45 ToAnimatedZero,
46 ToComputedValue,
47 ToResolvedValue,
48 ToShmem,
49)]
50#[repr(C)]
51pub struct SVGPathData(
52 #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>,
55);
56
57impl SVGPathData {
58 #[inline]
60 pub fn commands(&self) -> &[PathCommand] {
61 &self.0
62 }
63
64 pub fn normalize(&self, reduce: bool) -> Self {
67 let mut state = PathTraversalState {
68 subpath_start: CoordPair::new(0.0, 0.0),
69 pos: CoordPair::new(0.0, 0.0),
70 last_command: PathCommand::Close,
71 last_control: CoordPair::new(0.0, 0.0),
72 };
73 let iter = self.0.iter().map(|seg| seg.normalize(&mut state, reduce));
74 SVGPathData(crate::ArcSlice::from_iter(iter))
75 }
76
77 pub fn parse<'i, 't>(
90 input: &mut Parser<'i, 't>,
91 allow_empty: AllowEmpty,
92 ) -> Result<Self, ParseError<'i>> {
93 let location = input.current_source_location();
94 let path_string = input.expect_string()?.as_ref();
95 let (path, ok) = Self::parse_bytes(path_string.as_bytes());
96 if !ok || (allow_empty == AllowEmpty::No && path.0.is_empty()) {
97 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
98 }
99 return Ok(path);
100 }
101
102 pub fn parse_bytes(input: &[u8]) -> (Self, bool) {
108 let mut ok = true;
110 let mut path_parser = PathParser::new(input);
111
112 while skip_wsp(&mut path_parser.chars) {
113 if path_parser.parse_subpath().is_err() {
114 ok = false;
115 break;
116 }
117 }
118
119 let path = Self(crate::ArcSlice::from_iter(path_parser.path.into_iter()));
120 (path, ok)
121 }
122
123 pub fn to_css<W>(&self, dest: &mut CssWriter<W>, quote: bool) -> fmt::Result
125 where
126 W: fmt::Write,
127 {
128 if quote {
129 dest.write_char('"')?;
130 }
131 let mut writer = SequenceWriter::new(dest, " ");
132 for command in self.commands() {
133 writer.write_item(|inner| command.to_css_for_svg(inner))?;
134 }
135 if quote {
136 dest.write_char('"')?;
137 }
138 Ok(())
139 }
140}
141
142impl ToCss for SVGPathData {
143 #[inline]
144 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
145 where
146 W: fmt::Write,
147 {
148 self.to_css(dest, true)
149 }
150}
151
152impl Parse for SVGPathData {
153 fn parse<'i, 't>(
154 _context: &ParserContext,
155 input: &mut Parser<'i, 't>,
156 ) -> Result<Self, ParseError<'i>> {
157 SVGPathData::parse(input, AllowEmpty::Yes)
161 }
162}
163
164impl Animate for SVGPathData {
165 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
166 if self.0.len() != other.0.len() {
167 return Err(());
168 }
169
170 let left = self.normalize(false);
174 let right = other.normalize(false);
175
176 let items: Vec<_> = lists::by_computed_value::animate(&left.0, &right.0, procedure)?;
177 Ok(SVGPathData(crate::ArcSlice::from_iter(items.into_iter())))
178 }
179}
180
181impl ComputeSquaredDistance for SVGPathData {
182 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
183 if self.0.len() != other.0.len() {
184 return Err(());
185 }
186 let left = self.normalize(false);
187 let right = other.normalize(false);
188 lists::by_computed_value::squared_distance(&left.0, &right.0)
189 }
190}
191
192pub type SVGPathPosition = GenericPosition<CSSFloat, CSSFloat>;
194
195pub type PathCommand = GenericShapeCommand<CSSFloat, SVGPathPosition, CSSFloat>;
202
203#[allow(missing_docs)]
205struct PathTraversalState {
206 subpath_start: CoordPair,
207 pos: CoordPair,
208 last_command: PathCommand,
209 last_control: CoordPair,
210}
211
212impl PathCommand {
213 fn normalize(&self, state: &mut PathTraversalState, reduce: bool) -> Self {
220 use crate::values::generics::basic_shape::GenericShapeCommand::*;
221 match *self {
222 Close => {
223 state.pos = state.subpath_start;
224 if reduce {
225 state.last_command = *self;
226 }
227 Close
228 },
229 Move { mut point } => {
230 point = point.to_abs(state.pos);
231 state.pos = point.into();
232 state.subpath_start = point.into();
233 if reduce {
234 state.last_command = *self;
235 }
236 Move { point }
237 },
238 Line { mut point } => {
239 point = point.to_abs(state.pos);
240 state.pos = point.into();
241 if reduce {
242 state.last_command = *self;
243 }
244 Line { point }
245 },
246 HLine { mut x } => {
247 x = x.to_abs(state.pos.x);
248 state.pos.x = x.into();
249 if reduce {
250 state.last_command = *self;
251 PathCommand::Line {
252 point: CommandEndPoint::ToPosition(state.pos.into()),
253 }
254 } else {
255 HLine { x }
256 }
257 },
258 VLine { mut y } => {
259 y = y.to_abs(state.pos.y);
260 state.pos.y = y.into();
261 if reduce {
262 state.last_command = *self;
263 PathCommand::Line {
264 point: CommandEndPoint::ToPosition(state.pos.into()),
265 }
266 } else {
267 VLine { y }
268 }
269 },
270 CubicCurve {
271 mut point,
272 mut control1,
273 mut control2,
274 } => {
275 control1 = control1.to_abs(state.pos, point);
276 control2 = control2.to_abs(state.pos, point);
277 point = point.to_abs(state.pos);
278 state.pos = point.into();
279 if reduce {
280 state.last_command = *self;
281 state.last_control = control2.into();
282 }
283 CubicCurve {
284 point,
285 control1,
286 control2,
287 }
288 },
289 QuadCurve {
290 mut point,
291 mut control1,
292 } => {
293 control1 = control1.to_abs(state.pos, point);
294 point = point.to_abs(state.pos);
295 if reduce {
296 let c1 = state.pos + 2. * (CoordPair::from(control1) - state.pos) / 3.;
297 let control2 = CoordPair::from(point)
298 + 2. * (CoordPair::from(control1) - point.into()) / 3.;
299 state.pos = point.into();
300 state.last_command = *self;
301 state.last_control = control1.into();
302 CubicCurve {
303 point,
304 control1: ControlPoint::Absolute(c1.into()),
305 control2: ControlPoint::Absolute(control2.into()),
306 }
307 } else {
308 state.pos = point.into();
309 QuadCurve { point, control1 }
310 }
311 },
312 SmoothCubic {
313 mut point,
314 mut control2,
315 } => {
316 control2 = control2.to_abs(state.pos, point);
317 point = point.to_abs(state.pos);
318 if reduce {
319 let control1 = match state.last_command {
320 PathCommand::CubicCurve {
321 point: _,
322 control1: _,
323 control2: _,
324 }
325 | PathCommand::SmoothCubic {
326 point: _,
327 control2: _,
328 } => state.pos + state.pos - state.last_control,
329 _ => state.pos,
330 };
331 state.pos = point.into();
332 state.last_control = control2.into();
333 state.last_command = *self;
334 CubicCurve {
335 point,
336 control1: ControlPoint::Absolute(control1.into()),
337 control2,
338 }
339 } else {
340 state.pos = point.into();
341 SmoothCubic { point, control2 }
342 }
343 },
344 SmoothQuad { mut point } => {
345 point = point.to_abs(state.pos);
346 if reduce {
347 let control = match state.last_command {
348 PathCommand::QuadCurve {
349 point: _,
350 control1: _,
351 }
352 | PathCommand::SmoothQuad { point: _ } => {
353 state.pos + state.pos - state.last_control
354 },
355 _ => state.pos,
356 };
357 let control1 = state.pos + 2. * (control - state.pos) / 3.;
358 let control2 = CoordPair::from(point) + 2. * (control - point.into()) / 3.;
359 state.pos = point.into();
360 state.last_command = *self;
361 state.last_control = control;
362 CubicCurve {
363 point,
364 control1: ControlPoint::Absolute(control1.into()),
365 control2: ControlPoint::Absolute(control2.into()),
366 }
367 } else {
368 state.pos = point.into();
369 SmoothQuad { point }
370 }
371 },
372 Arc {
373 mut point,
374 radii,
375 arc_sweep,
376 arc_size,
377 rotate,
378 } => {
379 point = point.to_abs(state.pos);
380 state.pos = point.into();
381 if reduce {
382 state.last_command = *self;
383 if radii.rx == 0. && radii.ry.as_ref().is_none_or(|v| *v == 0.) {
384 let end_point = CoordPair::from(point);
385 CubicCurve {
386 point: CommandEndPoint::ToPosition(state.pos.into()),
387 control1: ControlPoint::Absolute(end_point.into()),
388 control2: ControlPoint::Absolute(end_point.into()),
389 }
390 } else {
391 Arc {
392 point,
393 radii,
394 arc_sweep,
395 arc_size,
396 rotate,
397 }
398 }
399 } else {
400 Arc {
401 point,
402 radii,
403 arc_sweep,
404 arc_size,
405 rotate,
406 }
407 }
408 },
409 }
410 }
411
412 fn to_css_for_svg<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
414 where
415 W: fmt::Write,
416 {
417 use crate::values::generics::basic_shape::GenericShapeCommand::*;
418 match *self {
419 Close => dest.write_char('Z'),
420 Move { point } => {
421 dest.write_char(if point.is_abs() { 'M' } else { 'm' })?;
422 dest.write_char(' ')?;
423 CoordPair::from(point).to_css(dest)
424 },
425 Line { point } => {
426 dest.write_char(if point.is_abs() { 'L' } else { 'l' })?;
427 dest.write_char(' ')?;
428 CoordPair::from(point).to_css(dest)
429 },
430 CubicCurve {
431 point,
432 control1,
433 control2,
434 } => {
435 dest.write_char(if point.is_abs() { 'C' } else { 'c' })?;
436 dest.write_char(' ')?;
437 control1.to_css(dest, point.is_abs())?;
438 dest.write_char(' ')?;
439 control2.to_css(dest, point.is_abs())?;
440 dest.write_char(' ')?;
441 CoordPair::from(point).to_css(dest)
442 },
443 QuadCurve { point, control1 } => {
444 dest.write_char(if point.is_abs() { 'Q' } else { 'q' })?;
445 dest.write_char(' ')?;
446 control1.to_css(dest, point.is_abs())?;
447 dest.write_char(' ')?;
448 CoordPair::from(point).to_css(dest)
449 },
450 Arc {
451 point,
452 radii,
453 arc_sweep,
454 arc_size,
455 rotate,
456 } => {
457 dest.write_char(if point.is_abs() { 'A' } else { 'a' })?;
458 dest.write_char(' ')?;
459 radii.to_css(dest)?;
460 dest.write_char(' ')?;
461 rotate.to_css(dest)?;
462 dest.write_char(' ')?;
463 (arc_size as i32).to_css(dest)?;
464 dest.write_char(' ')?;
465 (arc_sweep as i32).to_css(dest)?;
466 dest.write_char(' ')?;
467 CoordPair::from(point).to_css(dest)
468 },
469 HLine { x } => {
470 dest.write_char(if x.is_abs() { 'H' } else { 'h' })?;
471 dest.write_char(' ')?;
472 CSSFloat::from(x).to_css(dest)
473 },
474 VLine { y } => {
475 dest.write_char(if y.is_abs() { 'V' } else { 'v' })?;
476 dest.write_char(' ')?;
477 CSSFloat::from(y).to_css(dest)
478 },
479 SmoothCubic { point, control2 } => {
480 dest.write_char(if point.is_abs() { 'S' } else { 's' })?;
481 dest.write_char(' ')?;
482 control2.to_css(dest, point.is_abs())?;
483 dest.write_char(' ')?;
484 CoordPair::from(point).to_css(dest)
485 },
486 SmoothQuad { point } => {
487 dest.write_char(if point.is_abs() { 'T' } else { 't' })?;
488 dest.write_char(' ')?;
489 CoordPair::from(point).to_css(dest)
490 },
491 }
492 }
493}
494
495pub type CoordPair = CoordinatePair<CSSFloat>;
497
498impl ops::Add<CoordPair> for CoordPair {
499 type Output = CoordPair;
500
501 fn add(self, rhs: CoordPair) -> CoordPair {
502 Self {
503 x: self.x + rhs.x,
504 y: self.y + rhs.y,
505 }
506 }
507}
508
509impl ops::Sub<CoordPair> for CoordPair {
510 type Output = CoordPair;
511
512 fn sub(self, rhs: CoordPair) -> CoordPair {
513 Self {
514 x: self.x - rhs.x,
515 y: self.y - rhs.y,
516 }
517 }
518}
519
520impl ops::Mul<CSSFloat> for CoordPair {
521 type Output = CoordPair;
522
523 fn mul(self, f: CSSFloat) -> CoordPair {
524 Self {
525 x: self.x * f,
526 y: self.y * f,
527 }
528 }
529}
530
531impl ops::Mul<CoordPair> for CSSFloat {
532 type Output = CoordPair;
533
534 fn mul(self, rhs: CoordPair) -> CoordPair {
535 rhs * self
536 }
537}
538
539impl ops::Div<CSSFloat> for CoordPair {
540 type Output = CoordPair;
541
542 fn div(self, f: CSSFloat) -> CoordPair {
543 Self {
544 x: self.x / f,
545 y: self.y / f,
546 }
547 }
548}
549
550impl CommandEndPoint<SVGPathPosition, CSSFloat> {
551 pub fn to_abs(self, state_pos: CoordPair) -> Self {
553 match self {
555 CommandEndPoint::ToPosition(_) => self,
556 CommandEndPoint::ByCoordinate(coord) => {
557 let pos = GenericPosition {
558 horizontal: coord.x + state_pos.x,
559 vertical: coord.y + state_pos.y,
560 };
561 CommandEndPoint::ToPosition(pos)
562 },
563 }
564 }
565}
566
567impl AxisEndPoint<CSSFloat> {
568 pub fn to_abs(self, base: CSSFloat) -> AxisEndPoint<CSSFloat> {
570 match self {
572 AxisEndPoint::ToPosition(_) => self,
573 AxisEndPoint::ByCoordinate(coord) => {
574 AxisEndPoint::ToPosition(AxisPosition::LengthPercent(coord + base))
575 },
576 }
577 }
578}
579
580impl ControlPoint<SVGPathPosition, CSSFloat> {
581 pub fn to_abs(
583 self,
584 state_pos: CoordPair,
585 end_point: CommandEndPoint<SVGPathPosition, CSSFloat>,
586 ) -> Self {
587 match self {
589 ControlPoint::Absolute(_) => self,
590 ControlPoint::Relative(point) => {
591 let mut pos = GenericPosition {
592 horizontal: point.coord.x,
593 vertical: point.coord.y,
594 };
595
596 match point.reference {
597 ControlReference::Start => {
598 pos.horizontal += state_pos.x;
599 pos.vertical += state_pos.y;
600 },
601 ControlReference::End => {
602 let end = CoordPair::from(end_point);
603 pos.horizontal += end.x;
604 pos.vertical += end.y;
605 },
606 _ => (),
607 }
608 ControlPoint::Absolute(pos)
609 },
610 }
611 }
612}
613
614impl From<CommandEndPoint<SVGPathPosition, CSSFloat>> for CoordPair {
615 #[inline]
616 fn from(p: CommandEndPoint<SVGPathPosition, CSSFloat>) -> Self {
617 match p {
618 CommandEndPoint::ToPosition(pos) => CoordPair {
619 x: pos.horizontal,
620 y: pos.vertical,
621 },
622 CommandEndPoint::ByCoordinate(coord) => coord,
623 }
624 }
625}
626
627impl From<ControlPoint<SVGPathPosition, CSSFloat>> for CoordPair {
628 #[inline]
629 fn from(point: ControlPoint<SVGPathPosition, CSSFloat>) -> Self {
630 match point {
631 ControlPoint::Absolute(pos) => CoordPair {
632 x: pos.horizontal,
633 y: pos.vertical,
634 },
635 ControlPoint::Relative(_) => {
636 panic!(
637 "Attempted to convert a relative ControlPoint to CoordPair, which is lossy. \
638 Consider converting it to absolute type first using `.to_abs()`."
639 )
640 },
641 }
642 }
643}
644
645impl From<CoordPair> for CommandEndPoint<SVGPathPosition, CSSFloat> {
646 #[inline]
647 fn from(coord: CoordPair) -> Self {
648 CommandEndPoint::ByCoordinate(coord)
649 }
650}
651
652impl From<CoordPair> for SVGPathPosition {
653 #[inline]
654 fn from(coord: CoordPair) -> Self {
655 GenericPosition {
656 horizontal: coord.x,
657 vertical: coord.y,
658 }
659 }
660}
661
662impl From<AxisEndPoint<CSSFloat>> for CSSFloat {
663 #[inline]
664 fn from(p: AxisEndPoint<CSSFloat>) -> Self {
665 match p {
666 AxisEndPoint::ToPosition(AxisPosition::LengthPercent(a)) => a,
667 AxisEndPoint::ToPosition(AxisPosition::Keyword(_)) => {
668 unreachable!("Invalid state: SVG path commands cannot contain a keyword.")
669 },
670 AxisEndPoint::ByCoordinate(a) => a,
671 }
672 }
673}
674
675impl ToCss for SVGPathPosition {
676 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
677 where
678 W: Write,
679 {
680 self.horizontal.to_css(dest)?;
681 dest.write_char(' ')?;
682 self.vertical.to_css(dest)
683 }
684}
685
686struct PathParser<'a> {
688 chars: Peekable<Cloned<slice::Iter<'a, u8>>>,
689 path: Vec<PathCommand>,
690}
691
692macro_rules! parse_arguments {
693 (
694 $parser:ident,
695 $enum:ident,
696 $( $field:ident : $value:expr, )*
697 [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
698 ) => {
699 {
700 loop {
701 let $para = $func(&mut $parser.chars)?;
702 $(
703 skip_comma_wsp(&mut $parser.chars);
704 let $other_para = $other_func(&mut $parser.chars)?;
705 )*
706 $parser.path.push(
707 PathCommand::$enum { $( $field: $value, )* $para $(, $other_para)* }
708 );
709
710 if !skip_wsp(&mut $parser.chars) ||
712 $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
713 break;
714 }
715 skip_comma_wsp(&mut $parser.chars);
716 }
717 Ok(())
718 }
719 }
720}
721
722impl<'a> PathParser<'a> {
723 #[inline]
725 fn new(bytes: &'a [u8]) -> Self {
726 PathParser {
727 chars: bytes.iter().cloned().peekable(),
728 path: Vec::new(),
729 }
730 }
731
732 fn parse_subpath(&mut self) -> Result<(), ()> {
734 self.parse_moveto()?;
737
738 loop {
740 skip_wsp(&mut self.chars);
741 if self.chars.peek().map_or(true, |&m| m == b'M' || m == b'm') {
742 break;
743 }
744
745 let command = self.chars.next().unwrap();
746
747 skip_wsp(&mut self.chars);
748 match command {
749 b'Z' | b'z' => self.parse_closepath(),
750 b'L' => self.parse_line_abs(),
751 b'l' => self.parse_line_rel(),
752 b'H' => self.parse_h_line_abs(),
753 b'h' => self.parse_h_line_rel(),
754 b'V' => self.parse_v_line_abs(),
755 b'v' => self.parse_v_line_rel(),
756 b'C' => self.parse_curve_abs(),
757 b'c' => self.parse_curve_rel(),
758 b'S' => self.parse_smooth_curve_abs(),
759 b's' => self.parse_smooth_curve_rel(),
760 b'Q' => self.parse_quadratic_bezier_curve_abs(),
761 b'q' => self.parse_quadratic_bezier_curve_rel(),
762 b'T' => self.parse_smooth_quadratic_bezier_curve_abs(),
763 b't' => self.parse_smooth_quadratic_bezier_curve_rel(),
764 b'A' => self.parse_elliptical_arc_abs(),
765 b'a' => self.parse_elliptical_arc_rel(),
766 _ => return Err(()),
767 }?;
768 }
769 Ok(())
770 }
771
772 fn parse_moveto(&mut self) -> Result<(), ()> {
774 let command = match self.chars.next() {
775 Some(c) if c == b'M' || c == b'm' => c,
776 _ => return Err(()),
777 };
778
779 skip_wsp(&mut self.chars);
780 let point = if command == b'M' {
781 parse_command_end_abs(&mut self.chars)
782 } else {
783 parse_command_end_rel(&mut self.chars)
784 }?;
785 self.path.push(PathCommand::Move { point });
786
787 if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic())
789 {
790 return Ok(());
791 }
792 skip_comma_wsp(&mut self.chars);
793
794 if point.is_abs() {
797 self.parse_line_abs()
798 } else {
799 self.parse_line_rel()
800 }
801 }
802
803 fn parse_closepath(&mut self) -> Result<(), ()> {
805 self.path.push(PathCommand::Close);
806 Ok(())
807 }
808
809 fn parse_line_abs(&mut self) -> Result<(), ()> {
811 parse_arguments!(self, Line, [ point => parse_command_end_abs ])
812 }
813
814 fn parse_line_rel(&mut self) -> Result<(), ()> {
816 parse_arguments!(self, Line, [ point => parse_command_end_rel ])
817 }
818
819 fn parse_h_line_abs(&mut self) -> Result<(), ()> {
821 parse_arguments!(self, HLine, [ x => parse_axis_end_abs ])
822 }
823
824 fn parse_h_line_rel(&mut self) -> Result<(), ()> {
826 parse_arguments!(self, HLine, [ x => parse_axis_end_rel ])
827 }
828
829 fn parse_v_line_abs(&mut self) -> Result<(), ()> {
831 parse_arguments!(self, VLine, [ y => parse_axis_end_abs ])
832 }
833
834 fn parse_v_line_rel(&mut self) -> Result<(), ()> {
836 parse_arguments!(self, VLine, [ y => parse_axis_end_rel ])
837 }
838
839 fn parse_curve_abs(&mut self) -> Result<(), ()> {
841 parse_arguments!(self, CubicCurve, [
842 control1 => parse_control_point_abs, control2 => parse_control_point_abs, point => parse_command_end_abs
843 ])
844 }
845
846 fn parse_curve_rel(&mut self) -> Result<(), ()> {
848 parse_arguments!(self, CubicCurve, [
849 control1 => parse_control_point_rel, control2 => parse_control_point_rel, point => parse_command_end_rel
850 ])
851 }
852
853 fn parse_smooth_curve_abs(&mut self) -> Result<(), ()> {
855 parse_arguments!(self, SmoothCubic, [
856 control2 => parse_control_point_abs, point => parse_command_end_abs
857 ])
858 }
859
860 fn parse_smooth_curve_rel(&mut self) -> Result<(), ()> {
862 parse_arguments!(self, SmoothCubic, [
863 control2 => parse_control_point_rel, point => parse_command_end_rel
864 ])
865 }
866
867 fn parse_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> {
869 parse_arguments!(self, QuadCurve, [
870 control1 => parse_control_point_abs, point => parse_command_end_abs
871 ])
872 }
873
874 fn parse_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> {
876 parse_arguments!(self, QuadCurve, [
877 control1 => parse_control_point_rel, point => parse_command_end_rel
878 ])
879 }
880
881 fn parse_smooth_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> {
883 parse_arguments!(self, SmoothQuad, [ point => parse_command_end_abs ])
884 }
885
886 fn parse_smooth_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> {
888 parse_arguments!(self, SmoothQuad, [ point => parse_command_end_rel ])
889 }
890
891 fn parse_elliptical_arc_abs(&mut self) -> Result<(), ()> {
893 let (parse_arc_size, parse_arc_sweep) = Self::arc_flag_parsers();
894 parse_arguments!(self, Arc, [
895 radii => parse_arc_radii,
896 rotate => parse_number,
897 arc_size => parse_arc_size,
898 arc_sweep => parse_arc_sweep,
899 point => parse_command_end_abs
900 ])
901 }
902
903 fn parse_elliptical_arc_rel(&mut self) -> Result<(), ()> {
905 let (parse_arc_size, parse_arc_sweep) = Self::arc_flag_parsers();
906 parse_arguments!(self, Arc, [
907 radii => parse_arc_radii,
908 rotate => parse_number,
909 arc_size => parse_arc_size,
910 arc_sweep => parse_arc_sweep,
911 point => parse_command_end_rel
912 ])
913 }
914
915 fn arc_flag_parsers() -> (
917 impl Fn(&mut Peekable<Cloned<slice::Iter<'_, u8>>>) -> Result<ArcSize, ()>,
918 impl Fn(&mut Peekable<Cloned<slice::Iter<'_, u8>>>) -> Result<ArcSweep, ()>,
919 ) {
920 let parse_arc_size = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
922 Some(c) if c == b'1' => Ok(ArcSize::Large),
923 Some(c) if c == b'0' => Ok(ArcSize::Small),
924 _ => Err(()),
925 };
926 let parse_arc_sweep = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
927 Some(c) if c == b'1' => Ok(ArcSweep::Cw),
928 Some(c) if c == b'0' => Ok(ArcSweep::Ccw),
929 _ => Err(()),
930 };
931 (parse_arc_size, parse_arc_sweep)
932 }
933}
934
935fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> {
937 let x = parse_number(iter)?;
938 skip_comma_wsp(iter);
939 let y = parse_number(iter)?;
940 Ok(CoordPair::new(x, y))
941}
942
943fn parse_command_end_abs(
945 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
946) -> Result<CommandEndPoint<SVGPathPosition, CSSFloat>, ()> {
947 let coord = parse_coord(iter)?;
948 Ok(CommandEndPoint::ToPosition(coord.into()))
949}
950
951fn parse_command_end_rel(
953 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
954) -> Result<CommandEndPoint<SVGPathPosition, CSSFloat>, ()> {
955 let coord = parse_coord(iter)?;
956 Ok(CommandEndPoint::ByCoordinate(coord))
957}
958
959fn parse_control_point_abs(
961 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
962) -> Result<ControlPoint<SVGPathPosition, CSSFloat>, ()> {
963 let coord = parse_coord(iter)?;
964 Ok(ControlPoint::Relative(RelativeControlPoint {
965 coord,
966 reference: ControlReference::Origin,
967 }))
968}
969
970fn parse_control_point_rel(
972 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
973) -> Result<ControlPoint<SVGPathPosition, CSSFloat>, ()> {
974 let coord = parse_coord(iter)?;
975 Ok(ControlPoint::Relative(RelativeControlPoint {
976 coord,
977 reference: ControlReference::Start,
978 }))
979}
980
981fn parse_axis_end_abs(
983 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
984) -> Result<AxisEndPoint<f32>, ()> {
985 let value = parse_number(iter)?;
986 Ok(AxisEndPoint::ToPosition(AxisPosition::LengthPercent(value)))
987}
988
989fn parse_axis_end_rel(
991 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
992) -> Result<AxisEndPoint<f32>, ()> {
993 let value = parse_number(iter)?;
994 Ok(AxisEndPoint::ByCoordinate(value))
995}
996
997fn parse_arc_radii(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<ArcRadii<CSSFloat>, ()> {
999 let coord = parse_coord(iter)?;
1000 Ok(ArcRadii {
1001 rx: coord.x,
1002 ry: Some(coord.y).into(),
1003 })
1004}
1005
1006fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> {
1014 let sign = if iter
1016 .peek()
1017 .map_or(false, |&sign| sign == b'+' || sign == b'-')
1018 {
1019 if iter.next().unwrap() == b'-' {
1020 -1.
1021 } else {
1022 1.
1023 }
1024 } else {
1025 1.
1026 };
1027
1028 let mut integral_part: f64 = 0.;
1030 let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') {
1031 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
1033 return Err(());
1034 }
1035
1036 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
1037 integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64;
1038 }
1039
1040 iter.peek().map_or(false, |&n| n == b'.')
1041 } else {
1042 true
1043 };
1044
1045 let mut fractional_part: f64 = 0.;
1047 if got_dot {
1048 iter.next();
1050 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
1052 return Err(());
1053 }
1054
1055 let mut factor = 0.1;
1056 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
1057 fractional_part += (iter.next().unwrap() - b'0') as f64 * factor;
1058 factor *= 0.1;
1059 }
1060 }
1061
1062 let mut value = sign * (integral_part + fractional_part);
1063
1064 if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') {
1067 iter.next();
1069 let exp_sign = if iter
1070 .peek()
1071 .map_or(false, |&sign| sign == b'+' || sign == b'-')
1072 {
1073 if iter.next().unwrap() == b'-' {
1074 -1.
1075 } else {
1076 1.
1077 }
1078 } else {
1079 1.
1080 };
1081
1082 let mut exp: f64 = 0.;
1083 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
1084 exp = exp * 10. + (iter.next().unwrap() - b'0') as f64;
1085 }
1086
1087 value *= f64::powf(10., exp * exp_sign);
1088 }
1089
1090 if value.is_finite() {
1091 Ok(value.min(f32::MAX as f64).max(f32::MIN as f64) as CSSFloat)
1092 } else {
1093 Err(())
1094 }
1095}
1096
1097#[inline]
1099fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1100 while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) {
1105 iter.next();
1106 }
1107 iter.peek().is_some()
1108}
1109
1110#[inline]
1112fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1113 if !skip_wsp(iter) {
1114 return false;
1115 }
1116
1117 if *iter.peek().unwrap() != b',' {
1118 return true;
1119 }
1120 iter.next();
1121
1122 skip_wsp(iter)
1123}