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