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