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::{ArcSize, ArcSweep, ByTo, CoordinatePair};
12use crate::values::CSSFloat;
13use cssparser::Parser;
14use std::fmt::{self, Write};
15use std::iter::{Cloned, Peekable};
16use std::ops;
17use std::slice;
18use style_traits::values::SequenceWriter;
19use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
20
21#[derive(Clone, Debug, Eq, PartialEq)]
23#[allow(missing_docs)]
24pub enum AllowEmpty {
25 Yes,
26 No,
27}
28
29#[derive(
33 Clone,
34 Debug,
35 Deserialize,
36 MallocSizeOf,
37 PartialEq,
38 Serialize,
39 SpecifiedValueInfo,
40 ToAnimatedZero,
41 ToComputedValue,
42 ToResolvedValue,
43 ToShmem,
44)]
45#[repr(C)]
46pub struct SVGPathData(
47 #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>,
50);
51
52impl SVGPathData {
53 #[inline]
55 pub fn commands(&self) -> &[PathCommand] {
56 &self.0
57 }
58
59 pub fn normalize(&self, reduce: bool) -> Self {
62 let mut state = PathTraversalState {
63 subpath_start: CoordPair::new(0.0, 0.0),
64 pos: CoordPair::new(0.0, 0.0),
65 last_command: PathCommand::Close,
66 last_control: CoordPair::new(0.0, 0.0),
67 };
68 let iter = self.0.iter().map(|seg| seg.normalize(&mut state, reduce));
69 SVGPathData(crate::ArcSlice::from_iter(iter))
70 }
71
72 pub fn parse<'i, 't>(
85 input: &mut Parser<'i, 't>,
86 allow_empty: AllowEmpty,
87 ) -> Result<Self, ParseError<'i>> {
88 let location = input.current_source_location();
89 let path_string = input.expect_string()?.as_ref();
90 let (path, ok) = Self::parse_bytes(path_string.as_bytes());
91 if !ok || (allow_empty == AllowEmpty::No && path.0.is_empty()) {
92 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
93 }
94 return Ok(path);
95 }
96
97 pub fn parse_bytes(input: &[u8]) -> (Self, bool) {
103 let mut ok = true;
105 let mut path_parser = PathParser::new(input);
106
107 while skip_wsp(&mut path_parser.chars) {
108 if path_parser.parse_subpath().is_err() {
109 ok = false;
110 break;
111 }
112 }
113
114 let path = Self(crate::ArcSlice::from_iter(path_parser.path.into_iter()));
115 (path, ok)
116 }
117
118 pub fn to_css<W>(&self, dest: &mut CssWriter<W>, quote: bool) -> fmt::Result
120 where
121 W: fmt::Write,
122 {
123 if quote {
124 dest.write_char('"')?;
125 }
126 let mut writer = SequenceWriter::new(dest, " ");
127 for command in self.commands() {
128 writer.write_item(|inner| command.to_css_for_svg(inner))?;
129 }
130 if quote {
131 dest.write_char('"')?;
132 }
133 Ok(())
134 }
135}
136
137impl ToCss for SVGPathData {
138 #[inline]
139 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
140 where
141 W: fmt::Write,
142 {
143 self.to_css(dest, true)
144 }
145}
146
147impl Parse for SVGPathData {
148 fn parse<'i, 't>(
149 _context: &ParserContext,
150 input: &mut Parser<'i, 't>,
151 ) -> Result<Self, ParseError<'i>> {
152 SVGPathData::parse(input, AllowEmpty::Yes)
156 }
157}
158
159impl Animate for SVGPathData {
160 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
161 if self.0.len() != other.0.len() {
162 return Err(());
163 }
164
165 let left = self.normalize(false);
169 let right = other.normalize(false);
170
171 let items: Vec<_> = lists::by_computed_value::animate(&left.0, &right.0, procedure)?;
172 Ok(SVGPathData(crate::ArcSlice::from_iter(items.into_iter())))
173 }
174}
175
176impl ComputeSquaredDistance for SVGPathData {
177 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
178 if self.0.len() != other.0.len() {
179 return Err(());
180 }
181 let left = self.normalize(false);
182 let right = other.normalize(false);
183 lists::by_computed_value::squared_distance(&left.0, &right.0)
184 }
185}
186
187pub type PathCommand = GenericShapeCommand<CSSFloat, CSSFloat>;
194
195#[allow(missing_docs)]
197struct PathTraversalState {
198 subpath_start: CoordPair,
199 pos: CoordPair,
200 last_command: PathCommand,
201 last_control: CoordPair,
202}
203
204impl PathCommand {
205 fn normalize(&self, state: &mut PathTraversalState, reduce: bool) -> Self {
212 use crate::values::generics::basic_shape::GenericShapeCommand::*;
213 match *self {
214 Close => {
215 state.pos = state.subpath_start;
216 if reduce {
217 state.last_command = *self;
218 }
219 Close
220 },
221 Move { by_to, mut point } => {
222 if !by_to.is_abs() {
223 point += state.pos;
224 }
225 state.pos = point;
226 state.subpath_start = point;
227 if reduce {
228 state.last_command = *self;
229 }
230 Move {
231 by_to: ByTo::To,
232 point,
233 }
234 },
235 Line { by_to, mut point } => {
236 if !by_to.is_abs() {
237 point += state.pos;
238 }
239 state.pos = point;
240 if reduce {
241 state.last_command = *self;
242 }
243 Line {
244 by_to: ByTo::To,
245 point,
246 }
247 },
248 HLine { by_to, mut x } => {
249 if !by_to.is_abs() {
250 x += state.pos.x;
251 }
252 state.pos.x = x;
253 if reduce {
254 state.last_command = *self;
255 PathCommand::Line {
256 by_to: ByTo::To,
257 point: state.pos,
258 }
259 } else {
260 HLine { by_to: ByTo::To, x }
261 }
262 },
263 VLine { by_to, mut y } => {
264 if !by_to.is_abs() {
265 y += state.pos.y;
266 }
267 state.pos.y = y;
268 if reduce {
269 state.last_command = *self;
270 PathCommand::Line {
271 by_to: ByTo::To,
272 point: state.pos,
273 }
274 } else {
275 VLine { by_to: ByTo::To, y }
276 }
277 },
278 CubicCurve {
279 by_to,
280 mut point,
281 mut control1,
282 mut control2,
283 } => {
284 if !by_to.is_abs() {
285 point += state.pos;
286 control1 += state.pos;
287 control2 += state.pos;
288 }
289 state.pos = point;
290 if reduce {
291 state.last_command = *self;
292 state.last_control = control2;
293 }
294 CubicCurve {
295 by_to: ByTo::To,
296 point,
297 control1,
298 control2,
299 }
300 },
301 QuadCurve {
302 by_to,
303 mut point,
304 mut control1,
305 } => {
306 if !by_to.is_abs() {
307 point += state.pos;
308 control1 += state.pos;
309 }
310 if reduce {
311 let c1 = state.pos + 2. * (control1 - state.pos) / 3.;
312 let control2 = point + 2. * (control1 - point) / 3.;
313 state.pos = point;
314 state.last_command = *self;
315 state.last_control = control1;
316 CubicCurve {
317 by_to: ByTo::To,
318 point,
319 control1: c1,
320 control2,
321 }
322 } else {
323 state.pos = point;
324 QuadCurve {
325 by_to: ByTo::To,
326 point,
327 control1,
328 }
329 }
330 },
331 SmoothCubic {
332 by_to,
333 mut point,
334 mut control2,
335 } => {
336 if !by_to.is_abs() {
337 point += state.pos;
338 control2 += state.pos;
339 }
340 if reduce {
341 let control1 = match state.last_command {
342 PathCommand::CubicCurve {
343 by_to: _,
344 point: _,
345 control1: _,
346 control2: _,
347 }
348 | PathCommand::SmoothCubic {
349 by_to: _,
350 point: _,
351 control2: _,
352 } => state.pos + state.pos - state.last_control,
353 _ => state.pos,
354 };
355 state.pos = point;
356 state.last_control = control2;
357 state.last_command = *self;
358 CubicCurve {
359 by_to: ByTo::To,
360 point,
361 control1,
362 control2,
363 }
364 } else {
365 state.pos = point;
366 SmoothCubic {
367 by_to: ByTo::To,
368 point,
369 control2,
370 }
371 }
372 },
373 SmoothQuad { by_to, mut point } => {
374 if !by_to.is_abs() {
375 point += state.pos;
376 }
377 if reduce {
378 let control = match state.last_command {
379 PathCommand::QuadCurve {
380 by_to: _,
381 point: _,
382 control1: _,
383 }
384 | PathCommand::SmoothQuad { by_to: _, point: _ } => {
385 state.pos + state.pos - state.last_control
386 },
387 _ => state.pos,
388 };
389 let control1 = state.pos + 2. * (control - state.pos) / 3.;
390 let control2 = point + 2. * (control - point) / 3.;
391 state.pos = point;
392 state.last_command = *self;
393 state.last_control = control;
394 CubicCurve {
395 by_to: ByTo::To,
396 point,
397 control1,
398 control2,
399 }
400 } else {
401 state.pos = point;
402 SmoothQuad {
403 by_to: ByTo::To,
404 point,
405 }
406 }
407 },
408 Arc {
409 by_to,
410 mut point,
411 radii,
412 arc_sweep,
413 arc_size,
414 rotate,
415 } => {
416 if !by_to.is_abs() {
417 point += state.pos;
418 }
419 state.pos = point;
420 if reduce {
421 state.last_command = *self;
422 if radii.x == 0. && radii.y == 0. {
423 CubicCurve {
424 by_to: ByTo::To,
425 point: state.pos,
426 control1: point,
427 control2: point,
428 }
429 } else {
430 Arc {
431 by_to: ByTo::To,
432 point,
433 radii,
434 arc_sweep,
435 arc_size,
436 rotate,
437 }
438 }
439 } else {
440 Arc {
441 by_to: ByTo::To,
442 point,
443 radii,
444 arc_sweep,
445 arc_size,
446 rotate,
447 }
448 }
449 },
450 }
451 }
452
453 fn to_css_for_svg<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
455 where
456 W: fmt::Write,
457 {
458 use crate::values::generics::basic_shape::GenericShapeCommand::*;
459 match *self {
460 Close => dest.write_char('Z'),
461 Move { by_to, point } => {
462 dest.write_char(if by_to.is_abs() { 'M' } else { 'm' })?;
463 dest.write_char(' ')?;
464 point.to_css(dest)
465 },
466 Line { by_to, point } => {
467 dest.write_char(if by_to.is_abs() { 'L' } else { 'l' })?;
468 dest.write_char(' ')?;
469 point.to_css(dest)
470 },
471 CubicCurve {
472 by_to,
473 point,
474 control1,
475 control2,
476 } => {
477 dest.write_char(if by_to.is_abs() { 'C' } else { 'c' })?;
478 dest.write_char(' ')?;
479 control1.to_css(dest)?;
480 dest.write_char(' ')?;
481 control2.to_css(dest)?;
482 dest.write_char(' ')?;
483 point.to_css(dest)
484 },
485 QuadCurve {
486 by_to,
487 point,
488 control1,
489 } => {
490 dest.write_char(if by_to.is_abs() { 'Q' } else { 'q' })?;
491 dest.write_char(' ')?;
492 control1.to_css(dest)?;
493 dest.write_char(' ')?;
494 point.to_css(dest)
495 },
496 Arc {
497 by_to,
498 point,
499 radii,
500 arc_sweep,
501 arc_size,
502 rotate,
503 } => {
504 dest.write_char(if by_to.is_abs() { 'A' } else { 'a' })?;
505 dest.write_char(' ')?;
506 radii.to_css(dest)?;
507 dest.write_char(' ')?;
508 rotate.to_css(dest)?;
509 dest.write_char(' ')?;
510 (arc_size as i32).to_css(dest)?;
511 dest.write_char(' ')?;
512 (arc_sweep as i32).to_css(dest)?;
513 dest.write_char(' ')?;
514 point.to_css(dest)
515 },
516 HLine { by_to, x } => {
517 dest.write_char(if by_to.is_abs() { 'H' } else { 'h' })?;
518 dest.write_char(' ')?;
519 x.to_css(dest)
520 },
521 VLine { by_to, y } => {
522 dest.write_char(if by_to.is_abs() { 'V' } else { 'v' })?;
523 dest.write_char(' ')?;
524 y.to_css(dest)
525 },
526 SmoothCubic {
527 by_to,
528 point,
529 control2,
530 } => {
531 dest.write_char(if by_to.is_abs() { 'S' } else { 's' })?;
532 dest.write_char(' ')?;
533 control2.to_css(dest)?;
534 dest.write_char(' ')?;
535 point.to_css(dest)
536 },
537 SmoothQuad { by_to, point } => {
538 dest.write_char(if by_to.is_abs() { 'T' } else { 't' })?;
539 dest.write_char(' ')?;
540 point.to_css(dest)
541 },
542 }
543 }
544}
545
546pub type CoordPair = CoordinatePair<CSSFloat>;
548
549impl ops::Add<CoordPair> for CoordPair {
550 type Output = CoordPair;
551
552 fn add(self, rhs: CoordPair) -> CoordPair {
553 Self {
554 x: self.x + rhs.x,
555 y: self.y + rhs.y,
556 }
557 }
558}
559
560impl ops::Sub<CoordPair> for CoordPair {
561 type Output = CoordPair;
562
563 fn sub(self, rhs: CoordPair) -> CoordPair {
564 Self {
565 x: self.x - rhs.x,
566 y: self.y - rhs.y,
567 }
568 }
569}
570
571impl ops::Mul<CSSFloat> for CoordPair {
572 type Output = CoordPair;
573
574 fn mul(self, f: CSSFloat) -> CoordPair {
575 Self {
576 x: self.x * f,
577 y: self.y * f,
578 }
579 }
580}
581
582impl ops::Mul<CoordPair> for CSSFloat {
583 type Output = CoordPair;
584
585 fn mul(self, rhs: CoordPair) -> CoordPair {
586 rhs * self
587 }
588}
589
590impl ops::Div<CSSFloat> for CoordPair {
591 type Output = CoordPair;
592
593 fn div(self, f: CSSFloat) -> CoordPair {
594 Self {
595 x: self.x / f,
596 y: self.y / f,
597 }
598 }
599}
600
601struct PathParser<'a> {
603 chars: Peekable<Cloned<slice::Iter<'a, u8>>>,
604 path: Vec<PathCommand>,
605}
606
607macro_rules! parse_arguments {
608 (
609 $parser:ident,
610 $by_to:ident,
611 $enum:ident,
612 [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
613 ) => {
614 {
615 loop {
616 let $para = $func(&mut $parser.chars)?;
617 $(
618 skip_comma_wsp(&mut $parser.chars);
619 let $other_para = $other_func(&mut $parser.chars)?;
620 )*
621 $parser.path.push(
622 PathCommand::$enum { $by_to, $para $(, $other_para)* }
623 );
624
625 if !skip_wsp(&mut $parser.chars) ||
627 $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
628 break;
629 }
630 skip_comma_wsp(&mut $parser.chars);
631 }
632 Ok(())
633 }
634 }
635}
636
637impl<'a> PathParser<'a> {
638 #[inline]
640 fn new(bytes: &'a [u8]) -> Self {
641 PathParser {
642 chars: bytes.iter().cloned().peekable(),
643 path: Vec::new(),
644 }
645 }
646
647 fn parse_subpath(&mut self) -> Result<(), ()> {
649 self.parse_moveto()?;
652
653 loop {
655 skip_wsp(&mut self.chars);
656 if self.chars.peek().map_or(true, |&m| m == b'M' || m == b'm') {
657 break;
658 }
659
660 let command = self.chars.next().unwrap();
661 let by_to = if command.is_ascii_uppercase() {
662 ByTo::To
663 } else {
664 ByTo::By
665 };
666
667 skip_wsp(&mut self.chars);
668 match command {
669 b'Z' | b'z' => self.parse_closepath(),
670 b'L' | b'l' => self.parse_lineto(by_to),
671 b'H' | b'h' => self.parse_h_lineto(by_to),
672 b'V' | b'v' => self.parse_v_lineto(by_to),
673 b'C' | b'c' => self.parse_curveto(by_to),
674 b'S' | b's' => self.parse_smooth_curveto(by_to),
675 b'Q' | b'q' => self.parse_quadratic_bezier_curveto(by_to),
676 b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(by_to),
677 b'A' | b'a' => self.parse_elliptical_arc(by_to),
678 _ => return Err(()),
679 }?;
680 }
681 Ok(())
682 }
683
684 fn parse_moveto(&mut self) -> Result<(), ()> {
686 let command = match self.chars.next() {
687 Some(c) if c == b'M' || c == b'm' => c,
688 _ => return Err(()),
689 };
690
691 skip_wsp(&mut self.chars);
692 let point = parse_coord(&mut self.chars)?;
693 let by_to = if command == b'M' { ByTo::To } else { ByTo::By };
694 self.path.push(PathCommand::Move { by_to, point });
695
696 if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic())
698 {
699 return Ok(());
700 }
701 skip_comma_wsp(&mut self.chars);
702
703 self.parse_lineto(by_to)
706 }
707
708 fn parse_closepath(&mut self) -> Result<(), ()> {
710 self.path.push(PathCommand::Close);
711 Ok(())
712 }
713
714 fn parse_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
716 parse_arguments!(self, by_to, Line, [ point => parse_coord ])
717 }
718
719 fn parse_h_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
721 parse_arguments!(self, by_to, HLine, [ x => parse_number ])
722 }
723
724 fn parse_v_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
726 parse_arguments!(self, by_to, VLine, [ y => parse_number ])
727 }
728
729 fn parse_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
731 parse_arguments!(self, by_to, CubicCurve, [
732 control1 => parse_coord, control2 => parse_coord, point => parse_coord
733 ])
734 }
735
736 fn parse_smooth_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
738 parse_arguments!(self, by_to, SmoothCubic, [
739 control2 => parse_coord, point => parse_coord
740 ])
741 }
742
743 fn parse_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
745 parse_arguments!(self, by_to, QuadCurve, [
746 control1 => parse_coord, point => parse_coord
747 ])
748 }
749
750 fn parse_smooth_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
752 parse_arguments!(self, by_to, SmoothQuad, [ point => parse_coord ])
753 }
754
755 fn parse_elliptical_arc(&mut self, by_to: ByTo) -> Result<(), ()> {
757 let parse_arc_size = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
759 Some(c) if c == b'1' => Ok(ArcSize::Large),
760 Some(c) if c == b'0' => Ok(ArcSize::Small),
761 _ => Err(()),
762 };
763 let parse_arc_sweep = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
764 Some(c) if c == b'1' => Ok(ArcSweep::Cw),
765 Some(c) if c == b'0' => Ok(ArcSweep::Ccw),
766 _ => Err(()),
767 };
768 parse_arguments!(self, by_to, Arc, [
769 radii => parse_coord,
770 rotate => parse_number,
771 arc_size => parse_arc_size,
772 arc_sweep => parse_arc_sweep,
773 point => parse_coord
774 ])
775 }
776}
777
778fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> {
780 let x = parse_number(iter)?;
781 skip_comma_wsp(iter);
782 let y = parse_number(iter)?;
783 Ok(CoordPair::new(x, y))
784}
785
786fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> {
794 let sign = if iter
796 .peek()
797 .map_or(false, |&sign| sign == b'+' || sign == b'-')
798 {
799 if iter.next().unwrap() == b'-' {
800 -1.
801 } else {
802 1.
803 }
804 } else {
805 1.
806 };
807
808 let mut integral_part: f64 = 0.;
810 let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') {
811 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
813 return Err(());
814 }
815
816 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
817 integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64;
818 }
819
820 iter.peek().map_or(false, |&n| n == b'.')
821 } else {
822 true
823 };
824
825 let mut fractional_part: f64 = 0.;
827 if got_dot {
828 iter.next();
830 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
832 return Err(());
833 }
834
835 let mut factor = 0.1;
836 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
837 fractional_part += (iter.next().unwrap() - b'0') as f64 * factor;
838 factor *= 0.1;
839 }
840 }
841
842 let mut value = sign * (integral_part + fractional_part);
843
844 if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') {
847 iter.next();
849 let exp_sign = if iter
850 .peek()
851 .map_or(false, |&sign| sign == b'+' || sign == b'-')
852 {
853 if iter.next().unwrap() == b'-' {
854 -1.
855 } else {
856 1.
857 }
858 } else {
859 1.
860 };
861
862 let mut exp: f64 = 0.;
863 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
864 exp = exp * 10. + (iter.next().unwrap() - b'0') as f64;
865 }
866
867 value *= f64::powf(10., exp * exp_sign);
868 }
869
870 if value.is_finite() {
871 Ok(value.min(f32::MAX as f64).max(f32::MIN as f64) as CSSFloat)
872 } else {
873 Err(())
874 }
875}
876
877#[inline]
879fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
880 while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) {
885 iter.next();
886 }
887 iter.peek().is_some()
888}
889
890#[inline]
892fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
893 if !skip_wsp(iter) {
894 return false;
895 }
896
897 if *iter.peek().unwrap() != b',' {
898 return true;
899 }
900 iter.next();
901
902 skip_wsp(iter)
903}