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 { by_to: ByTo::To, point: state.pos }
256 } else {
257 HLine { by_to: ByTo::To, x }
258 }
259 },
260 VLine { by_to, mut y } => {
261 if !by_to.is_abs() {
262 y += state.pos.y;
263 }
264 state.pos.y = y;
265 if reduce {
266 state.last_command = *self;
267 PathCommand::Line { by_to: ByTo::To, point: state.pos }
268 } else {
269 VLine { by_to: ByTo::To, y }
270 }
271 },
272 CubicCurve {
273 by_to,
274 mut point,
275 mut control1,
276 mut control2,
277 } => {
278 if !by_to.is_abs() {
279 point += state.pos;
280 control1 += state.pos;
281 control2 += state.pos;
282 }
283 state.pos = point;
284 if reduce {
285 state.last_command = *self;
286 state.last_control = control2;
287 }
288 CubicCurve {
289 by_to: ByTo::To,
290 point,
291 control1,
292 control2,
293 }
294 },
295 QuadCurve {
296 by_to,
297 mut point,
298 mut control1,
299 } => {
300 if !by_to.is_abs() {
301 point += state.pos;
302 control1 += state.pos;
303 }
304 if reduce {
305 let c1 = state.pos + 2. * (control1 - state.pos) / 3.;
306 let control2 = point + 2. * (control1 - point) / 3.;
307 state.pos = point;
308 state.last_command = *self;
309 state.last_control = control1;
310 CubicCurve {
311 by_to: ByTo::To,
312 point,
313 control1: c1,
314 control2,
315 }
316 } else {
317 state.pos = point;
318 QuadCurve {
319 by_to: ByTo::To,
320 point,
321 control1,
322 }
323 }
324 },
325 SmoothCubic {
326 by_to,
327 mut point,
328 mut control2,
329 } => {
330 if !by_to.is_abs() {
331 point += state.pos;
332 control2 += state.pos;
333 }
334 if reduce {
335 let control1 = match state.last_command {
336 PathCommand::CubicCurve { by_to: _, point: _, control1: _, control2: _ } | PathCommand::SmoothCubic { by_to: _, point: _, control2: _ } =>
337 state.pos + state.pos - state.last_control,
338 _ => state.pos
339 };
340 state.pos = point;
341 state.last_control = control2;
342 state.last_command = *self;
343 CubicCurve {
344 by_to: ByTo::To,
345 point,
346 control1,
347 control2,
348 }
349 } else {
350 state.pos = point;
351 SmoothCubic {
352 by_to: ByTo::To,
353 point,
354 control2,
355 }
356 }
357 },
358 SmoothQuad { by_to, mut point } => {
359 if !by_to.is_abs() {
360 point += state.pos;
361 }
362 if reduce {
363 let control = match state.last_command {
364 PathCommand::QuadCurve { by_to: _, point: _, control1: _ } | PathCommand::SmoothQuad { by_to: _, point: _ } =>
365 state.pos + state.pos - state.last_control,
366 _ => state.pos
367 };
368 let control1 = state.pos + 2. * (control - state.pos) / 3.;
369 let control2 = point + 2. * (control - point) / 3.;
370 state.pos = point;
371 state.last_command = *self;
372 state.last_control = control;
373 CubicCurve {
374 by_to: ByTo::To,
375 point,
376 control1,
377 control2,
378 }
379 } else {
380 state.pos = point;
381 SmoothQuad {
382 by_to: ByTo::To,
383 point,
384 }
385 }
386 },
387 Arc {
388 by_to,
389 mut point,
390 radii,
391 arc_sweep,
392 arc_size,
393 rotate,
394 } => {
395 if !by_to.is_abs() {
396 point += state.pos;
397 }
398 state.pos = point;
399 if reduce {
400 state.last_command = *self;
401 if radii.x == 0. && radii.y == 0. {
402 CubicCurve {
403 by_to: ByTo::To,
404 point: state.pos,
405 control1: point,
406 control2: point,
407 }
408 } else {
409 Arc {
410 by_to: ByTo::To,
411 point,
412 radii,
413 arc_sweep,
414 arc_size,
415 rotate,
416 }
417 }
418 } else {
419 Arc {
420 by_to: ByTo::To,
421 point,
422 radii,
423 arc_sweep,
424 arc_size,
425 rotate,
426 }
427 }
428 },
429 }
430 }
431
432 fn to_css_for_svg<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
434 where
435 W: fmt::Write,
436 {
437 use crate::values::generics::basic_shape::GenericShapeCommand::*;
438 match *self {
439 Close => dest.write_char('Z'),
440 Move { by_to, point } => {
441 dest.write_char(if by_to.is_abs() { 'M' } else { 'm' })?;
442 dest.write_char(' ')?;
443 point.to_css(dest)
444 },
445 Line { by_to, point } => {
446 dest.write_char(if by_to.is_abs() { 'L' } else { 'l' })?;
447 dest.write_char(' ')?;
448 point.to_css(dest)
449 },
450 CubicCurve {
451 by_to,
452 point,
453 control1,
454 control2,
455 } => {
456 dest.write_char(if by_to.is_abs() { 'C' } else { 'c' })?;
457 dest.write_char(' ')?;
458 control1.to_css(dest)?;
459 dest.write_char(' ')?;
460 control2.to_css(dest)?;
461 dest.write_char(' ')?;
462 point.to_css(dest)
463 },
464 QuadCurve {
465 by_to,
466 point,
467 control1,
468 } => {
469 dest.write_char(if by_to.is_abs() { 'Q' } else { 'q' })?;
470 dest.write_char(' ')?;
471 control1.to_css(dest)?;
472 dest.write_char(' ')?;
473 point.to_css(dest)
474 },
475 Arc {
476 by_to,
477 point,
478 radii,
479 arc_sweep,
480 arc_size,
481 rotate,
482 } => {
483 dest.write_char(if by_to.is_abs() { 'A' } else { 'a' })?;
484 dest.write_char(' ')?;
485 radii.to_css(dest)?;
486 dest.write_char(' ')?;
487 rotate.to_css(dest)?;
488 dest.write_char(' ')?;
489 (arc_size as i32).to_css(dest)?;
490 dest.write_char(' ')?;
491 (arc_sweep as i32).to_css(dest)?;
492 dest.write_char(' ')?;
493 point.to_css(dest)
494 },
495 HLine { by_to, x } => {
496 dest.write_char(if by_to.is_abs() { 'H' } else { 'h' })?;
497 dest.write_char(' ')?;
498 x.to_css(dest)
499 },
500 VLine { by_to, y } => {
501 dest.write_char(if by_to.is_abs() { 'V' } else { 'v' })?;
502 dest.write_char(' ')?;
503 y.to_css(dest)
504 },
505 SmoothCubic {
506 by_to,
507 point,
508 control2,
509 } => {
510 dest.write_char(if by_to.is_abs() { 'S' } else { 's' })?;
511 dest.write_char(' ')?;
512 control2.to_css(dest)?;
513 dest.write_char(' ')?;
514 point.to_css(dest)
515 },
516 SmoothQuad { by_to, point } => {
517 dest.write_char(if by_to.is_abs() { 'T' } else { 't' })?;
518 dest.write_char(' ')?;
519 point.to_css(dest)
520 },
521 }
522 }
523}
524
525pub type CoordPair = CoordinatePair<CSSFloat>;
527
528impl ops::Add<CoordPair> for CoordPair {
529 type Output = CoordPair;
530
531 fn add(self, rhs: CoordPair) -> CoordPair {
532 Self {
533 x: self.x + rhs.x,
534 y: self.y + rhs.y,
535 }
536 }
537}
538
539impl ops::Sub<CoordPair> for CoordPair {
540 type Output = CoordPair;
541
542 fn sub(self, rhs: CoordPair) -> CoordPair {
543 Self {
544 x: self.x - rhs.x,
545 y: self.y - rhs.y,
546 }
547 }
548}
549
550impl ops::Mul<CSSFloat> for CoordPair {
551 type Output = CoordPair;
552
553 fn mul(self, f: CSSFloat) -> CoordPair {
554 Self {
555 x: self.x * f,
556 y: self.y * f,
557 }
558 }
559}
560
561impl ops::Mul<CoordPair> for CSSFloat {
562 type Output = CoordPair;
563
564 fn mul(self, rhs: CoordPair) -> CoordPair {
565 rhs * self
566 }
567}
568
569impl ops::Div<CSSFloat> for CoordPair {
570 type Output = CoordPair;
571
572 fn div(self, f: CSSFloat) -> CoordPair {
573 Self {
574 x: self.x / f,
575 y: self.y / f,
576 }
577 }
578}
579
580struct PathParser<'a> {
582 chars: Peekable<Cloned<slice::Iter<'a, u8>>>,
583 path: Vec<PathCommand>,
584}
585
586macro_rules! parse_arguments {
587 (
588 $parser:ident,
589 $by_to:ident,
590 $enum:ident,
591 [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
592 ) => {
593 {
594 loop {
595 let $para = $func(&mut $parser.chars)?;
596 $(
597 skip_comma_wsp(&mut $parser.chars);
598 let $other_para = $other_func(&mut $parser.chars)?;
599 )*
600 $parser.path.push(
601 PathCommand::$enum { $by_to, $para $(, $other_para)* }
602 );
603
604 if !skip_wsp(&mut $parser.chars) ||
606 $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
607 break;
608 }
609 skip_comma_wsp(&mut $parser.chars);
610 }
611 Ok(())
612 }
613 }
614}
615
616impl<'a> PathParser<'a> {
617 #[inline]
619 fn new(bytes: &'a [u8]) -> Self {
620 PathParser {
621 chars: bytes.iter().cloned().peekable(),
622 path: Vec::new(),
623 }
624 }
625
626 fn parse_subpath(&mut self) -> Result<(), ()> {
628 self.parse_moveto()?;
631
632 loop {
634 skip_wsp(&mut self.chars);
635 if self.chars.peek().map_or(true, |&m| m == b'M' || m == b'm') {
636 break;
637 }
638
639 let command = self.chars.next().unwrap();
640 let by_to = if command.is_ascii_uppercase() {
641 ByTo::To
642 } else {
643 ByTo::By
644 };
645
646 skip_wsp(&mut self.chars);
647 match command {
648 b'Z' | b'z' => self.parse_closepath(),
649 b'L' | b'l' => self.parse_lineto(by_to),
650 b'H' | b'h' => self.parse_h_lineto(by_to),
651 b'V' | b'v' => self.parse_v_lineto(by_to),
652 b'C' | b'c' => self.parse_curveto(by_to),
653 b'S' | b's' => self.parse_smooth_curveto(by_to),
654 b'Q' | b'q' => self.parse_quadratic_bezier_curveto(by_to),
655 b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(by_to),
656 b'A' | b'a' => self.parse_elliptical_arc(by_to),
657 _ => return Err(()),
658 }?;
659 }
660 Ok(())
661 }
662
663 fn parse_moveto(&mut self) -> Result<(), ()> {
665 let command = match self.chars.next() {
666 Some(c) if c == b'M' || c == b'm' => c,
667 _ => return Err(()),
668 };
669
670 skip_wsp(&mut self.chars);
671 let point = parse_coord(&mut self.chars)?;
672 let by_to = if command == b'M' { ByTo::To } else { ByTo::By };
673 self.path.push(PathCommand::Move { by_to, point });
674
675 if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic())
677 {
678 return Ok(());
679 }
680 skip_comma_wsp(&mut self.chars);
681
682 self.parse_lineto(by_to)
685 }
686
687 fn parse_closepath(&mut self) -> Result<(), ()> {
689 self.path.push(PathCommand::Close);
690 Ok(())
691 }
692
693 fn parse_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
695 parse_arguments!(self, by_to, Line, [ point => parse_coord ])
696 }
697
698 fn parse_h_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
700 parse_arguments!(self, by_to, HLine, [ x => parse_number ])
701 }
702
703 fn parse_v_lineto(&mut self, by_to: ByTo) -> Result<(), ()> {
705 parse_arguments!(self, by_to, VLine, [ y => parse_number ])
706 }
707
708 fn parse_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
710 parse_arguments!(self, by_to, CubicCurve, [
711 control1 => parse_coord, control2 => parse_coord, point => parse_coord
712 ])
713 }
714
715 fn parse_smooth_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
717 parse_arguments!(self, by_to, SmoothCubic, [
718 control2 => parse_coord, point => parse_coord
719 ])
720 }
721
722 fn parse_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
724 parse_arguments!(self, by_to, QuadCurve, [
725 control1 => parse_coord, point => parse_coord
726 ])
727 }
728
729 fn parse_smooth_quadratic_bezier_curveto(&mut self, by_to: ByTo) -> Result<(), ()> {
731 parse_arguments!(self, by_to, SmoothQuad, [ point => parse_coord ])
732 }
733
734 fn parse_elliptical_arc(&mut self, by_to: ByTo) -> Result<(), ()> {
736 let parse_arc_size = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
738 Some(c) if c == b'1' => Ok(ArcSize::Large),
739 Some(c) if c == b'0' => Ok(ArcSize::Small),
740 _ => Err(()),
741 };
742 let parse_arc_sweep = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() {
743 Some(c) if c == b'1' => Ok(ArcSweep::Cw),
744 Some(c) if c == b'0' => Ok(ArcSweep::Ccw),
745 _ => Err(()),
746 };
747 parse_arguments!(self, by_to, Arc, [
748 radii => parse_coord,
749 rotate => parse_number,
750 arc_size => parse_arc_size,
751 arc_sweep => parse_arc_sweep,
752 point => parse_coord
753 ])
754 }
755}
756
757fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> {
759 let x = parse_number(iter)?;
760 skip_comma_wsp(iter);
761 let y = parse_number(iter)?;
762 Ok(CoordPair::new(x, y))
763}
764
765fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> {
773 let sign = if iter
775 .peek()
776 .map_or(false, |&sign| sign == b'+' || sign == b'-')
777 {
778 if iter.next().unwrap() == b'-' {
779 -1.
780 } else {
781 1.
782 }
783 } else {
784 1.
785 };
786
787 let mut integral_part: f64 = 0.;
789 let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') {
790 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
792 return Err(());
793 }
794
795 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
796 integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64;
797 }
798
799 iter.peek().map_or(false, |&n| n == b'.')
800 } else {
801 true
802 };
803
804 let mut fractional_part: f64 = 0.;
806 if got_dot {
807 iter.next();
809 if iter.peek().map_or(true, |n| !n.is_ascii_digit()) {
811 return Err(());
812 }
813
814 let mut factor = 0.1;
815 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
816 fractional_part += (iter.next().unwrap() - b'0') as f64 * factor;
817 factor *= 0.1;
818 }
819 }
820
821 let mut value = sign * (integral_part + fractional_part);
822
823 if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') {
826 iter.next();
828 let exp_sign = if iter
829 .peek()
830 .map_or(false, |&sign| sign == b'+' || sign == b'-')
831 {
832 if iter.next().unwrap() == b'-' {
833 -1.
834 } else {
835 1.
836 }
837 } else {
838 1.
839 };
840
841 let mut exp: f64 = 0.;
842 while iter.peek().map_or(false, |n| n.is_ascii_digit()) {
843 exp = exp * 10. + (iter.next().unwrap() - b'0') as f64;
844 }
845
846 value *= f64::powf(10., exp * exp_sign);
847 }
848
849 if value.is_finite() {
850 Ok(value.min(f32::MAX as f64).max(f32::MIN as f64) as CSSFloat)
851 } else {
852 Err(())
853 }
854}
855
856#[inline]
858fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
859 while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) {
864 iter.next();
865 }
866 iter.peek().is_some()
867}
868
869#[inline]
871fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool {
872 if !skip_wsp(iter) {
873 return false;
874 }
875
876 if *iter.peek().unwrap() != b',' {
877 return true;
878 }
879 iter.next();
880
881 skip_wsp(iter)
882}