1use crate::parser::{Parse, ParserContext};
8use crate::values::animated::{lists, Animate, Procedure};
9use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
10use crate::values::generics::basic_shape::GenericShapeCommand;
11use crate::values::generics::basic_shape::{
12 ArcRadii, ArcSize, ArcSweep, ByTo, CommandEndPoint, ControlPoint, ControlReference,
13 CoordinatePair, RelativeControlPoint, ShapePosition,
14};
15use crate::values::generics::position::GenericPosition;
16use crate::values::CSSFloat;
17use cssparser::Parser;
18use std::fmt::{self, Write};
19use std::iter::{Cloned, Peekable};
20use std::ops;
21use std::slice;
22use style_traits::values::SequenceWriter;
23use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
24
25#[derive(Clone, Debug, Eq, PartialEq)]
27#[allow(missing_docs)]
28pub enum AllowEmpty {
29 Yes,
30 No,
31}
32
33#[derive(
37 Clone,
38 Debug,
39 Deserialize,
40 MallocSizeOf,
41 PartialEq,
42 Serialize,
43 SpecifiedValueInfo,
44 ToAnimatedZero,
45 ToComputedValue,
46 ToResolvedValue,
47 ToShmem,
48)]
49#[repr(C)]
50pub struct SVGPathData(
51 #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>,
54);
55
56impl SVGPathData {
57 #[inline]
59 pub fn commands(&self) -> &[PathCommand] {
60 &self.0
61 }
62
63 pub fn normalize(&self, reduce: bool) -> Self {
66 let mut state = PathTraversalState {
67 subpath_start: CoordPair::new(0.0, 0.0),
68 pos: CoordPair::new(0.0, 0.0),
69 last_command: PathCommand::Close,
70 last_control: CoordPair::new(0.0, 0.0),
71 };
72 let iter = self.0.iter().map(|seg| seg.normalize(&mut state, reduce));
73 SVGPathData(crate::ArcSlice::from_iter(iter))
74 }
75
76 pub fn parse<'i, 't>(
89 input: &mut Parser<'i, 't>,
90 allow_empty: AllowEmpty,
91 ) -> Result<Self, ParseError<'i>> {
92 let location = input.current_source_location();
93 let path_string = input.expect_string()?.as_ref();
94 let (path, ok) = Self::parse_bytes(path_string.as_bytes());
95 if !ok || (allow_empty == AllowEmpty::No && path.0.is_empty()) {
96 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
97 }
98 return Ok(path);
99 }
100
101 pub fn parse_bytes(input: &[u8]) -> (Self, bool) {
107 let mut ok = true;
109 let mut path_parser = PathParser::new(input);
110
111 while skip_wsp(&mut path_parser.chars) {
112 if path_parser.parse_subpath().is_err() {
113 ok = false;
114 break;
115 }
116 }
117
118 let path = Self(crate::ArcSlice::from_iter(path_parser.path.into_iter()));
119 (path, ok)
120 }
121
122 pub fn to_css<W>(&self, dest: &mut CssWriter<W>, quote: bool) -> fmt::Result
124 where
125 W: fmt::Write,
126 {
127 if quote {
128 dest.write_char('"')?;
129 }
130 let mut writer = SequenceWriter::new(dest, " ");
131 for command in self.commands() {
132 writer.write_item(|inner| command.to_css_for_svg(inner))?;
133 }
134 if quote {
135 dest.write_char('"')?;
136 }
137 Ok(())
138 }
139}
140
141impl ToCss for SVGPathData {
142 #[inline]
143 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
144 where
145 W: fmt::Write,
146 {
147 self.to_css(dest, true)
148 }
149}
150
151impl Parse for SVGPathData {
152 fn parse<'i, 't>(
153 _context: &ParserContext,
154 input: &mut Parser<'i, 't>,
155 ) -> Result<Self, ParseError<'i>> {
156 SVGPathData::parse(input, AllowEmpty::Yes)
160 }
161}
162
163impl Animate for SVGPathData {
164 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
165 if self.0.len() != other.0.len() {
166 return Err(());
167 }
168
169 let left = self.normalize(false);
173 let right = other.normalize(false);
174
175 let items: Vec<_> = lists::by_computed_value::animate(&left.0, &right.0, procedure)?;
176 Ok(SVGPathData(crate::ArcSlice::from_iter(items.into_iter())))
177 }
178}
179
180impl ComputeSquaredDistance for SVGPathData {
181 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
182 if self.0.len() != other.0.len() {
183 return Err(());
184 }
185 let left = self.normalize(false);
186 let right = other.normalize(false);
187 lists::by_computed_value::squared_distance(&left.0, &right.0)
188 }
189}
190
191pub type PathCommand = GenericShapeCommand<CSSFloat, ShapePosition<CSSFloat>, CSSFloat>;
198
199#[allow(missing_docs)]
201struct PathTraversalState {
202 subpath_start: CoordPair,
203 pos: CoordPair,
204 last_command: PathCommand,
205 last_control: CoordPair,
206}
207
208impl PathCommand {
209 fn normalize(&self, state: &mut PathTraversalState, reduce: bool) -> Self {
216 use crate::values::generics::basic_shape::GenericShapeCommand::*;
217 match *self {
218 Close => {
219 state.pos = state.subpath_start;
220 if reduce {
221 state.last_command = *self;
222 }
223 Close
224 },
225 Move { mut point } => {
226 point = point.to_abs(state.pos);
227 state.pos = point.into();
228 state.subpath_start = point.into();
229 if reduce {
230 state.last_command = *self;
231 }
232 Move { point }
233 },
234 Line { mut point } => {
235 point = point.to_abs(state.pos);
236 state.pos = point.into();
237 if reduce {
238 state.last_command = *self;
239 }
240 Line { point }
241 },
242 HLine { by_to, mut x } => {
243 if !by_to.is_abs() {
244 x += state.pos.x;
245 }
246 state.pos.x = x;
247 if reduce {
248 state.last_command = *self;
249 PathCommand::Line {
250 point: CommandEndPoint::ToPosition(state.pos.into()),
251 }
252 } else {
253 HLine { by_to: ByTo::To, x }
254 }
255 },
256 VLine { by_to, mut y } => {
257 if !by_to.is_abs() {
258 y += state.pos.y;
259 }
260 state.pos.y = y;
261 if reduce {
262 state.last_command = *self;
263 PathCommand::Line {
264 point: CommandEndPoint::ToPosition(state.pos.into()),
265 }
266 } else {
267 VLine { by_to: ByTo::To, 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 { by_to, x } => {
470 dest.write_char(if by_to.is_abs() { 'H' } else { 'h' })?;
471 dest.write_char(' ')?;
472 x.to_css(dest)
473 },
474 VLine { by_to, y } => {
475 dest.write_char(if by_to.is_abs() { 'V' } else { 'v' })?;
476 dest.write_char(' ')?;
477 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<ShapePosition<CSSFloat>, 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 ControlPoint<ShapePosition<CSSFloat>, CSSFloat> {
568 pub fn to_abs(
570 self,
571 state_pos: CoordPair,
572 end_point: CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>,
573 ) -> Self {
574 match self {
576 ControlPoint::Absolute(_) => self,
577 ControlPoint::Relative(point) => {
578 let mut pos = GenericPosition {
579 horizontal: point.coord.x,
580 vertical: point.coord.y,
581 };
582
583 match point.reference {
584 ControlReference::None if !end_point.is_abs() => {
585 pos.horizontal += state_pos.x;
586 pos.vertical += state_pos.y;
587 },
588 ControlReference::Start => {
589 pos.horizontal += state_pos.x;
590 pos.vertical += state_pos.y;
591 },
592 ControlReference::End => {
593 let end = CoordPair::from(end_point);
594 pos.horizontal += end.x;
595 pos.vertical += end.y;
596 },
597 _ => (),
598 }
599 ControlPoint::Absolute(pos)
600 },
601 }
602 }
603}
604
605impl From<CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>> for CoordPair {
606 #[inline]
607 fn from(p: CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>) -> Self {
608 match p {
609 CommandEndPoint::ToPosition(pos) => CoordPair {
610 x: pos.horizontal,
611 y: pos.vertical,
612 },
613 CommandEndPoint::ByCoordinate(coord) => coord,
614 }
615 }
616}
617
618impl From<ControlPoint<ShapePosition<CSSFloat>, CSSFloat>> for CoordPair {
619 #[inline]
620 fn from(point: ControlPoint<ShapePosition<CSSFloat>, CSSFloat>) -> Self {
621 match point {
622 ControlPoint::Absolute(pos) => CoordPair {
623 x: pos.horizontal,
624 y: pos.vertical,
625 },
626 ControlPoint::Relative(_) => {
627 panic!(
628 "Attempted to convert a relative ControlPoint to CoordPair, which is lossy. \
629 Consider converting it to absolute type first using `.to_abs()`."
630 )
631 },
632 }
633 }
634}
635
636impl From<CoordPair> for CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat> {
637 #[inline]
638 fn from(coord: CoordPair) -> Self {
639 CommandEndPoint::ByCoordinate(coord)
640 }
641}
642
643impl From<CoordPair> for ShapePosition<CSSFloat> {
644 #[inline]
645 fn from(coord: CoordPair) -> Self {
646 GenericPosition {
647 horizontal: coord.x,
648 vertical: coord.y,
649 }
650 }
651}
652
653impl ToCss for ShapePosition<CSSFloat> {
654 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
655 where
656 W: Write,
657 {
658 self.horizontal.to_css(dest)?;
659 dest.write_char(' ')?;
660 self.vertical.to_css(dest)
661 }
662}
663
664struct PathParser<'a> {
666 chars: Peekable<Cloned<slice::Iter<'a, u8>>>,
667 path: Vec<PathCommand>,
668}
669
670macro_rules! parse_arguments {
671 (
672 $parser:ident,
673 $enum:ident,
674 $( $field:ident : $value:expr, )*
675 [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
676 ) => {
677 {
678 loop {
679 let $para = $func(&mut $parser.chars)?;
680 $(
681 skip_comma_wsp(&mut $parser.chars);
682 let $other_para = $other_func(&mut $parser.chars)?;
683 )*
684 $parser.path.push(
685 PathCommand::$enum { $( $field: $value, )* $para $(, $other_para)* }
686 );
687
688 if !skip_wsp(&mut $parser.chars) ||
690 $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
691 break;
692 }
693 skip_comma_wsp(&mut $parser.chars);
694 }
695 Ok(())
696 }
697 }
698}
699
700impl<'a> PathParser<'a> {
701 #[inline]
703 fn new(bytes: &'a [u8]) -> Self {
704 PathParser {
705 chars: bytes.iter().cloned().peekable(),
706 path: Vec::new(),
707 }
708 }
709
710 fn parse_subpath(&mut self) -> Result<(), ()> {
712 self.parse_moveto()?;
715
716 loop {
718 skip_wsp(&mut self.chars);
719 if self.chars.peek().map_or(true, |&m| m == b'M' || m == b'm') {
720 break;
721 }
722
723 let command = self.chars.next().unwrap();
724 let by_to = if command.is_ascii_uppercase() {
725 ByTo::To
726 } else {
727 ByTo::By
728 };
729
730 skip_wsp(&mut self.chars);
731 match command {
732 b'Z' | b'z' => self.parse_closepath(),
733 b'L' | b'l' => self.parse_lineto(by_to),
734 b'H' | b'h' => self.parse_h_lineto(by_to),
735 b'V' | b'v' => self.parse_v_lineto(by_to),
736 b'C' | b'c' => self.parse_curveto(by_to),
737 b'S' | b's' => self.parse_smooth_curveto(by_to),
738 b'Q' | b'q' => self.parse_quadratic_bezier_curveto(by_to),
739 b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(by_to),
740 b'A' | b'a' => self.parse_elliptical_arc(by_to),
741 _ => return Err(()),
742 }?;
743 }
744 Ok(())
745 }
746
747 fn parse_moveto(&mut self) -> Result<(), ()> {
749 let command = match self.chars.next() {
750 Some(c) if c == b'M' || c == b'm' => c,
751 _ => return Err(()),
752 };
753
754 skip_wsp(&mut self.chars);
755 let by_to = if command == b'M' { ByTo::To } else { ByTo::By };
756 let point = if by_to == ByTo::To {
757 parse_command_point_abs(&mut self.chars)
758 } else {
759 parse_command_point_rel(&mut self.chars)
760 }?;
761 self.path.push(PathCommand::Move { point });
762
763 if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic())
765 {
766 return Ok(());
767 }
768 skip_comma_wsp(&mut self.chars);
769
770 self.parse_lineto(by_to)
773 }
774
775 fn parse_closepath(&mut self) -> Result<(), ()> {
777 self.path.push(PathCommand::Close);
778 Ok(())
779 }
780
781 fn parse_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
783 if by_to.is_abs() {
784 parse_arguments!(self, Line, [ point => parse_command_point_abs ])
785 } else {
786 parse_arguments!(self, Line, [ point => parse_command_point_rel ])
787 }
788 }
789
790 fn parse_h_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
792 parse_arguments!(self, HLine, by_to: by_to, [ x => parse_number ])
793 }
794
795 fn parse_v_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
797 parse_arguments!(self, VLine, by_to: by_to, [ y => parse_number ])
798 }
799
800 fn parse_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
802 if by_to.is_abs() {
803 parse_arguments!(self, CubicCurve, [
804 control1 => parse_control_point, control2 => parse_control_point, point => parse_command_point_abs
805 ])
806 } else {
807 parse_arguments!(self, CubicCurve, [
808 control1 => parse_control_point, control2 => parse_control_point, point => parse_command_point_rel
809 ])
810 }
811 }
812
813 fn parse_smooth_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
815 if by_to.is_abs() {
816 parse_arguments!(self, SmoothCubic, [
817 control2 => parse_control_point, point => parse_command_point_abs
818 ])
819 } else {
820 parse_arguments!(self, SmoothCubic, [
821 control2 => parse_control_point, point => parse_command_point_rel
822 ])
823 }
824 }
825
826 fn parse_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
828 if by_to.is_abs() {
829 parse_arguments!(self, QuadCurve, [
830 control1 => parse_control_point, point => parse_command_point_abs
831 ])
832 } else {
833 parse_arguments!(self, QuadCurve, [
834 control1 => parse_control_point, point => parse_command_point_rel
835 ])
836 }
837 }
838
839 fn parse_smooth_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
841 if by_to.is_abs() {
842 parse_arguments!(self, SmoothQuad, [ point => parse_command_point_abs ])
843 } else {
844 parse_arguments!(self, SmoothQuad, [ point => parse_command_point_rel ])
845 }
846 }
847
848 fn parse_elliptical_arc(&mut self, by_to: ByTo) -> Result<(), ()> {
850 let parse_arc_size = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
852 Some(c) if c == b'1' => Ok(ArcSize::Large),
853 Some(c) if c == b'0' => Ok(ArcSize::Small),
854 _ => Err(()),
855 };
856 let parse_arc_sweep = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
857 Some(c) if c == b'1' => Ok(ArcSweep::Cw),
858 Some(c) if c == b'0' => Ok(ArcSweep::Ccw),
859 _ => Err(()),
860 };
861 if by_to.is_abs() {
862 parse_arguments!(self, Arc, [
863 radii => parse_arc,
864 rotate => parse_number,
865 arc_size => parse_arc_size,
866 arc_sweep => parse_arc_sweep,
867 point => parse_command_point_abs
868 ])
869 } else {
870 parse_arguments!(self, Arc, [
871 radii => parse_arc,
872 rotate => parse_number,
873 arc_size => parse_arc_size,
874 arc_sweep => parse_arc_sweep,
875 point => parse_command_point_rel
876 ])
877 }
878 }
879}
880
881fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> {
883 let x = parse_number(iter)?;
884 skip_comma_wsp(iter);
885 let y = parse_number(iter)?;
886 Ok(CoordPair::new(x, y))
887}
888
889fn parse_command_point_abs(
891 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
892) -> Result<CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> {
893 let coord = parse_coord(iter)?;
894 Ok(CommandEndPoint::ToPosition(coord.into()))
895}
896
897fn parse_command_point_rel(
899 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
900) -> Result<CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> {
901 let coord = parse_coord(iter)?;
902 Ok(CommandEndPoint::ByCoordinate(coord))
903}
904
905fn parse_control_point(
910 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
911) -> Result<ControlPoint<ShapePosition<CSSFloat>, CSSFloat>, ()> {
912 let coord = parse_coord(iter)?;
913 Ok(ControlPoint::Relative(RelativeControlPoint {
914 coord,
915 reference: ControlReference::None,
916 }))
917}
918
919fn parse_arc(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<ArcRadii<CSSFloat>, ()> {
920 let coord = parse_coord(iter)?;
921 Ok(ArcRadii {
922 rx: coord.x,
923 ry: Some(coord.y).into(),
924 })
925}
926
927fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> {
935 let sign = if iter
937 .peek()
938 .map_or(false, |&sign| sign == b'+' || sign == b'-')
939 {
940 if iter.next().unwrap() == b'-' {
941 -1.
942 } else {
943 1.
944 }
945 } else {
946 1.
947 };
948
949 let mut integral_part: f64 = 0.;
951 let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') {
952 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
954 return Err(());
955 }
956
957 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
958 integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64;
959 }
960
961 iter.peek().map_or(false, |&n| n == b'.')
962 } else {
963 true
964 };
965
966 let mut fractional_part: f64 = 0.;
968 if got_dot {
969 iter.next();
971 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
973 return Err(());
974 }
975
976 let mut factor = 0.1;
977 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
978 fractional_part += (iter.next().unwrap() - b'0') as f64 * factor;
979 factor *= 0.1;
980 }
981 }
982
983 let mut value = sign * (integral_part + fractional_part);
984
985 if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') {
988 iter.next();
990 let exp_sign = if iter
991 .peek()
992 .map_or(false, |&sign| sign == b'+' || sign == b'-')
993 {
994 if iter.next().unwrap() == b'-' {
995 -1.
996 } else {
997 1.
998 }
999 } else {
1000 1.
1001 };
1002
1003 let mut exp: f64 = 0.;
1004 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
1005 exp = exp * 10. + (iter.next().unwrap() - b'0') as f64;
1006 }
1007
1008 value *= f64::powf(10., exp * exp_sign);
1009 }
1010
1011 if value.is_finite() {
1012 Ok(value.min(f32::MAX as f64).max(f32::MIN as f64) as CSSFloat)
1013 } else {
1014 Err(())
1015 }
1016}
1017
1018#[inline]
1020fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1021 while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) {
1026 iter.next();
1027 }
1028 iter.peek().is_some()
1029}
1030
1031#[inline]
1033fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1034 if !skip_wsp(iter) {
1035 return false;
1036 }
1037
1038 if *iter.peek().unwrap() != b',' {
1039 return true;
1040 }
1041 iter.next();
1042
1043 skip_wsp(iter)
1044}