basic_shape.rs (36741B)
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 //! CSS handling for the [`basic-shape`](https://drafts.csswg.org/css-shapes/#typedef-basic-shape) 6 //! types that are generic over their `ToCss` implementations. 7 8 use crate::derives::*; 9 use crate::values::animated::{lists, Animate, Procedure, ToAnimatedZero}; 10 use crate::values::computed::Percentage; 11 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; 12 use crate::values::generics::{ 13 border::GenericBorderRadius, 14 position::{GenericPosition, GenericPositionOrAuto}, 15 rect::Rect, 16 NonNegative, Optional, 17 }; 18 use crate::values::specified::svg_path::{PathCommand, SVGPathData}; 19 use crate::Zero; 20 use std::fmt::{self, Write}; 21 use style_traits::{CssWriter, ToCss}; 22 23 /// A generic value for `<position>` in circle(), ellipse(), and shape(). 24 pub type ShapePosition<LengthPercentage> = GenericPosition<LengthPercentage, LengthPercentage>; 25 26 /// <https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box> 27 #[allow(missing_docs)] 28 #[derive( 29 Animate, 30 Clone, 31 ComputeSquaredDistance, 32 Copy, 33 Debug, 34 MallocSizeOf, 35 PartialEq, 36 Parse, 37 SpecifiedValueInfo, 38 ToAnimatedValue, 39 ToComputedValue, 40 ToCss, 41 ToResolvedValue, 42 ToShmem, 43 ToTyped, 44 )] 45 #[repr(u8)] 46 #[typed_value(derive_fields)] 47 pub enum ShapeGeometryBox { 48 /// Depending on which kind of element this style value applied on, the 49 /// default value of the reference-box can be different. For an HTML 50 /// element, the default value of reference-box is border-box; for an SVG 51 /// element, the default value is fill-box. Since we can not determine the 52 /// default value at parsing time, we keep this value to make a decision on 53 /// it. 54 #[css(skip)] 55 ElementDependent, 56 FillBox, 57 StrokeBox, 58 ViewBox, 59 ShapeBox(ShapeBox), 60 } 61 62 impl Default for ShapeGeometryBox { 63 fn default() -> Self { 64 Self::ElementDependent 65 } 66 } 67 68 /// Skip the serialization if the author omits the box or specifies border-box. 69 #[inline] 70 fn is_default_box_for_clip_path(b: &ShapeGeometryBox) -> bool { 71 // Note: for clip-path, ElementDependent is always border-box, so we have to check both of them 72 // for serialization. 73 matches!(b, ShapeGeometryBox::ElementDependent) 74 || matches!(b, ShapeGeometryBox::ShapeBox(ShapeBox::BorderBox)) 75 } 76 77 /// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box 78 #[allow(missing_docs)] 79 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 80 #[derive( 81 Animate, 82 Clone, 83 Copy, 84 ComputeSquaredDistance, 85 Debug, 86 Eq, 87 MallocSizeOf, 88 Parse, 89 PartialEq, 90 SpecifiedValueInfo, 91 ToAnimatedValue, 92 ToComputedValue, 93 ToCss, 94 ToResolvedValue, 95 ToShmem, 96 ToTyped, 97 )] 98 #[repr(u8)] 99 pub enum ShapeBox { 100 MarginBox, 101 BorderBox, 102 PaddingBox, 103 ContentBox, 104 } 105 106 impl Default for ShapeBox { 107 fn default() -> Self { 108 ShapeBox::MarginBox 109 } 110 } 111 112 /// A value for the `clip-path` property. 113 #[allow(missing_docs)] 114 #[derive( 115 Animate, 116 Clone, 117 ComputeSquaredDistance, 118 Debug, 119 MallocSizeOf, 120 PartialEq, 121 SpecifiedValueInfo, 122 ToAnimatedValue, 123 ToComputedValue, 124 ToCss, 125 ToResolvedValue, 126 ToShmem, 127 ToTyped, 128 )] 129 #[animation(no_bound(U))] 130 #[repr(u8)] 131 #[typed_value(derive_fields)] 132 pub enum GenericClipPath<BasicShape, U> { 133 #[animation(error)] 134 None, 135 #[animation(error)] 136 // XXX This will likely change to skip since it seems Typed OM Level 1 137 // won't be updated to cover this case even though there's some preparation 138 // in WPT tests for this. 139 #[typed_value(todo)] 140 Url(U), 141 #[typed_value(skip)] 142 Shape( 143 #[animation(field_bound)] Box<BasicShape>, 144 #[css(skip_if = "is_default_box_for_clip_path")] ShapeGeometryBox, 145 ), 146 #[animation(error)] 147 Box(ShapeGeometryBox), 148 } 149 150 pub use self::GenericClipPath as ClipPath; 151 152 /// A value for the `shape-outside` property. 153 #[allow(missing_docs)] 154 #[derive( 155 Animate, 156 Clone, 157 ComputeSquaredDistance, 158 Debug, 159 MallocSizeOf, 160 PartialEq, 161 SpecifiedValueInfo, 162 ToAnimatedValue, 163 ToComputedValue, 164 ToCss, 165 ToResolvedValue, 166 ToShmem, 167 ToTyped, 168 )] 169 #[animation(no_bound(I))] 170 #[repr(u8)] 171 pub enum GenericShapeOutside<BasicShape, I> { 172 #[animation(error)] 173 None, 174 #[animation(error)] 175 Image(I), 176 Shape(Box<BasicShape>, #[css(skip_if = "is_default")] ShapeBox), 177 #[animation(error)] 178 Box(ShapeBox), 179 } 180 181 pub use self::GenericShapeOutside as ShapeOutside; 182 183 /// The <basic-shape>. 184 /// 185 /// https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes 186 #[derive( 187 Animate, 188 Clone, 189 ComputeSquaredDistance, 190 Debug, 191 Deserialize, 192 MallocSizeOf, 193 PartialEq, 194 Serialize, 195 SpecifiedValueInfo, 196 ToAnimatedValue, 197 ToComputedValue, 198 ToCss, 199 ToResolvedValue, 200 ToShmem, 201 )] 202 #[repr(C, u8)] 203 pub enum GenericBasicShape<Angle, Position, LengthPercentage, BasicShapeRect> { 204 /// The <basic-shape-rect>. 205 Rect(BasicShapeRect), 206 /// Defines a circle with a center and a radius. 207 Circle( 208 #[animation(field_bound)] 209 #[css(field_bound)] 210 #[shmem(field_bound)] 211 Circle<LengthPercentage>, 212 ), 213 /// Defines an ellipse with a center and x-axis/y-axis radii. 214 Ellipse( 215 #[animation(field_bound)] 216 #[css(field_bound)] 217 #[shmem(field_bound)] 218 Ellipse<LengthPercentage>, 219 ), 220 /// Defines a polygon with pair arguments. 221 Polygon(GenericPolygon<LengthPercentage>), 222 /// Defines a path() or shape(). 223 PathOrShape( 224 #[animation(field_bound)] 225 #[css(field_bound)] 226 #[compute(field_bound)] 227 GenericPathOrShapeFunction<Angle, Position, LengthPercentage>, 228 ), 229 } 230 231 pub use self::GenericBasicShape as BasicShape; 232 233 /// <https://drafts.csswg.org/css-shapes/#funcdef-inset> 234 #[allow(missing_docs)] 235 #[derive( 236 Animate, 237 Clone, 238 ComputeSquaredDistance, 239 Debug, 240 Deserialize, 241 MallocSizeOf, 242 PartialEq, 243 Serialize, 244 SpecifiedValueInfo, 245 ToAnimatedValue, 246 ToComputedValue, 247 ToResolvedValue, 248 ToShmem, 249 )] 250 #[css(function = "inset")] 251 #[repr(C)] 252 pub struct GenericInsetRect<LengthPercentage> { 253 pub rect: Rect<LengthPercentage>, 254 #[shmem(field_bound)] 255 #[animation(field_bound)] 256 pub round: GenericBorderRadius<NonNegative<LengthPercentage>>, 257 } 258 259 pub use self::GenericInsetRect as InsetRect; 260 261 /// <https://drafts.csswg.org/css-shapes/#funcdef-circle> 262 #[allow(missing_docs)] 263 #[derive( 264 Animate, 265 Clone, 266 ComputeSquaredDistance, 267 Copy, 268 Debug, 269 Deserialize, 270 MallocSizeOf, 271 PartialEq, 272 Serialize, 273 SpecifiedValueInfo, 274 ToAnimatedValue, 275 ToComputedValue, 276 ToResolvedValue, 277 ToShmem, 278 )] 279 #[css(function)] 280 #[repr(C)] 281 pub struct Circle<LengthPercentage> { 282 pub position: GenericPositionOrAuto<ShapePosition<LengthPercentage>>, 283 #[animation(field_bound)] 284 pub radius: GenericShapeRadius<LengthPercentage>, 285 } 286 287 /// <https://drafts.csswg.org/css-shapes/#funcdef-ellipse> 288 #[allow(missing_docs)] 289 #[derive( 290 Animate, 291 Clone, 292 ComputeSquaredDistance, 293 Copy, 294 Debug, 295 Deserialize, 296 MallocSizeOf, 297 PartialEq, 298 Serialize, 299 SpecifiedValueInfo, 300 ToAnimatedValue, 301 ToComputedValue, 302 ToResolvedValue, 303 ToShmem, 304 )] 305 #[css(function)] 306 #[repr(C)] 307 pub struct Ellipse<LengthPercentage> { 308 pub position: GenericPositionOrAuto<ShapePosition<LengthPercentage>>, 309 #[animation(field_bound)] 310 pub semiaxis_x: GenericShapeRadius<LengthPercentage>, 311 #[animation(field_bound)] 312 pub semiaxis_y: GenericShapeRadius<LengthPercentage>, 313 } 314 315 /// <https://drafts.csswg.org/css-shapes/#typedef-shape-radius> 316 #[allow(missing_docs)] 317 #[derive( 318 Animate, 319 Clone, 320 ComputeSquaredDistance, 321 Copy, 322 Debug, 323 Deserialize, 324 MallocSizeOf, 325 Parse, 326 PartialEq, 327 Serialize, 328 SpecifiedValueInfo, 329 ToAnimatedValue, 330 ToComputedValue, 331 ToCss, 332 ToResolvedValue, 333 ToShmem, 334 )] 335 #[repr(C, u8)] 336 pub enum GenericShapeRadius<LengthPercentage> { 337 Length( 338 #[animation(field_bound)] 339 #[parse(field_bound)] 340 NonNegative<LengthPercentage>, 341 ), 342 #[animation(error)] 343 ClosestSide, 344 #[animation(error)] 345 FarthestSide, 346 } 347 348 pub use self::GenericShapeRadius as ShapeRadius; 349 350 /// A generic type for representing the `polygon()` function 351 /// 352 /// <https://drafts.csswg.org/css-shapes/#funcdef-polygon> 353 #[derive( 354 Clone, 355 Debug, 356 Deserialize, 357 MallocSizeOf, 358 PartialEq, 359 Serialize, 360 SpecifiedValueInfo, 361 ToAnimatedValue, 362 ToComputedValue, 363 ToCss, 364 ToResolvedValue, 365 ToShmem, 366 )] 367 #[css(comma, function = "polygon")] 368 #[repr(C)] 369 pub struct GenericPolygon<LengthPercentage> { 370 /// The filling rule for a polygon. 371 #[css(skip_if = "is_default")] 372 pub fill: FillRule, 373 /// A collection of (x, y) coordinates to draw the polygon. 374 #[css(iterable)] 375 pub coordinates: crate::OwnedSlice<PolygonCoord<LengthPercentage>>, 376 } 377 378 pub use self::GenericPolygon as Polygon; 379 380 /// Coordinates for Polygon. 381 #[derive( 382 Animate, 383 Clone, 384 ComputeSquaredDistance, 385 Debug, 386 Deserialize, 387 MallocSizeOf, 388 PartialEq, 389 Serialize, 390 SpecifiedValueInfo, 391 ToAnimatedValue, 392 ToComputedValue, 393 ToCss, 394 ToResolvedValue, 395 ToShmem, 396 )] 397 #[repr(C)] 398 pub struct PolygonCoord<LengthPercentage>(pub LengthPercentage, pub LengthPercentage); 399 400 /// path() function or shape() function. 401 #[derive( 402 Clone, 403 ComputeSquaredDistance, 404 Debug, 405 Deserialize, 406 MallocSizeOf, 407 PartialEq, 408 Serialize, 409 SpecifiedValueInfo, 410 ToAnimatedValue, 411 ToComputedValue, 412 ToCss, 413 ToResolvedValue, 414 ToShmem, 415 )] 416 #[repr(C, u8)] 417 pub enum GenericPathOrShapeFunction<Angle, Position, LengthPercentage> { 418 /// Defines a path with SVG path syntax. 419 Path(Path), 420 /// Defines a shape function, which is identical to path() but it uses the CSS syntax. 421 Shape( 422 #[css(field_bound)] 423 #[compute(field_bound)] 424 Shape<Angle, Position, LengthPercentage>, 425 ), 426 } 427 428 // https://drafts.csswg.org/css-shapes/#typedef-fill-rule 429 // NOTE: Basic shapes spec says that these are the only two values, however 430 // https://www.w3.org/TR/SVG/painting.html#FillRuleProperty 431 // says that it can also be `inherit` 432 #[allow(missing_docs)] 433 #[derive( 434 Animate, 435 Clone, 436 ComputeSquaredDistance, 437 Copy, 438 Debug, 439 Deserialize, 440 Eq, 441 MallocSizeOf, 442 Parse, 443 PartialEq, 444 Serialize, 445 SpecifiedValueInfo, 446 ToAnimatedValue, 447 ToComputedValue, 448 ToCss, 449 ToResolvedValue, 450 ToShmem, 451 ToTyped, 452 )] 453 #[repr(u8)] 454 pub enum FillRule { 455 Nonzero, 456 Evenodd, 457 } 458 459 /// The path function. 460 /// 461 /// https://drafts.csswg.org/css-shapes-1/#funcdef-basic-shape-path 462 #[derive( 463 Animate, 464 Clone, 465 ComputeSquaredDistance, 466 Debug, 467 Deserialize, 468 MallocSizeOf, 469 PartialEq, 470 Serialize, 471 SpecifiedValueInfo, 472 ToAnimatedValue, 473 ToComputedValue, 474 ToCss, 475 ToResolvedValue, 476 ToShmem, 477 )] 478 #[css(comma, function = "path")] 479 #[repr(C)] 480 pub struct Path { 481 /// The filling rule for the svg path. 482 #[css(skip_if = "is_default")] 483 pub fill: FillRule, 484 /// The svg path data. 485 pub path: SVGPathData, 486 } 487 488 impl Path { 489 /// Returns the slice of PathCommand. 490 #[inline] 491 pub fn commands(&self) -> &[PathCommand] { 492 self.path.commands() 493 } 494 } 495 496 impl<B, U> ToAnimatedZero for ClipPath<B, U> { 497 fn to_animated_zero(&self) -> Result<Self, ()> { 498 Err(()) 499 } 500 } 501 502 impl<B, U> ToAnimatedZero for ShapeOutside<B, U> { 503 fn to_animated_zero(&self) -> Result<Self, ()> { 504 Err(()) 505 } 506 } 507 508 impl<Length> ToCss for InsetRect<Length> 509 where 510 Length: ToCss + PartialEq + Zero, 511 { 512 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 513 where 514 W: Write, 515 { 516 dest.write_str("inset(")?; 517 self.rect.to_css(dest)?; 518 if !self.round.is_zero() { 519 dest.write_str(" round ")?; 520 self.round.to_css(dest)?; 521 } 522 dest.write_char(')') 523 } 524 } 525 526 impl<LengthPercentage> ToCss for Circle<LengthPercentage> 527 where 528 LengthPercentage: ToCss + PartialEq, 529 ShapePosition<LengthPercentage>: ToCss, 530 { 531 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 532 where 533 W: Write, 534 { 535 let has_radius = self.radius != Default::default(); 536 537 dest.write_str("circle(")?; 538 if has_radius { 539 self.radius.to_css(dest)?; 540 } 541 542 // Preserve the `at <position>` even if it specified the default value. 543 // https://github.com/w3c/csswg-drafts/issues/8695 544 if !matches!(self.position, GenericPositionOrAuto::Auto) { 545 if has_radius { 546 dest.write_char(' ')?; 547 } 548 dest.write_str("at ")?; 549 self.position.to_css(dest)?; 550 } 551 dest.write_char(')') 552 } 553 } 554 555 impl<LengthPercentage> ToCss for Ellipse<LengthPercentage> 556 where 557 LengthPercentage: ToCss + PartialEq, 558 ShapePosition<LengthPercentage>: ToCss, 559 { 560 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 561 where 562 W: Write, 563 { 564 let has_radii = 565 self.semiaxis_x != Default::default() || self.semiaxis_y != Default::default(); 566 567 dest.write_str("ellipse(")?; 568 if has_radii { 569 self.semiaxis_x.to_css(dest)?; 570 dest.write_char(' ')?; 571 self.semiaxis_y.to_css(dest)?; 572 } 573 574 // Preserve the `at <position>` even if it specified the default value. 575 // https://github.com/w3c/csswg-drafts/issues/8695 576 if !matches!(self.position, GenericPositionOrAuto::Auto) { 577 if has_radii { 578 dest.write_char(' ')?; 579 } 580 dest.write_str("at ")?; 581 self.position.to_css(dest)?; 582 } 583 dest.write_char(')') 584 } 585 } 586 587 impl<L> Default for ShapeRadius<L> { 588 #[inline] 589 fn default() -> Self { 590 ShapeRadius::ClosestSide 591 } 592 } 593 594 impl<L> Animate for Polygon<L> 595 where 596 L: Animate, 597 { 598 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { 599 if self.fill != other.fill { 600 return Err(()); 601 } 602 let coordinates = 603 lists::by_computed_value::animate(&self.coordinates, &other.coordinates, procedure)?; 604 Ok(Polygon { 605 fill: self.fill, 606 coordinates, 607 }) 608 } 609 } 610 611 impl<L> ComputeSquaredDistance for Polygon<L> 612 where 613 L: ComputeSquaredDistance, 614 { 615 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { 616 if self.fill != other.fill { 617 return Err(()); 618 } 619 lists::by_computed_value::squared_distance(&self.coordinates, &other.coordinates) 620 } 621 } 622 623 impl Default for FillRule { 624 #[inline] 625 fn default() -> Self { 626 FillRule::Nonzero 627 } 628 } 629 630 #[inline] 631 fn is_default<T: Default + PartialEq>(fill: &T) -> bool { 632 *fill == Default::default() 633 } 634 635 /// The shape function defined in css-shape-2. 636 /// shape() = shape(<fill-rule>? from <coordinate-pair>, <shape-command>#) 637 /// 638 /// https://drafts.csswg.org/css-shapes-2/#shape-function 639 #[derive( 640 Clone, 641 Debug, 642 Deserialize, 643 MallocSizeOf, 644 PartialEq, 645 Serialize, 646 SpecifiedValueInfo, 647 ToAnimatedValue, 648 ToComputedValue, 649 ToResolvedValue, 650 ToShmem, 651 )] 652 #[repr(C)] 653 pub struct Shape<Angle, Position, LengthPercentage> { 654 /// The filling rule for this shape. 655 pub fill: FillRule, 656 /// The shape command data. Note that the starting point will be the first command in this 657 /// slice. 658 // Note: The first command is always GenericShapeCommand::Move. 659 #[compute(field_bound)] 660 pub commands: crate::OwnedSlice<GenericShapeCommand<Angle, Position, LengthPercentage>>, 661 } 662 663 impl<Angle, Position, LengthPercentage> Shape<Angle, Position, LengthPercentage> { 664 /// Returns the slice of GenericShapeCommand<..>. 665 #[inline] 666 pub fn commands(&self) -> &[GenericShapeCommand<Angle, Position, LengthPercentage>] { 667 &self.commands 668 } 669 } 670 671 impl<Angle, Position, LengthPercentage> Animate for Shape<Angle, Position, LengthPercentage> 672 where 673 Angle: Animate, 674 Position: Animate, 675 LengthPercentage: Animate, 676 { 677 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { 678 if self.fill != other.fill { 679 return Err(()); 680 } 681 let commands = 682 lists::by_computed_value::animate(&self.commands, &other.commands, procedure)?; 683 Ok(Self { 684 fill: self.fill, 685 commands, 686 }) 687 } 688 } 689 690 impl<Angle, Position, LengthPercentage> ComputeSquaredDistance 691 for Shape<Angle, Position, LengthPercentage> 692 where 693 Angle: ComputeSquaredDistance, 694 Position: ComputeSquaredDistance, 695 LengthPercentage: ComputeSquaredDistance, 696 { 697 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { 698 if self.fill != other.fill { 699 return Err(()); 700 } 701 lists::by_computed_value::squared_distance(&self.commands, &other.commands) 702 } 703 } 704 705 impl<Angle, Position, LengthPercentage> ToCss for Shape<Angle, Position, LengthPercentage> 706 where 707 Angle: ToCss + Zero, 708 Position: ToCss, 709 LengthPercentage: PartialEq + ToCss, 710 { 711 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 712 where 713 W: Write, 714 { 715 use style_traits::values::SequenceWriter; 716 717 // Per spec, we must have the first move command and at least one following command. 718 debug_assert!(self.commands.len() > 1); 719 720 dest.write_str("shape(")?; 721 if !is_default(&self.fill) { 722 self.fill.to_css(dest)?; 723 dest.write_char(' ')?; 724 } 725 dest.write_str("from ")?; 726 match &self.commands[0] { 727 ShapeCommand::Move { 728 point: CommandEndPoint::ToPosition(pos), 729 } => pos.to_css(dest)?, 730 ShapeCommand::Move { 731 point: CommandEndPoint::ByCoordinate(coord), 732 } => coord.to_css(dest)?, 733 _ => unreachable!("The first command must be move"), 734 } 735 dest.write_str(", ")?; 736 { 737 let mut writer = SequenceWriter::new(dest, ", "); 738 for command in self.commands.iter().skip(1) { 739 writer.item(command)?; 740 } 741 } 742 dest.write_char(')') 743 } 744 } 745 746 /// This is a more general shape(path) command type, for both shape() and path(). 747 /// 748 /// https://www.w3.org/TR/SVG11/paths.html#PathData 749 /// https://drafts.csswg.org/css-shapes-2/#shape-function 750 #[derive( 751 Animate, 752 Clone, 753 ComputeSquaredDistance, 754 Copy, 755 Debug, 756 Deserialize, 757 MallocSizeOf, 758 PartialEq, 759 Serialize, 760 SpecifiedValueInfo, 761 ToAnimatedValue, 762 ToAnimatedZero, 763 ToComputedValue, 764 ToResolvedValue, 765 ToShmem, 766 )] 767 #[allow(missing_docs)] 768 #[repr(C, u8)] 769 pub enum GenericShapeCommand<Angle, Position, LengthPercentage> { 770 /// The move command. 771 Move { 772 point: CommandEndPoint<Position, LengthPercentage>, 773 }, 774 /// The line command. 775 Line { 776 point: CommandEndPoint<Position, LengthPercentage>, 777 }, 778 /// The hline command. 779 HLine { 780 #[compute(field_bound)] 781 x: AxisEndPoint<LengthPercentage>, 782 }, 783 /// The vline command. 784 VLine { 785 #[compute(field_bound)] 786 y: AxisEndPoint<LengthPercentage>, 787 }, 788 /// The cubic Bézier curve command. 789 CubicCurve { 790 point: CommandEndPoint<Position, LengthPercentage>, 791 control1: ControlPoint<Position, LengthPercentage>, 792 control2: ControlPoint<Position, LengthPercentage>, 793 }, 794 /// The quadratic Bézier curve command. 795 QuadCurve { 796 point: CommandEndPoint<Position, LengthPercentage>, 797 control1: ControlPoint<Position, LengthPercentage>, 798 }, 799 /// The smooth command. 800 SmoothCubic { 801 point: CommandEndPoint<Position, LengthPercentage>, 802 control2: ControlPoint<Position, LengthPercentage>, 803 }, 804 /// The smooth quadratic Bézier curve command. 805 SmoothQuad { 806 point: CommandEndPoint<Position, LengthPercentage>, 807 }, 808 /// The arc command. 809 Arc { 810 point: CommandEndPoint<Position, LengthPercentage>, 811 radii: ArcRadii<LengthPercentage>, 812 arc_sweep: ArcSweep, 813 arc_size: ArcSize, 814 rotate: Angle, 815 }, 816 /// The closepath command. 817 Close, 818 } 819 820 pub use self::GenericShapeCommand as ShapeCommand; 821 822 impl<Angle, Position, LengthPercentage> ToCss for ShapeCommand<Angle, Position, LengthPercentage> 823 where 824 Angle: ToCss + Zero, 825 Position: ToCss, 826 LengthPercentage: PartialEq + ToCss, 827 { 828 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 829 where 830 W: fmt::Write, 831 { 832 use self::ShapeCommand::*; 833 match *self { 834 Move { ref point } => { 835 dest.write_str("move ")?; 836 point.to_css(dest) 837 }, 838 Line { ref point } => { 839 dest.write_str("line ")?; 840 point.to_css(dest) 841 }, 842 HLine { ref x } => { 843 dest.write_str("hline ")?; 844 x.to_css(dest) 845 }, 846 VLine { ref y } => { 847 dest.write_str("vline ")?; 848 y.to_css(dest) 849 }, 850 CubicCurve { 851 ref point, 852 ref control1, 853 ref control2, 854 } => { 855 dest.write_str("curve ")?; 856 point.to_css(dest)?; 857 dest.write_str(" with ")?; 858 control1.to_css(dest, point.is_abs())?; 859 dest.write_char(' ')?; 860 dest.write_char('/')?; 861 dest.write_char(' ')?; 862 control2.to_css(dest, point.is_abs()) 863 }, 864 QuadCurve { 865 ref point, 866 ref control1, 867 } => { 868 dest.write_str("curve ")?; 869 point.to_css(dest)?; 870 dest.write_str(" with ")?; 871 control1.to_css(dest, point.is_abs()) 872 }, 873 SmoothCubic { 874 ref point, 875 ref control2, 876 } => { 877 dest.write_str("smooth ")?; 878 point.to_css(dest)?; 879 dest.write_str(" with ")?; 880 control2.to_css(dest, point.is_abs()) 881 }, 882 SmoothQuad { ref point } => { 883 dest.write_str("smooth ")?; 884 point.to_css(dest) 885 }, 886 Arc { 887 ref point, 888 ref radii, 889 arc_sweep, 890 arc_size, 891 ref rotate, 892 } => { 893 dest.write_str("arc ")?; 894 point.to_css(dest)?; 895 dest.write_str(" of ")?; 896 radii.to_css(dest)?; 897 898 if matches!(arc_sweep, ArcSweep::Cw) { 899 dest.write_str(" cw")?; 900 } 901 902 if matches!(arc_size, ArcSize::Large) { 903 dest.write_str(" large")?; 904 } 905 906 if !rotate.is_zero() { 907 dest.write_str(" rotate ")?; 908 rotate.to_css(dest)?; 909 } 910 Ok(()) 911 }, 912 Close => dest.write_str("close"), 913 } 914 } 915 } 916 917 /// Defines the end point of the command, which can be specified in absolute or relative coordinates, 918 /// determined by their "to" or "by" components respectively. 919 /// https://drafts.csswg.org/css-shapes/#typedef-shape-command-end-point 920 #[allow(missing_docs)] 921 #[derive( 922 Animate, 923 Clone, 924 ComputeSquaredDistance, 925 Copy, 926 Debug, 927 Deserialize, 928 MallocSizeOf, 929 PartialEq, 930 Serialize, 931 SpecifiedValueInfo, 932 ToAnimatedValue, 933 ToAnimatedZero, 934 ToComputedValue, 935 ToResolvedValue, 936 ToShmem, 937 )] 938 #[repr(C, u8)] 939 pub enum CommandEndPoint<Position, LengthPercentage> { 940 ToPosition(Position), 941 ByCoordinate(CoordinatePair<LengthPercentage>), 942 } 943 944 impl<Position, LengthPercentage> CommandEndPoint<Position, LengthPercentage> { 945 /// Return true if it is absolute, i.e. it is To. 946 #[inline] 947 pub fn is_abs(&self) -> bool { 948 matches!(self, CommandEndPoint::ToPosition(_)) 949 } 950 } 951 952 impl<Position, LengthPercentage> CommandEndPoint<Position, LengthPercentage> { 953 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 954 where 955 W: Write, 956 Position: ToCss, 957 LengthPercentage: ToCss, 958 { 959 match self { 960 CommandEndPoint::ToPosition(pos) => { 961 dest.write_str("to ")?; 962 pos.to_css(dest) 963 }, 964 CommandEndPoint::ByCoordinate(coord) => { 965 dest.write_str("by ")?; 966 coord.to_css(dest) 967 }, 968 } 969 } 970 } 971 972 /// Defines the end point for the commands <horizontal-line-command> and <vertical-line-command>, which 973 /// can be specified in absolute or relative values, determined by their "to" or "by" components respectively. 974 /// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command 975 /// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command 976 #[allow(missing_docs)] 977 #[derive( 978 Animate, 979 Clone, 980 Copy, 981 ComputeSquaredDistance, 982 Debug, 983 Deserialize, 984 MallocSizeOf, 985 PartialEq, 986 Parse, 987 Serialize, 988 SpecifiedValueInfo, 989 ToAnimatedValue, 990 ToAnimatedZero, 991 ToComputedValue, 992 ToResolvedValue, 993 ToShmem, 994 )] 995 #[repr(u8)] 996 pub enum AxisEndPoint<LengthPercentage> { 997 ToPosition(#[compute(field_bound)] AxisPosition<LengthPercentage>), 998 ByCoordinate(LengthPercentage), 999 } 1000 1001 impl<LengthPercentage> AxisEndPoint<LengthPercentage> { 1002 /// Return true if it is absolute, i.e. it is To. 1003 #[inline] 1004 pub fn is_abs(&self) -> bool { 1005 matches!(self, AxisEndPoint::ToPosition(_)) 1006 } 1007 } 1008 1009 impl<LengthPercentage: ToCss> ToCss for AxisEndPoint<LengthPercentage> { 1010 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 1011 where 1012 W: Write, 1013 { 1014 if self.is_abs() { 1015 dest.write_str("to ")?; 1016 } else { 1017 dest.write_str("by ")?; 1018 } 1019 match self { 1020 AxisEndPoint::ToPosition(pos) => pos.to_css(dest), 1021 AxisEndPoint::ByCoordinate(coord) => coord.to_css(dest), 1022 } 1023 } 1024 } 1025 1026 /// Defines how the absolutely positioned end point for <horizontal-line-command> and 1027 /// <vertical-line-command> is positioned. 1028 /// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command 1029 /// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command 1030 #[allow(missing_docs)] 1031 #[derive( 1032 Animate, 1033 Clone, 1034 ComputeSquaredDistance, 1035 Copy, 1036 Debug, 1037 Deserialize, 1038 MallocSizeOf, 1039 Parse, 1040 PartialEq, 1041 Serialize, 1042 SpecifiedValueInfo, 1043 ToAnimatedValue, 1044 ToAnimatedZero, 1045 ToCss, 1046 ToResolvedValue, 1047 ToShmem, 1048 )] 1049 #[repr(u8)] 1050 pub enum AxisPosition<LengthPercentage> { 1051 LengthPercent(LengthPercentage), 1052 Keyword(AxisPositionKeyword), 1053 } 1054 1055 /// The set of position keywords used in <horizontal-line-command> and <vertical-line-command> 1056 /// for absolute positioning. Note: this is the shared union list between hline and vline, so 1057 /// not every value is valid for either. I.e. hline cannot be positioned with top or y-start. 1058 /// https://drafts.csswg.org/css-shapes-1/#typedef-shape-horizontal-line-command 1059 /// https://drafts.csswg.org/css-shapes-1/#typedef-shape-vertical-line-command 1060 #[allow(missing_docs)] 1061 #[derive( 1062 Animate, 1063 Clone, 1064 ComputeSquaredDistance, 1065 Copy, 1066 Debug, 1067 Deserialize, 1068 MallocSizeOf, 1069 Parse, 1070 PartialEq, 1071 Serialize, 1072 SpecifiedValueInfo, 1073 ToAnimatedValue, 1074 ToAnimatedZero, 1075 ToCss, 1076 ToResolvedValue, 1077 ToShmem, 1078 )] 1079 #[repr(u8)] 1080 pub enum AxisPositionKeyword { 1081 Center, 1082 Left, 1083 Right, 1084 Top, 1085 Bottom, 1086 XStart, 1087 XEnd, 1088 YStart, 1089 YEnd, 1090 } 1091 1092 impl AxisPositionKeyword { 1093 /// Returns the axis position keyword as its corresponding percentage. 1094 #[inline] 1095 pub fn as_percentage(&self) -> Percentage { 1096 match self { 1097 Self::Center => Percentage(0.5), 1098 Self::Left | Self::Top | Self::XStart | Self::YStart => Percentage(0.), 1099 Self::Right | Self::Bottom | Self::XEnd | Self::YEnd => Percentage(1.), 1100 } 1101 } 1102 } 1103 1104 /// Defines a pair of coordinates, representing a rightward and downward offset, respectively, from 1105 /// a specified reference point. Percentages are resolved against the width or height, 1106 /// respectively, of the reference box. 1107 /// https://drafts.csswg.org/css-shapes-2/#typedef-shape-coordinate-pair 1108 #[allow(missing_docs)] 1109 #[derive( 1110 AddAssign, 1111 Animate, 1112 Clone, 1113 ComputeSquaredDistance, 1114 Copy, 1115 Debug, 1116 Deserialize, 1117 MallocSizeOf, 1118 PartialEq, 1119 Serialize, 1120 SpecifiedValueInfo, 1121 ToAnimatedValue, 1122 ToAnimatedZero, 1123 ToComputedValue, 1124 ToCss, 1125 ToResolvedValue, 1126 ToShmem, 1127 )] 1128 #[repr(C)] 1129 pub struct CoordinatePair<LengthPercentage> { 1130 pub x: LengthPercentage, 1131 pub y: LengthPercentage, 1132 } 1133 1134 impl<LengthPercentage> CoordinatePair<LengthPercentage> { 1135 /// Create a CoordinatePair. 1136 #[inline] 1137 pub fn new(x: LengthPercentage, y: LengthPercentage) -> Self { 1138 Self { x, y } 1139 } 1140 } 1141 1142 /// Defines a control point for a quadratic or cubic Bézier curve, which can be specified 1143 /// in absolute or relative coordinates. 1144 /// https://drafts.csswg.org/css-shapes/#typedef-shape-control-point 1145 #[allow(missing_docs)] 1146 #[derive( 1147 Animate, 1148 Clone, 1149 Copy, 1150 ComputeSquaredDistance, 1151 Debug, 1152 Deserialize, 1153 MallocSizeOf, 1154 PartialEq, 1155 Serialize, 1156 SpecifiedValueInfo, 1157 ToAnimatedValue, 1158 ToAnimatedZero, 1159 ToComputedValue, 1160 ToResolvedValue, 1161 ToShmem, 1162 )] 1163 #[repr(C, u8)] 1164 pub enum ControlPoint<Position, LengthPercentage> { 1165 Absolute(Position), 1166 Relative(RelativeControlPoint<LengthPercentage>), 1167 } 1168 1169 impl<Position, LengthPercentage> ControlPoint<Position, LengthPercentage> { 1170 /// Serialize <control-point> 1171 pub fn to_css<W>(&self, dest: &mut CssWriter<W>, is_end_point_abs: bool) -> fmt::Result 1172 where 1173 W: Write, 1174 Position: ToCss, 1175 LengthPercentage: ToCss, 1176 { 1177 match self { 1178 ControlPoint::Absolute(pos) => pos.to_css(dest), 1179 ControlPoint::Relative(point) => point.to_css(dest, is_end_point_abs), 1180 } 1181 } 1182 } 1183 1184 /// Defines a relative control point to a quadratic or cubic Bézier curve, dependent on the 1185 /// reference value. The default `None` is to be relative to the command’s starting point. 1186 /// https://drafts.csswg.org/css-shapes/#typedef-shape-relative-control-point 1187 #[allow(missing_docs)] 1188 #[derive( 1189 Animate, 1190 Clone, 1191 Copy, 1192 Debug, 1193 Deserialize, 1194 MallocSizeOf, 1195 PartialEq, 1196 Serialize, 1197 SpecifiedValueInfo, 1198 ToAnimatedValue, 1199 ToAnimatedZero, 1200 ToComputedValue, 1201 ToResolvedValue, 1202 ToShmem, 1203 )] 1204 #[repr(C)] 1205 pub struct RelativeControlPoint<LengthPercentage> { 1206 pub coord: CoordinatePair<LengthPercentage>, 1207 pub reference: ControlReference, 1208 } 1209 1210 impl<LengthPercentage: ToCss> RelativeControlPoint<LengthPercentage> { 1211 fn to_css<W>(&self, dest: &mut CssWriter<W>, is_end_point_abs: bool) -> fmt::Result 1212 where 1213 W: Write, 1214 { 1215 self.coord.to_css(dest)?; 1216 match self.reference { 1217 ControlReference::Origin if is_end_point_abs => Ok(()), 1218 ControlReference::Start if !is_end_point_abs => Ok(()), 1219 other => { 1220 dest.write_str(" from ")?; 1221 other.to_css(dest) 1222 }, 1223 } 1224 } 1225 } 1226 1227 impl<LengthPercentage: ComputeSquaredDistance> ComputeSquaredDistance 1228 for RelativeControlPoint<LengthPercentage> 1229 { 1230 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { 1231 self.coord.compute_squared_distance(&other.coord) 1232 } 1233 } 1234 1235 /// Defines the point of reference for a <relative-control-point>. 1236 /// 1237 /// When a reference is not specified, depending on whether the associated 1238 /// <command-end-point> is absolutely or relatively positioned, the default 1239 /// will be `Origin` or `Start`, respectively. 1240 /// https://drafts.csswg.org/css-shapes/#typedef-shape-relative-control-point 1241 #[allow(missing_docs)] 1242 #[derive( 1243 Animate, 1244 Clone, 1245 Copy, 1246 Debug, 1247 Deserialize, 1248 Eq, 1249 MallocSizeOf, 1250 PartialEq, 1251 Parse, 1252 Serialize, 1253 SpecifiedValueInfo, 1254 ToAnimatedValue, 1255 ToAnimatedZero, 1256 ToComputedValue, 1257 ToCss, 1258 ToResolvedValue, 1259 ToShmem, 1260 )] 1261 #[repr(C)] 1262 pub enum ControlReference { 1263 Start, 1264 End, 1265 Origin, 1266 } 1267 1268 /// Defines the radiuses for an <arc-command>. 1269 /// 1270 /// The first <length-percentage> is the ellipse's horizontal radius, and the second is 1271 /// the vertical radius. If only one value is provided, it is used for both radii, and any 1272 /// <percentage> is resolved against the direction-agnostic size of the reference box. 1273 /// https://drafts.csswg.org/css-shapes-1/#typedef-shape-arc-command 1274 #[allow(missing_docs)] 1275 #[derive( 1276 Animate, 1277 Clone, 1278 ComputeSquaredDistance, 1279 Copy, 1280 Debug, 1281 Deserialize, 1282 MallocSizeOf, 1283 PartialEq, 1284 Serialize, 1285 SpecifiedValueInfo, 1286 ToAnimatedValue, 1287 ToAnimatedZero, 1288 ToComputedValue, 1289 ToCss, 1290 ToResolvedValue, 1291 ToShmem, 1292 )] 1293 #[repr(C)] 1294 pub struct ArcRadii<LengthPercentage> { 1295 pub rx: LengthPercentage, 1296 pub ry: Optional<LengthPercentage>, 1297 } 1298 1299 /// This indicates that the arc that is traced around the ellipse clockwise or counter-clockwise 1300 /// from the center. 1301 /// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-sweep 1302 #[derive( 1303 Clone, 1304 Copy, 1305 Debug, 1306 Deserialize, 1307 FromPrimitive, 1308 MallocSizeOf, 1309 Parse, 1310 PartialEq, 1311 Serialize, 1312 SpecifiedValueInfo, 1313 ToAnimatedValue, 1314 ToAnimatedZero, 1315 ToComputedValue, 1316 ToCss, 1317 ToResolvedValue, 1318 ToShmem, 1319 )] 1320 #[repr(u8)] 1321 pub enum ArcSweep { 1322 /// Counter-clockwise. The default value. (This also represents 0 in the svg path.) 1323 Ccw = 0, 1324 /// Clockwise. (This also represents 1 in the svg path.) 1325 Cw = 1, 1326 } 1327 1328 impl Animate for ArcSweep { 1329 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { 1330 use num_traits::FromPrimitive; 1331 // If an arc command has different <arc-sweep> between its starting and ending list, then 1332 // the interpolated result uses cw for any progress value between 0 and 1. 1333 // Note: we cast progress from f64->f32->f64 to drop tiny noise near 0.0. 1334 let progress = procedure.weights().1 as f32 as f64; 1335 let procedure = Procedure::Interpolate { progress }; 1336 (*self as i32 as f32) 1337 .animate(&(*other as i32 as f32), procedure) 1338 .map(|v| ArcSweep::from_u8((v > 0.) as u8).unwrap_or(ArcSweep::Ccw)) 1339 } 1340 } 1341 1342 impl ComputeSquaredDistance for ArcSweep { 1343 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { 1344 (*self as i32).compute_squared_distance(&(*other as i32)) 1345 } 1346 } 1347 1348 /// This indicates that the larger or smaller, respectively, of the two possible arcs must be 1349 /// chosen. 1350 /// https://drafts.csswg.org/css-shapes-2/#typedef-shape-arc-size 1351 #[derive( 1352 Clone, 1353 Copy, 1354 Debug, 1355 Deserialize, 1356 FromPrimitive, 1357 MallocSizeOf, 1358 Parse, 1359 PartialEq, 1360 Serialize, 1361 SpecifiedValueInfo, 1362 ToAnimatedValue, 1363 ToAnimatedZero, 1364 ToComputedValue, 1365 ToCss, 1366 ToResolvedValue, 1367 ToShmem, 1368 )] 1369 #[repr(u8)] 1370 pub enum ArcSize { 1371 /// Choose the small one. The default value. (This also represents 0 in the svg path.) 1372 Small = 0, 1373 /// Choose the large one. (This also represents 1 in the svg path.) 1374 Large = 1, 1375 } 1376 1377 impl Animate for ArcSize { 1378 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { 1379 use num_traits::FromPrimitive; 1380 // If it has different <arc-size> keywords, then the interpolated result uses large for any 1381 // progress value between 0 and 1. 1382 // Note: we cast progress from f64->f32->f64 to drop tiny noise near 0.0. 1383 let progress = procedure.weights().1 as f32 as f64; 1384 let procedure = Procedure::Interpolate { progress }; 1385 (*self as i32 as f32) 1386 .animate(&(*other as i32 as f32), procedure) 1387 .map(|v| ArcSize::from_u8((v > 0.) as u8).unwrap_or(ArcSize::Small)) 1388 } 1389 } 1390 1391 impl ComputeSquaredDistance for ArcSize { 1392 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { 1393 (*self as i32).compute_squared_distance(&(*other as i32)) 1394 } 1395 }