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 //! Generic types for the handling of [images]. 6 //! 7 //! [images]: https://drafts.csswg.org/css-images/#image-values 8 9 use crate::custom_properties; 10 use crate::values::generics::position::PositionComponent; 11 use crate::values::serialize_atom_identifier; 12 use crate::Atom; 13 use crate::Zero; 14 use servo_arc::Arc; 15 use std::fmt::{self, Write}; 16 use style_traits::{CssWriter, ToCss}; 17 18 /// An `<image> | none` value. 19 /// 20 /// https://drafts.csswg.org/css-images/#image-values 21 #[derive( 22 Clone, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, 23 )] 24 #[repr(C, u8)] 25 pub enum GenericImage<G, MozImageRect, ImageUrl, Color, Percentage, Resolution> { 26 /// `none` variant. 27 None, 28 /// A `<url()>` image. 29 Url(ImageUrl), 30 31 /// A `<gradient>` image. Gradients are rather large, and not nearly as 32 /// common as urls, so we box them here to keep the size of this enum sane. 33 Gradient(Box<G>), 34 /// A `-moz-image-rect` image. Also fairly large and rare. 35 // not cfg’ed out on non-Gecko to avoid `error[E0392]: parameter `MozImageRect` is never used` 36 // Instead we make MozImageRect an empty enum 37 Rect(Box<MozImageRect>), 38 39 /// A `-moz-element(# <element-id>)` 40 #[cfg(feature = "gecko")] 41 #[css(function = "-moz-element")] 42 Element(Atom), 43 44 /// A paint worklet image. 45 /// <https://drafts.css-houdini.org/css-paint-api/> 46 #[cfg(feature = "servo-layout-2013")] 47 PaintWorklet(PaintWorklet), 48 49 /// A `<cross-fade()>` image. Storing this directly inside of 50 /// GenericImage increases the size by 8 bytes so we box it here 51 /// and store images directly inside of cross-fade instead of 52 /// boxing them there. 53 CrossFade(Box<GenericCrossFade<Self, Color, Percentage>>), 54 55 /// An `image-set()` function. 56 ImageSet(#[compute(field_bound)] Box<GenericImageSet<Self, Resolution>>), 57 } 58 59 pub use self::GenericImage as Image; 60 61 /// <https://drafts.csswg.org/css-images-4/#cross-fade-function> 62 #[derive( 63 Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, ToComputedValue, 64 )] 65 #[css(comma, function = "cross-fade")] 66 #[repr(C)] 67 pub struct GenericCrossFade<Image, Color, Percentage> { 68 /// All of the image percent pairings passed as arguments to 69 /// cross-fade. 70 #[css(iterable)] 71 pub elements: crate::OwnedSlice<GenericCrossFadeElement<Image, Color, Percentage>>, 72 } 73 74 /// A `<percent> | none` value. Represents optional percentage values 75 /// assosicated with cross-fade images. 76 #[derive( 77 Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, 78 )] 79 #[repr(C, u8)] 80 pub enum PercentOrNone<Percentage> { 81 /// `none` variant. 82 #[css(skip)] 83 None, 84 /// A percentage variant. 85 Percent(Percentage), 86 } 87 88 /// An optional percent and a cross fade image. 89 #[derive( 90 Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, 91 )] 92 #[repr(C)] 93 pub struct GenericCrossFadeElement<Image, Color, Percentage> { 94 /// The percent of the final image that `image` will be. 95 pub percent: PercentOrNone<Percentage>, 96 /// A color or image that will be blended when cross-fade is 97 /// evaluated. 98 pub image: GenericCrossFadeImage<Image, Color>, 99 } 100 101 /// An image or a color. `cross-fade` takes either when blending 102 /// images together. 103 #[derive( 104 Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, 105 )] 106 #[repr(C, u8)] 107 pub enum GenericCrossFadeImage<I, C> { 108 /// A boxed image value. Boxing provides indirection so images can 109 /// be cross-fades and cross-fades can be images. 110 Image(I), 111 /// A color value. 112 Color(C), 113 } 114 115 pub use self::GenericCrossFade as CrossFade; 116 pub use self::GenericCrossFadeElement as CrossFadeElement; 117 pub use self::GenericCrossFadeImage as CrossFadeImage; 118 119 /// https://drafts.csswg.org/css-images-4/#image-set-notation 120 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)] 121 #[css(comma, function = "image-set")] 122 #[repr(C)] 123 pub struct GenericImageSet<Image, Resolution> { 124 /// The index of the selected candidate. Zero for specified values. 125 #[css(skip)] 126 pub selected_index: usize, 127 128 /// All of the image and resolution pairs. 129 #[css(iterable)] 130 pub items: crate::OwnedSlice<GenericImageSetItem<Image, Resolution>>, 131 } 132 133 /// An optional percent and a cross fade image. 134 #[derive( 135 Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, 136 )] 137 #[repr(C)] 138 pub struct GenericImageSetItem<Image, Resolution> { 139 /// `<image>`. `<string>` is converted to `Image::Url` at parse time. 140 pub image: Image, 141 /// The `<resolution>`. 142 /// 143 /// TODO: Skip serialization if it is 1x. 144 pub resolution: Resolution, 145 146 /// The `type(<string>)` 147 /// (Optional) Specify the image's MIME type 148 pub mime_type: crate::OwnedStr, 149 150 /// True if mime_type has been specified 151 pub has_mime_type: bool, 152 } 153 154 impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R> 155 { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,156 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 157 where 158 W: fmt::Write, 159 { 160 self.image.to_css(dest)?; 161 dest.write_str(" ")?; 162 self.resolution.to_css(dest)?; 163 164 if self.has_mime_type { 165 dest.write_str(" ")?; 166 dest.write_str("type(")?; 167 self.mime_type.to_css(dest)?; 168 dest.write_str(")")?; 169 } 170 Ok(()) 171 } 172 } 173 174 pub use self::GenericImageSet as ImageSet; 175 pub use self::GenericImageSetItem as ImageSetItem; 176 177 /// A CSS gradient. 178 /// <https://drafts.csswg.org/css-images/#gradients> 179 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] 180 #[repr(C)] 181 pub enum GenericGradient< 182 LineDirection, 183 LengthPercentage, 184 NonNegativeLength, 185 NonNegativeLengthPercentage, 186 Position, 187 Angle, 188 AngleOrPercentage, 189 Color, 190 > { 191 /// A linear gradient. 192 Linear { 193 /// Line direction 194 direction: LineDirection, 195 /// The color stops and interpolation hints. 196 items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>, 197 /// True if this is a repeating gradient. 198 repeating: bool, 199 /// Compatibility mode. 200 compat_mode: GradientCompatMode, 201 }, 202 /// A radial gradient. 203 Radial { 204 /// Shape of gradient 205 shape: GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>, 206 /// Center of gradient 207 position: Position, 208 /// The color stops and interpolation hints. 209 items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>, 210 /// True if this is a repeating gradient. 211 repeating: bool, 212 /// Compatibility mode. 213 compat_mode: GradientCompatMode, 214 }, 215 /// A conic gradient. 216 Conic { 217 /// Start angle of gradient 218 angle: Angle, 219 /// Center of gradient 220 position: Position, 221 /// The color stops and interpolation hints. 222 items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>, 223 /// True if this is a repeating gradient. 224 repeating: bool, 225 }, 226 } 227 228 pub use self::GenericGradient as Gradient; 229 230 #[derive( 231 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, 232 )] 233 #[repr(u8)] 234 /// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes. 235 pub enum GradientCompatMode { 236 /// Modern syntax. 237 Modern, 238 /// `-webkit` prefix. 239 WebKit, 240 /// `-moz` prefix 241 Moz, 242 } 243 244 /// A radial gradient's ending shape. 245 #[derive( 246 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 247 )] 248 #[repr(C, u8)] 249 pub enum GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage> { 250 /// A circular gradient. 251 Circle(GenericCircle<NonNegativeLength>), 252 /// An elliptic gradient. 253 Ellipse(GenericEllipse<NonNegativeLengthPercentage>), 254 } 255 256 pub use self::GenericEndingShape as EndingShape; 257 258 /// A circle shape. 259 #[derive( 260 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, 261 )] 262 #[repr(C, u8)] 263 pub enum GenericCircle<NonNegativeLength> { 264 /// A circle radius. 265 Radius(NonNegativeLength), 266 /// A circle extent. 267 Extent(ShapeExtent), 268 } 269 270 pub use self::GenericCircle as Circle; 271 272 /// An ellipse shape. 273 #[derive( 274 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 275 )] 276 #[repr(C, u8)] 277 pub enum GenericEllipse<NonNegativeLengthPercentage> { 278 /// An ellipse pair of radii. 279 Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage), 280 /// An ellipse extent. 281 Extent(ShapeExtent), 282 } 283 284 pub use self::GenericEllipse as Ellipse; 285 286 /// <https://drafts.csswg.org/css-images/#typedef-extent-keyword> 287 #[allow(missing_docs)] 288 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 289 #[derive( 290 Clone, 291 Copy, 292 Debug, 293 Eq, 294 MallocSizeOf, 295 Parse, 296 PartialEq, 297 ToComputedValue, 298 ToCss, 299 ToResolvedValue, 300 ToShmem, 301 )] 302 #[repr(u8)] 303 pub enum ShapeExtent { 304 ClosestSide, 305 FarthestSide, 306 ClosestCorner, 307 FarthestCorner, 308 Contain, 309 Cover, 310 } 311 312 /// A gradient item. 313 /// <https://drafts.csswg.org/css-images-4/#color-stop-syntax> 314 #[derive( 315 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 316 )] 317 #[repr(C, u8)] 318 pub enum GenericGradientItem<Color, T> { 319 /// A simple color stop, without position. 320 SimpleColorStop(Color), 321 /// A complex color stop, with a position. 322 ComplexColorStop { 323 /// The color for the stop. 324 color: Color, 325 /// The position for the stop. 326 position: T, 327 }, 328 /// An interpolation hint. 329 InterpolationHint(T), 330 } 331 332 pub use self::GenericGradientItem as GradientItem; 333 334 /// A color stop. 335 /// <https://drafts.csswg.org/css-images/#typedef-color-stop-list> 336 #[derive( 337 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 338 )] 339 pub struct ColorStop<Color, T> { 340 /// The color of this stop. 341 pub color: Color, 342 /// The position of this stop. 343 pub position: Option<T>, 344 } 345 346 impl<Color, T> ColorStop<Color, T> { 347 /// Convert the color stop into an appropriate `GradientItem`. 348 #[inline] into_item(self) -> GradientItem<Color, T>349 pub fn into_item(self) -> GradientItem<Color, T> { 350 match self.position { 351 Some(position) => GradientItem::ComplexColorStop { 352 color: self.color, 353 position, 354 }, 355 None => GradientItem::SimpleColorStop(self.color), 356 } 357 } 358 } 359 360 /// Specified values for a paint worklet. 361 /// <https://drafts.css-houdini.org/css-paint-api/> 362 #[cfg_attr(feature = "servo", derive(MallocSizeOf))] 363 #[derive(Clone, Debug, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] 364 pub struct PaintWorklet { 365 /// The name the worklet was registered with. 366 pub name: Atom, 367 /// The arguments for the worklet. 368 /// TODO: store a parsed representation of the arguments. 369 #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] 370 #[compute(no_field_bound)] 371 #[resolve(no_field_bound)] 372 pub arguments: Vec<Arc<custom_properties::SpecifiedValue>>, 373 } 374 375 impl ::style_traits::SpecifiedValueInfo for PaintWorklet {} 376 377 impl ToCss for PaintWorklet { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,378 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 379 where 380 W: Write, 381 { 382 dest.write_str("paint(")?; 383 serialize_atom_identifier(&self.name, dest)?; 384 for argument in &self.arguments { 385 dest.write_str(", ")?; 386 argument.to_css(dest)?; 387 } 388 dest.write_str(")") 389 } 390 } 391 392 /// Values for `moz-image-rect`. 393 /// 394 /// `-moz-image-rect(<uri>, top, right, bottom, left);` 395 #[allow(missing_docs)] 396 #[derive( 397 Clone, 398 Debug, 399 MallocSizeOf, 400 PartialEq, 401 SpecifiedValueInfo, 402 ToComputedValue, 403 ToCss, 404 ToResolvedValue, 405 ToShmem, 406 )] 407 #[css(comma, function = "-moz-image-rect")] 408 #[repr(C)] 409 pub struct GenericMozImageRect<NumberOrPercentage, MozImageRectUrl> { 410 pub url: MozImageRectUrl, 411 pub top: NumberOrPercentage, 412 pub right: NumberOrPercentage, 413 pub bottom: NumberOrPercentage, 414 pub left: NumberOrPercentage, 415 } 416 417 pub use self::GenericMozImageRect as MozImageRect; 418 419 impl<G, R, U, C, P, Resolution> fmt::Debug for Image<G, R, U, C, P, Resolution> 420 where 421 Image<G, R, U, C, P, Resolution>: ToCss, 422 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result423 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 424 self.to_css(&mut CssWriter::new(f)) 425 } 426 } 427 428 impl<G, R, U, C, P, Resolution> ToCss for Image<G, R, U, C, P, Resolution> 429 where 430 G: ToCss, 431 R: ToCss, 432 U: ToCss, 433 C: ToCss, 434 P: ToCss, 435 Resolution: ToCss, 436 { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,437 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 438 where 439 W: Write, 440 { 441 match *self { 442 Image::None => dest.write_str("none"), 443 Image::Url(ref url) => url.to_css(dest), 444 Image::Gradient(ref gradient) => gradient.to_css(dest), 445 Image::Rect(ref rect) => rect.to_css(dest), 446 #[cfg(feature = "servo-layout-2013")] 447 Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest), 448 #[cfg(feature = "gecko")] 449 Image::Element(ref selector) => { 450 dest.write_str("-moz-element(#")?; 451 serialize_atom_identifier(selector, dest)?; 452 dest.write_str(")") 453 }, 454 Image::ImageSet(ref is) => is.to_css(dest), 455 Image::CrossFade(ref cf) => cf.to_css(dest), 456 } 457 } 458 } 459 460 impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C> 461 where 462 D: LineDirection, 463 LP: ToCss, 464 NL: ToCss, 465 NLP: ToCss, 466 P: PositionComponent + ToCss, 467 A: ToCss, 468 AoP: ToCss, 469 C: ToCss, 470 { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,471 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 472 where 473 W: Write, 474 { 475 let (compat_mode, repeating) = match *self { 476 Gradient::Linear { 477 compat_mode, 478 repeating, 479 .. 480 } => (compat_mode, repeating), 481 Gradient::Radial { 482 compat_mode, 483 repeating, 484 .. 485 } => (compat_mode, repeating), 486 Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating), 487 }; 488 489 match compat_mode { 490 GradientCompatMode::WebKit => dest.write_str("-webkit-")?, 491 GradientCompatMode::Moz => dest.write_str("-moz-")?, 492 _ => {}, 493 } 494 495 if repeating { 496 dest.write_str("repeating-")?; 497 } 498 499 match *self { 500 Gradient::Linear { 501 ref direction, 502 ref items, 503 compat_mode, 504 .. 505 } => { 506 dest.write_str("linear-gradient(")?; 507 let mut skip_comma = if !direction.points_downwards(compat_mode) { 508 direction.to_css(dest, compat_mode)?; 509 false 510 } else { 511 true 512 }; 513 for item in &**items { 514 if !skip_comma { 515 dest.write_str(", ")?; 516 } 517 skip_comma = false; 518 item.to_css(dest)?; 519 } 520 }, 521 Gradient::Radial { 522 ref shape, 523 ref position, 524 ref items, 525 compat_mode, 526 .. 527 } => { 528 dest.write_str("radial-gradient(")?; 529 let omit_shape = match *shape { 530 EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) | 531 EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true, 532 _ => false, 533 }; 534 let omit_position = position.is_center(); 535 if compat_mode == GradientCompatMode::Modern { 536 if !omit_shape { 537 shape.to_css(dest)?; 538 if !omit_position { 539 dest.write_str(" ")?; 540 } 541 } 542 if !omit_position { 543 dest.write_str("at ")?; 544 position.to_css(dest)?; 545 } 546 } else { 547 if !omit_position { 548 position.to_css(dest)?; 549 if !omit_shape { 550 dest.write_str(", ")?; 551 } 552 } 553 if !omit_shape { 554 shape.to_css(dest)?; 555 } 556 } 557 let mut skip_comma = omit_shape && omit_position; 558 for item in &**items { 559 if !skip_comma { 560 dest.write_str(", ")?; 561 } 562 skip_comma = false; 563 item.to_css(dest)?; 564 } 565 }, 566 Gradient::Conic { 567 ref angle, 568 ref position, 569 ref items, 570 .. 571 } => { 572 dest.write_str("conic-gradient(")?; 573 let omit_angle = angle.is_zero(); 574 let omit_position = position.is_center(); 575 if !omit_angle { 576 dest.write_str("from ")?; 577 angle.to_css(dest)?; 578 if !omit_position { 579 dest.write_str(" ")?; 580 } 581 } 582 if !omit_position { 583 dest.write_str("at ")?; 584 position.to_css(dest)?; 585 } 586 let mut skip_comma = omit_angle && omit_position; 587 for item in &**items { 588 if !skip_comma { 589 dest.write_str(", ")?; 590 } 591 skip_comma = false; 592 item.to_css(dest)?; 593 } 594 }, 595 } 596 dest.write_str(")") 597 } 598 } 599 600 /// The direction of a linear gradient. 601 pub trait LineDirection { 602 /// Whether this direction points towards, and thus can be omitted. points_downwards(&self, compat_mode: GradientCompatMode) -> bool603 fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool; 604 605 /// Serialises this direction according to the compatibility mode. to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result where W: Write606 fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result 607 where 608 W: Write; 609 } 610 611 impl<L> ToCss for Circle<L> 612 where 613 L: ToCss, 614 { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,615 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 616 where 617 W: Write, 618 { 619 match *self { 620 Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => { 621 dest.write_str("circle") 622 }, 623 Circle::Extent(keyword) => { 624 dest.write_str("circle ")?; 625 keyword.to_css(dest) 626 }, 627 Circle::Radius(ref length) => length.to_css(dest), 628 } 629 } 630 } 631