1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::values::CSSFloat;
10use crate::values::animated::{Animate, Procedure, lists};
11use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
12use crate::values::generics::basic_shape::GenericShapeCommand;
13use crate::values::generics::basic_shape::{
14 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, CommandEndPoint, ControlPoint, ControlReference,
15 CoordinatePair, RelativeControlPoint,
16};
17use crate::values::generics::position::GenericPosition;
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, 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, CSSFloat> {
561 pub fn to_abs(self, base: CSSFloat) -> AxisEndPoint<CSSFloat, CSSFloat> {
563 match self {
565 AxisEndPoint::ToPosition(_) => self,
566 AxisEndPoint::ByCoordinate(coord) => AxisEndPoint::ToPosition(coord + base),
567 }
568 }
569}
570
571impl ControlPoint<SVGPathPosition, CSSFloat> {
572 pub fn to_abs(
574 self,
575 state_pos: CoordPair,
576 end_point: CommandEndPoint<SVGPathPosition, CSSFloat>,
577 ) -> Self {
578 match self {
580 ControlPoint::Absolute(_) => self,
581 ControlPoint::Relative(point) => {
582 let mut pos = GenericPosition {
583 horizontal: point.coord.x,
584 vertical: point.coord.y,
585 };
586
587 match point.reference {
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<SVGPathPosition, CSSFloat>> for CoordPair {
606 #[inline]
607 fn from(p: CommandEndPoint<SVGPathPosition, 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<SVGPathPosition, CSSFloat>> for CoordPair {
619 #[inline]
620 fn from(point: ControlPoint<SVGPathPosition, 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<SVGPathPosition, CSSFloat> {
637 #[inline]
638 fn from(coord: CoordPair) -> Self {
639 CommandEndPoint::ByCoordinate(coord)
640 }
641}
642
643impl From<CoordPair> for SVGPathPosition {
644 #[inline]
645 fn from(coord: CoordPair) -> Self {
646 GenericPosition {
647 horizontal: coord.x,
648 vertical: coord.y,
649 }
650 }
651}
652
653impl From<AxisEndPoint<CSSFloat, CSSFloat>> for CSSFloat {
654 #[inline]
655 fn from(p: AxisEndPoint<CSSFloat, CSSFloat>) -> Self {
656 match p {
657 AxisEndPoint::ToPosition(a) | AxisEndPoint::ByCoordinate(a) => a,
658 }
659 }
660}
661
662impl ToCss for SVGPathPosition {
663 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
664 where
665 W: Write,
666 {
667 self.horizontal.to_css(dest)?;
668 dest.write_char(' ')?;
669 self.vertical.to_css(dest)
670 }
671}
672
673struct PathParser<'a> {
675 chars: Peekable<Cloned<slice::Iter<'a, u8>>>,
676 path: Vec<PathCommand>,
677}
678
679macro_rules! parse_arguments {
680 (
681 $parser:ident,
682 $enum:ident,
683 $( $field:ident : $value:expr, )*
684 [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
685 ) => {
686 {
687 loop {
688 let $para = $func(&mut $parser.chars)?;
689 $(
690 skip_comma_wsp(&mut $parser.chars);
691 let $other_para = $other_func(&mut $parser.chars)?;
692 )*
693 $parser.path.push(
694 PathCommand::$enum { $( $field: $value, )* $para $(, $other_para)* }
695 );
696
697 if !skip_wsp(&mut $parser.chars) ||
699 $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
700 break;
701 }
702 skip_comma_wsp(&mut $parser.chars);
703 }
704 Ok(())
705 }
706 }
707}
708
709impl<'a> PathParser<'a> {
710 #[inline]
712 fn new(bytes: &'a [u8]) -> Self {
713 PathParser {
714 chars: bytes.iter().cloned().peekable(),
715 path: Vec::with_capacity(bytes.len() / 32),
723 }
724 }
725
726 fn parse_subpath(&mut self) -> Result<(), ()> {
728 self.parse_moveto()?;
731
732 loop {
734 skip_wsp(&mut self.chars);
735 if self.chars.peek().is_none_or(|&m| m == b'M' || m == b'm') {
736 break;
737 }
738
739 let command = self.chars.next().unwrap();
740
741 skip_wsp(&mut self.chars);
742 match command {
743 b'Z' | b'z' => self.parse_closepath(),
744 b'L' => self.parse_line_abs(),
745 b'l' => self.parse_line_rel(),
746 b'H' => self.parse_h_line_abs(),
747 b'h' => self.parse_h_line_rel(),
748 b'V' => self.parse_v_line_abs(),
749 b'v' => self.parse_v_line_rel(),
750 b'C' => self.parse_curve_abs(),
751 b'c' => self.parse_curve_rel(),
752 b'S' => self.parse_smooth_curve_abs(),
753 b's' => self.parse_smooth_curve_rel(),
754 b'Q' => self.parse_quadratic_bezier_curve_abs(),
755 b'q' => self.parse_quadratic_bezier_curve_rel(),
756 b'T' => self.parse_smooth_quadratic_bezier_curve_abs(),
757 b't' => self.parse_smooth_quadratic_bezier_curve_rel(),
758 b'A' => self.parse_elliptical_arc_abs(),
759 b'a' => self.parse_elliptical_arc_rel(),
760 _ => return Err(()),
761 }?;
762 }
763 Ok(())
764 }
765
766 fn parse_moveto(&mut self) -> Result<(), ()> {
768 let command = match self.chars.next() {
769 Some(c) if c == b'M' || c == b'm' => c,
770 _ => return Err(()),
771 };
772
773 skip_wsp(&mut self.chars);
774 let point = if command == b'M' {
775 parse_command_end_abs(&mut self.chars)
776 } else {
777 parse_command_end_rel(&mut self.chars)
778 }?;
779 self.path.push(PathCommand::Move { point });
780
781 if !skip_wsp(&mut self.chars) || self.chars.peek().is_none_or(|c| c.is_ascii_alphabetic()) {
783 return Ok(());
784 }
785 skip_comma_wsp(&mut self.chars);
786
787 if point.is_abs() {
790 self.parse_line_abs()
791 } else {
792 self.parse_line_rel()
793 }
794 }
795
796 fn parse_closepath(&mut self) -> Result<(), ()> {
798 self.path.push(PathCommand::Close);
799 Ok(())
800 }
801
802 fn parse_line_abs(&mut self) -> Result<(), ()> {
804 parse_arguments!(self, Line, [ point => parse_command_end_abs ])
805 }
806
807 fn parse_line_rel(&mut self) -> Result<(), ()> {
809 parse_arguments!(self, Line, [ point => parse_command_end_rel ])
810 }
811
812 fn parse_h_line_abs(&mut self) -> Result<(), ()> {
814 parse_arguments!(self, HLine, [ x => parse_axis_end_abs ])
815 }
816
817 fn parse_h_line_rel(&mut self) -> Result<(), ()> {
819 parse_arguments!(self, HLine, [ x => parse_axis_end_rel ])
820 }
821
822 fn parse_v_line_abs(&mut self) -> Result<(), ()> {
824 parse_arguments!(self, VLine, [ y => parse_axis_end_abs ])
825 }
826
827 fn parse_v_line_rel(&mut self) -> Result<(), ()> {
829 parse_arguments!(self, VLine, [ y => parse_axis_end_rel ])
830 }
831
832 fn parse_curve_abs(&mut self) -> Result<(), ()> {
834 parse_arguments!(self, CubicCurve, [
835 control1 => parse_control_point_abs, control2 => parse_control_point_abs, point => parse_command_end_abs
836 ])
837 }
838
839 fn parse_curve_rel(&mut self) -> Result<(), ()> {
841 parse_arguments!(self, CubicCurve, [
842 control1 => parse_control_point_rel, control2 => parse_control_point_rel, point => parse_command_end_rel
843 ])
844 }
845
846 fn parse_smooth_curve_abs(&mut self) -> Result<(), ()> {
848 parse_arguments!(self, SmoothCubic, [
849 control2 => parse_control_point_abs, point => parse_command_end_abs
850 ])
851 }
852
853 fn parse_smooth_curve_rel(&mut self) -> Result<(), ()> {
855 parse_arguments!(self, SmoothCubic, [
856 control2 => parse_control_point_rel, point => parse_command_end_rel
857 ])
858 }
859
860 fn parse_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> {
862 parse_arguments!(self, QuadCurve, [
863 control1 => parse_control_point_abs, point => parse_command_end_abs
864 ])
865 }
866
867 fn parse_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> {
869 parse_arguments!(self, QuadCurve, [
870 control1 => parse_control_point_rel, point => parse_command_end_rel
871 ])
872 }
873
874 fn parse_smooth_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> {
876 parse_arguments!(self, SmoothQuad, [ point => parse_command_end_abs ])
877 }
878
879 fn parse_smooth_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> {
881 parse_arguments!(self, SmoothQuad, [ point => parse_command_end_rel ])
882 }
883
884 fn parse_elliptical_arc_abs(&mut self) -> Result<(), ()> {
886 let (parse_arc_size, parse_arc_sweep) = Self::arc_flag_parsers();
887 parse_arguments!(self, Arc, [
888 radii => parse_arc_radii,
889 rotate => parse_number,
890 arc_size => parse_arc_size,
891 arc_sweep => parse_arc_sweep,
892 point => parse_command_end_abs
893 ])
894 }
895
896 fn parse_elliptical_arc_rel(&mut self) -> Result<(), ()> {
898 let (parse_arc_size, parse_arc_sweep) = Self::arc_flag_parsers();
899 parse_arguments!(self, Arc, [
900 radii => parse_arc_radii,
901 rotate => parse_number,
902 arc_size => parse_arc_size,
903 arc_sweep => parse_arc_sweep,
904 point => parse_command_end_rel
905 ])
906 }
907
908 fn arc_flag_parsers() -> (
910 impl Fn(&mut Peekable<Cloned<slice::Iter<'_, u8>>>) -> Result<ArcSize, ()>,
911 impl Fn(&mut Peekable<Cloned<slice::Iter<'_, u8>>>) -> Result<ArcSweep, ()>,
912 ) {
913 let parse_arc_size = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
915 Some(c) if c == b'1' => Ok(ArcSize::Large),
916 Some(c) if c == b'0' => Ok(ArcSize::Small),
917 _ => Err(()),
918 };
919 let parse_arc_sweep = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
920 Some(c) if c == b'1' => Ok(ArcSweep::Cw),
921 Some(c) if c == b'0' => Ok(ArcSweep::Ccw),
922 _ => Err(()),
923 };
924 (parse_arc_size, parse_arc_sweep)
925 }
926}
927
928fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> {
930 let x = parse_number(iter)?;
931 skip_comma_wsp(iter);
932 let y = parse_number(iter)?;
933 Ok(CoordPair::new(x, y))
934}
935
936fn parse_command_end_abs(
938 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
939) -> Result<CommandEndPoint<SVGPathPosition, CSSFloat>, ()> {
940 let coord = parse_coord(iter)?;
941 Ok(CommandEndPoint::ToPosition(coord.into()))
942}
943
944fn parse_command_end_rel(
946 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
947) -> Result<CommandEndPoint<SVGPathPosition, CSSFloat>, ()> {
948 let coord = parse_coord(iter)?;
949 Ok(CommandEndPoint::ByCoordinate(coord))
950}
951
952fn parse_control_point_abs(
954 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
955) -> Result<ControlPoint<SVGPathPosition, CSSFloat>, ()> {
956 let coord = parse_coord(iter)?;
957 Ok(ControlPoint::Relative(RelativeControlPoint {
958 coord,
959 reference: ControlReference::Origin,
960 }))
961}
962
963fn parse_control_point_rel(
965 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
966) -> Result<ControlPoint<SVGPathPosition, CSSFloat>, ()> {
967 let coord = parse_coord(iter)?;
968 Ok(ControlPoint::Relative(RelativeControlPoint {
969 coord,
970 reference: ControlReference::Start,
971 }))
972}
973
974fn parse_axis_end_abs(
976 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
977) -> Result<AxisEndPoint<f32, f32>, ()> {
978 let value = parse_number(iter)?;
979 Ok(AxisEndPoint::ToPosition(value))
980}
981
982fn parse_axis_end_rel(
984 iter: &mut Peekable<Cloned<slice::Iter<u8>>>,
985) -> Result<AxisEndPoint<f32, f32>, ()> {
986 let value = parse_number(iter)?;
987 Ok(AxisEndPoint::ByCoordinate(value))
988}
989
990fn parse_arc_radii(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<ArcRadii<CSSFloat>, ()> {
992 let coord = parse_coord(iter)?;
993 Ok(ArcRadii {
994 rx: coord.x,
995 ry: Some(coord.y).into(),
996 })
997}
998
999fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> {
1007 let sign = if iter
1009 .peek()
1010 .is_some_and(|&sign| sign == b'+' || sign == b'-')
1011 {
1012 if iter.next().unwrap() == b'-' {
1013 -1.
1014 } else {
1015 1.
1016 }
1017 } else {
1018 1.
1019 };
1020
1021 let mut integral_part: f64 = 0.;
1023 let got_dot = if iter.peek().is_none_or(|&n| n != b'.') {
1024 if iter.peek().is_none_or(|n| !n.is_ascii_digit()) {
1026 return Err(());
1027 }
1028
1029 while iter.peek().is_some_and(|n| n.is_ascii_digit()) {
1030 integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64;
1031 }
1032
1033 iter.peek().is_some_and(|&n| n == b'.')
1034 } else {
1035 true
1036 };
1037
1038 let mut fractional_part: f64 = 0.;
1040 if got_dot {
1041 iter.next();
1043 if iter.peek().is_none_or(|n| !n.is_ascii_digit()) {
1045 return Err(());
1046 }
1047
1048 let mut factor = 0.1;
1049 while iter.peek().is_some_and(|n| n.is_ascii_digit()) {
1050 fractional_part += (iter.next().unwrap() - b'0') as f64 * factor;
1051 factor *= 0.1;
1052 }
1053 }
1054
1055 let mut value = sign * (integral_part + fractional_part);
1056
1057 if iter.peek().is_some_and(|&exp| exp == b'E' || exp == b'e') {
1060 iter.next();
1062 let exp_sign = if iter
1063 .peek()
1064 .is_some_and(|&sign| sign == b'+' || sign == b'-')
1065 {
1066 if iter.next().unwrap() == b'-' {
1067 -1.
1068 } else {
1069 1.
1070 }
1071 } else {
1072 1.
1073 };
1074
1075 let mut exp: f64 = 0.;
1076 while iter.peek().is_some_and(|n| n.is_ascii_digit()) {
1077 exp = exp * 10. + (iter.next().unwrap() - b'0') as f64;
1078 }
1079
1080 value *= f64::powf(10., exp * exp_sign);
1081 }
1082
1083 if value.is_finite() {
1084 Ok(value.min(f32::MAX as f64).max(f32::MIN as f64) as CSSFloat)
1085 } else {
1086 Err(())
1087 }
1088}
1089
1090#[inline]
1092fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1093 while iter.peek().is_some_and(|c| c.is_ascii_whitespace()) {
1098 iter.next();
1099 }
1100 iter.peek().is_some()
1101}
1102
1103#[inline]
1105fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
1106 if !skip_wsp(iter) {
1107 return false;
1108 }
1109
1110 if *iter.peek().unwrap() != b',' {
1111 return true;
1112 }
1113 iter.next();
1114
1115 skip_wsp(iter)
1116}