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