svg_path.rs (39196B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Specified types for SVG Path. 6 7 use crate::derives::*; 8 use crate::parser::{Parse, ParserContext}; 9 use crate::values::animated::{lists, Animate, Procedure}; 10 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; 11 use crate::values::generics::basic_shape::GenericShapeCommand; 12 use crate::values::generics::basic_shape::{ 13 ArcRadii, ArcSize, ArcSweep, AxisEndPoint, AxisPosition, CommandEndPoint, ControlPoint, 14 ControlReference, CoordinatePair, RelativeControlPoint, ShapePosition, 15 }; 16 use crate::values::generics::position::GenericPosition; 17 use crate::values::CSSFloat; 18 use cssparser::Parser; 19 use std::fmt::{self, Write}; 20 use std::iter::{Cloned, Peekable}; 21 use std::ops; 22 use std::slice; 23 use style_traits::values::SequenceWriter; 24 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; 25 26 /// Whether to allow empty string in the parser. 27 #[derive(Clone, Debug, Eq, PartialEq)] 28 #[allow(missing_docs)] 29 pub enum AllowEmpty { 30 Yes, 31 No, 32 } 33 34 /// The SVG path data. 35 /// 36 /// https://www.w3.org/TR/SVG11/paths.html#PathData 37 #[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)] 51 pub struct SVGPathData( 52 // TODO(emilio): Should probably measure this somehow only from the 53 // specified values. 54 #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>, 55 ); 56 57 impl SVGPathData { 58 /// Get the array of PathCommand. 59 #[inline] 60 pub fn commands(&self) -> &[PathCommand] { 61 &self.0 62 } 63 64 /// Create a normalized copy of this path by converting each relative 65 /// command to an absolute command. 66 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 /// Parse this SVG path string with the argument that indicates whether we should allow the 78 /// empty string. 79 // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make 80 // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.) 81 // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident 82 // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable 83 // str::Char iterator to check each character. 84 // 85 // css-shapes-1 says a path data string that does conform but defines an empty path is 86 // invalid and causes the entire path() to be invalid, so we use allow_empty to decide 87 // whether we should allow it. 88 // https://drafts.csswg.org/css-shapes-1/#typedef-basic-shape 89 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 /// As above, but just parsing the raw byte stream. 103 /// 104 /// Returns the (potentially empty or partial) path, and whether the parsing was ok or we found 105 /// an error. The API is a bit weird because some SVG callers require "parse until first error" 106 /// behavior. 107 pub fn parse_bytes(input: &[u8]) -> (Self, bool) { 108 // Parse the svg path string as multiple sub-paths. 109 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 /// Serializes to the path string, potentially including quotes. 124 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 142 impl 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, /* quote = */ true) 149 } 150 } 151 152 impl Parse for SVGPathData { 153 fn parse<'i, 't>( 154 _context: &ParserContext, 155 input: &mut Parser<'i, 't>, 156 ) -> Result<Self, ParseError<'i>> { 157 // Note that the EBNF allows the path data string in the d property to be empty, so we 158 // don't reject empty SVG path data. 159 // https://svgwg.org/svg2-draft/single-page.html#paths-PathDataBNF 160 SVGPathData::parse(input, AllowEmpty::Yes) 161 } 162 } 163 164 impl 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 // FIXME(emilio): This allocates three copies of the path, that's not 171 // great! Specially, once we're normalized once, we don't need to 172 // re-normalize again. 173 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 181 impl 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 192 /// The SVG path command. 193 /// The fields of these commands are self-explanatory, so we skip the documents. 194 /// Note: the index of the control points, e.g. control1, control2, are mapping to the control 195 /// points of the Bézier curve in the spec. 196 /// 197 /// https://www.w3.org/TR/SVG11/paths.html#PathData 198 pub type PathCommand = GenericShapeCommand<CSSFloat, ShapePosition<CSSFloat>, CSSFloat>; 199 200 /// For internal SVGPath normalization. 201 #[allow(missing_docs)] 202 struct PathTraversalState { 203 subpath_start: CoordPair, 204 pos: CoordPair, 205 last_command: PathCommand, 206 last_control: CoordPair, 207 } 208 209 impl PathCommand { 210 /// Create a normalized copy of this PathCommand. Absolute commands will be copied as-is while 211 /// for relative commands an equivalent absolute command will be returned. 212 /// 213 /// See discussion: https://github.com/w3c/svgwg/issues/321 214 /// If reduce is true then the path will be restricted to 215 /// "M", "L", "C", "A" and "Z" commands. 216 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 /// The serialization of the svg path. 410 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 492 /// The path coord type. 493 pub type CoordPair = CoordinatePair<CSSFloat>; 494 495 impl 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 506 impl 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 517 impl 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 528 impl ops::Mul<CoordPair> for CSSFloat { 529 type Output = CoordPair; 530 531 fn mul(self, rhs: CoordPair) -> CoordPair { 532 rhs * self 533 } 534 } 535 536 impl 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 547 impl CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat> { 548 /// Converts <command-end-point> into absolutely positioned type. 549 pub fn to_abs(self, state_pos: CoordPair) -> Self { 550 // Consume self value. 551 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 564 impl AxisEndPoint<CSSFloat> { 565 /// Converts possibly relative end point into absolutely positioned type. 566 pub fn to_abs(self, base: CSSFloat) -> AxisEndPoint<CSSFloat> { 567 // Consume self value. 568 match self { 569 AxisEndPoint::ToPosition(_) => self, 570 AxisEndPoint::ByCoordinate(coord) => { 571 AxisEndPoint::ToPosition(AxisPosition::LengthPercent(coord + base)) 572 }, 573 } 574 } 575 } 576 577 impl ControlPoint<ShapePosition<CSSFloat>, CSSFloat> { 578 /// Converts <control-point> into absolutely positioned control point type. 579 pub fn to_abs( 580 self, 581 state_pos: CoordPair, 582 end_point: CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat>, 583 ) -> Self { 584 // Consume self value. 585 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 611 impl 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 624 impl 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 642 impl From<CoordPair> for CommandEndPoint<ShapePosition<CSSFloat>, CSSFloat> { 643 #[inline] 644 fn from(coord: CoordPair) -> Self { 645 CommandEndPoint::ByCoordinate(coord) 646 } 647 } 648 649 impl 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 659 impl 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 672 impl 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 683 /// SVG Path parser. 684 struct PathParser<'a> { 685 chars: Peekable<Cloned<slice::Iter<'a, u8>>>, 686 path: Vec<PathCommand>, 687 } 688 689 macro_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 // End of string or the next character is a possible new command. 708 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 719 impl<'a> PathParser<'a> { 720 /// Return a PathParser. 721 #[inline] 722 fn new(bytes: &'a [u8]) -> Self { 723 PathParser { 724 chars: bytes.iter().cloned().peekable(), 725 path: Vec::new(), 726 } 727 } 728 729 /// Parse a sub-path. 730 fn parse_subpath(&mut self) -> Result<(), ()> { 731 // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path 732 // (i.e. not a valid moveto-drawto-command-group). 733 self.parse_moveto()?; 734 735 // Handle other commands. 736 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 /// Parse "moveto" command. 770 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 // End of string or the next character is a possible new command. 785 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 a moveto is followed by multiple pairs of coordinates, the subsequent 792 // pairs are treated as implicit lineto commands. 793 if point.is_abs() { 794 self.parse_line_abs() 795 } else { 796 self.parse_line_rel() 797 } 798 } 799 800 /// Parse "closepath" command. 801 fn parse_closepath(&mut self) -> Result<(), ()> { 802 self.path.push(PathCommand::Close); 803 Ok(()) 804 } 805 806 /// Parse an absolute "lineto" ("L") command. 807 fn parse_line_abs(&mut self) -> Result<(), ()> { 808 parse_arguments!(self, Line, [ point => parse_command_end_abs ]) 809 } 810 811 /// Parse a relative "lineto" ("l") command. 812 fn parse_line_rel(&mut self) -> Result<(), ()> { 813 parse_arguments!(self, Line, [ point => parse_command_end_rel ]) 814 } 815 816 /// Parse an absolute horizontal "lineto" ("H") command. 817 fn parse_h_line_abs(&mut self) -> Result<(), ()> { 818 parse_arguments!(self, HLine, [ x => parse_axis_end_abs ]) 819 } 820 821 /// Parse a relative horizontal "lineto" ("h") command. 822 fn parse_h_line_rel(&mut self) -> Result<(), ()> { 823 parse_arguments!(self, HLine, [ x => parse_axis_end_rel ]) 824 } 825 826 /// Parse an absolute vertical "lineto" ("V") command. 827 fn parse_v_line_abs(&mut self) -> Result<(), ()> { 828 parse_arguments!(self, VLine, [ y => parse_axis_end_abs ]) 829 } 830 831 /// Parse a relative vertical "lineto" ("v") command. 832 fn parse_v_line_rel(&mut self) -> Result<(), ()> { 833 parse_arguments!(self, VLine, [ y => parse_axis_end_rel ]) 834 } 835 836 /// Parse an absolute cubic Bézier curve ("C") command. 837 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 /// Parse a relative cubic Bézier curve ("c") command. 844 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 /// Parse an absolute smooth "curveto" ("S") command. 851 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 /// Parse a relative smooth "curveto" ("s") command. 858 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 /// Parse an absolute quadratic Bézier curve ("Q") command. 865 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 /// Parse a relative quadratic Bézier curve ("q") command. 872 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 /// Parse an absolute smooth quadratic Bézier curveto ("T") command. 879 fn parse_smooth_quadratic_bezier_curve_abs(&mut self) -> Result<(), ()> { 880 parse_arguments!(self, SmoothQuad, [ point => parse_command_end_abs ]) 881 } 882 883 /// Parse a relative smooth quadratic Bézier curveto ("t") command. 884 fn parse_smooth_quadratic_bezier_curve_rel(&mut self) -> Result<(), ()> { 885 parse_arguments!(self, SmoothQuad, [ point => parse_command_end_rel ]) 886 } 887 888 /// Parse an absolute elliptical arc curve ("A") command. 889 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 /// Parse a relative elliptical arc curve ("a") command. 901 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 /// Helper that returns parsers for the arc-size and arc-sweep flags. 913 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 // Parse a flag whose value is '0' or '1'; otherwise, return Err(()). 918 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 932 /// Parse a pair of numbers into CoordPair. 933 fn 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 940 /// Parse a pair of numbers that describes the absolutely positioned end point. 941 fn 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 948 /// Parse a pair of numbers that describes the relatively positioned end point. 949 fn 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 956 /// Parse a pair of values that describe the absolutely positioned curve control point. 957 fn 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 967 /// Parse a pair of values that describe the relatively positioned curve control point. 968 fn 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 978 /// Parse a number that describes the absolutely positioned axis end point. 979 fn 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 986 /// Parse a number that describes the relatively positioned axis end point. 987 fn 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 994 /// Parse a pair of numbers that describes the size of the ellipse that the arc is taken from. 995 fn 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 1003 /// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed 1004 /// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating 1005 /// point number. In other words, the logic here is similar with that of 1006 /// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the 1007 /// input is a Peekable and we only accept an integer of a floating point number. 1008 /// 1009 /// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF 1010 fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> { 1011 // 1. Check optional sign. 1012 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 // 2. Check integer part. 1026 let mut integral_part: f64 = 0.; 1027 let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') { 1028 // If the first digit in integer part is neither a dot nor a digit, this is not a number. 1029 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 // 3. Check fractional part. 1043 let mut fractional_part: f64 = 0.; 1044 if got_dot { 1045 // Consume '.'. 1046 iter.next(); 1047 // If the first digit in fractional part is not a digit, this is not a number. 1048 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 // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to 1062 // treat the numbers after 'E' or 'e' are in the exponential part. 1063 if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') { 1064 // Consume 'E' or 'e'. 1065 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 /// Skip all svg whitespaces, and return true if |iter| hasn't finished. 1095 #[inline] 1096 fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool { 1097 // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}. 1098 // However, SVG 2 has one extra whitespace: \u{C}. 1099 // Therefore, we follow the newest spec for the definition of whitespace, 1100 // i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}. 1101 while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) { 1102 iter.next(); 1103 } 1104 iter.peek().is_some() 1105 } 1106 1107 /// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished. 1108 #[inline] 1109 fn 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 }