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(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] 135 #[repr(C)] 136 pub struct GenericImageSetItem<Image, Resolution> { 137 /// `<image>`. `<string>` is converted to `Image::Url` at parse time. 138 pub image: Image, 139 /// The `<resolution>`. 140 /// 141 /// TODO: Skip serialization if it is 1x. 142 pub resolution: Resolution, 143 144 /// The `type(<string>)` 145 /// (Optional) Specify the image's MIME type 146 pub mime_type: crate::OwnedStr, 147 148 /// True if mime_type has been specified 149 pub has_mime_type: bool, 150 } 151 152 impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R> { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,153 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 154 where 155 W: fmt::Write, 156 { 157 self.image.to_css(dest)?; 158 dest.write_str(" ")?; 159 self.resolution.to_css(dest)?; 160 161 if self.has_mime_type { 162 dest.write_str(" ")?; 163 dest.write_str("type(")?; 164 self.mime_type.to_css(dest)?; 165 dest.write_str(")")?; 166 } 167 Ok(()) 168 } 169 } 170 171 pub use self::GenericImageSet as ImageSet; 172 pub use self::GenericImageSetItem as ImageSetItem; 173 174 /// A CSS gradient. 175 /// <https://drafts.csswg.org/css-images/#gradients> 176 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] 177 #[repr(C)] 178 pub enum GenericGradient< 179 LineDirection, 180 LengthPercentage, 181 NonNegativeLength, 182 NonNegativeLengthPercentage, 183 Position, 184 Angle, 185 AngleOrPercentage, 186 Color, 187 > { 188 /// A linear gradient. 189 Linear { 190 /// Line direction 191 direction: LineDirection, 192 /// The color stops and interpolation hints. 193 items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>, 194 /// True if this is a repeating gradient. 195 repeating: bool, 196 /// Compatibility mode. 197 compat_mode: GradientCompatMode, 198 }, 199 /// A radial gradient. 200 Radial { 201 /// Shape of gradient 202 shape: GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>, 203 /// Center of gradient 204 position: Position, 205 /// The color stops and interpolation hints. 206 items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>, 207 /// True if this is a repeating gradient. 208 repeating: bool, 209 /// Compatibility mode. 210 compat_mode: GradientCompatMode, 211 }, 212 /// A conic gradient. 213 Conic { 214 /// Start angle of gradient 215 angle: Angle, 216 /// Center of gradient 217 position: Position, 218 /// The color stops and interpolation hints. 219 items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>, 220 /// True if this is a repeating gradient. 221 repeating: bool, 222 }, 223 } 224 225 pub use self::GenericGradient as Gradient; 226 227 #[derive( 228 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, 229 )] 230 #[repr(u8)] 231 /// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes. 232 pub enum GradientCompatMode { 233 /// Modern syntax. 234 Modern, 235 /// `-webkit` prefix. 236 WebKit, 237 /// `-moz` prefix 238 Moz, 239 } 240 241 /// A radial gradient's ending shape. 242 #[derive( 243 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 244 )] 245 #[repr(C, u8)] 246 pub enum GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage> { 247 /// A circular gradient. 248 Circle(GenericCircle<NonNegativeLength>), 249 /// An elliptic gradient. 250 Ellipse(GenericEllipse<NonNegativeLengthPercentage>), 251 } 252 253 pub use self::GenericEndingShape as EndingShape; 254 255 /// A circle shape. 256 #[derive( 257 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, 258 )] 259 #[repr(C, u8)] 260 pub enum GenericCircle<NonNegativeLength> { 261 /// A circle radius. 262 Radius(NonNegativeLength), 263 /// A circle extent. 264 Extent(ShapeExtent), 265 } 266 267 pub use self::GenericCircle as Circle; 268 269 /// An ellipse shape. 270 #[derive( 271 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 272 )] 273 #[repr(C, u8)] 274 pub enum GenericEllipse<NonNegativeLengthPercentage> { 275 /// An ellipse pair of radii. 276 Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage), 277 /// An ellipse extent. 278 Extent(ShapeExtent), 279 } 280 281 pub use self::GenericEllipse as Ellipse; 282 283 /// <https://drafts.csswg.org/css-images/#typedef-extent-keyword> 284 #[allow(missing_docs)] 285 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 286 #[derive( 287 Clone, 288 Copy, 289 Debug, 290 Eq, 291 MallocSizeOf, 292 Parse, 293 PartialEq, 294 ToComputedValue, 295 ToCss, 296 ToResolvedValue, 297 ToShmem, 298 )] 299 #[repr(u8)] 300 pub enum ShapeExtent { 301 ClosestSide, 302 FarthestSide, 303 ClosestCorner, 304 FarthestCorner, 305 Contain, 306 Cover, 307 } 308 309 /// A gradient item. 310 /// <https://drafts.csswg.org/css-images-4/#color-stop-syntax> 311 #[derive( 312 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 313 )] 314 #[repr(C, u8)] 315 pub enum GenericGradientItem<Color, T> { 316 /// A simple color stop, without position. 317 SimpleColorStop(Color), 318 /// A complex color stop, with a position. 319 ComplexColorStop { 320 /// The color for the stop. 321 color: Color, 322 /// The position for the stop. 323 position: T, 324 }, 325 /// An interpolation hint. 326 InterpolationHint(T), 327 } 328 329 pub use self::GenericGradientItem as GradientItem; 330 331 /// A color stop. 332 /// <https://drafts.csswg.org/css-images/#typedef-color-stop-list> 333 #[derive( 334 Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, 335 )] 336 pub struct ColorStop<Color, T> { 337 /// The color of this stop. 338 pub color: Color, 339 /// The position of this stop. 340 pub position: Option<T>, 341 } 342 343 impl<Color, T> ColorStop<Color, T> { 344 /// Convert the color stop into an appropriate `GradientItem`. 345 #[inline] into_item(self) -> GradientItem<Color, T>346 pub fn into_item(self) -> GradientItem<Color, T> { 347 match self.position { 348 Some(position) => GradientItem::ComplexColorStop { 349 color: self.color, 350 position, 351 }, 352 None => GradientItem::SimpleColorStop(self.color), 353 } 354 } 355 } 356 357 /// Specified values for a paint worklet. 358 /// <https://drafts.css-houdini.org/css-paint-api/> 359 #[cfg_attr(feature = "servo", derive(MallocSizeOf))] 360 #[derive(Clone, Debug, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] 361 pub struct PaintWorklet { 362 /// The name the worklet was registered with. 363 pub name: Atom, 364 /// The arguments for the worklet. 365 /// TODO: store a parsed representation of the arguments. 366 #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] 367 #[compute(no_field_bound)] 368 #[resolve(no_field_bound)] 369 pub arguments: Vec<Arc<custom_properties::SpecifiedValue>>, 370 } 371 372 impl ::style_traits::SpecifiedValueInfo for PaintWorklet {} 373 374 impl ToCss for PaintWorklet { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,375 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 376 where 377 W: Write, 378 { 379 dest.write_str("paint(")?; 380 serialize_atom_identifier(&self.name, dest)?; 381 for argument in &self.arguments { 382 dest.write_str(", ")?; 383 argument.to_css(dest)?; 384 } 385 dest.write_str(")") 386 } 387 } 388 389 /// Values for `moz-image-rect`. 390 /// 391 /// `-moz-image-rect(<uri>, top, right, bottom, left);` 392 #[allow(missing_docs)] 393 #[derive( 394 Clone, 395 Debug, 396 MallocSizeOf, 397 PartialEq, 398 SpecifiedValueInfo, 399 ToComputedValue, 400 ToCss, 401 ToResolvedValue, 402 ToShmem, 403 )] 404 #[css(comma, function = "-moz-image-rect")] 405 #[repr(C)] 406 pub struct GenericMozImageRect<NumberOrPercentage, MozImageRectUrl> { 407 pub url: MozImageRectUrl, 408 pub top: NumberOrPercentage, 409 pub right: NumberOrPercentage, 410 pub bottom: NumberOrPercentage, 411 pub left: NumberOrPercentage, 412 } 413 414 pub use self::GenericMozImageRect as MozImageRect; 415 416 impl<G, R, U, C, P, Resolution> fmt::Debug for Image<G, R, U, C, P, Resolution> 417 where 418 Image<G, R, U, C, P, Resolution>: ToCss, 419 { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result420 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 421 self.to_css(&mut CssWriter::new(f)) 422 } 423 } 424 425 impl<G, R, U, C, P, Resolution> ToCss for Image<G, R, U, C, P, Resolution> 426 where 427 G: ToCss, 428 R: ToCss, 429 U: ToCss, 430 C: ToCss, 431 P: ToCss, 432 Resolution: ToCss, 433 { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,434 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 435 where 436 W: Write, 437 { 438 match *self { 439 Image::None => dest.write_str("none"), 440 Image::Url(ref url) => url.to_css(dest), 441 Image::Gradient(ref gradient) => gradient.to_css(dest), 442 Image::Rect(ref rect) => rect.to_css(dest), 443 #[cfg(feature = "servo-layout-2013")] 444 Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest), 445 #[cfg(feature = "gecko")] 446 Image::Element(ref selector) => { 447 dest.write_str("-moz-element(#")?; 448 serialize_atom_identifier(selector, dest)?; 449 dest.write_str(")") 450 }, 451 Image::ImageSet(ref is) => is.to_css(dest), 452 Image::CrossFade(ref cf) => cf.to_css(dest), 453 } 454 } 455 } 456 457 impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C> 458 where 459 D: LineDirection, 460 LP: ToCss, 461 NL: ToCss, 462 NLP: ToCss, 463 P: PositionComponent + ToCss, 464 A: ToCss, 465 AoP: ToCss, 466 C: ToCss, 467 { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,468 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 469 where 470 W: Write, 471 { 472 let (compat_mode, repeating) = match *self { 473 Gradient::Linear { 474 compat_mode, 475 repeating, 476 .. 477 } => (compat_mode, repeating), 478 Gradient::Radial { 479 compat_mode, 480 repeating, 481 .. 482 } => (compat_mode, repeating), 483 Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating), 484 }; 485 486 match compat_mode { 487 GradientCompatMode::WebKit => dest.write_str("-webkit-")?, 488 GradientCompatMode::Moz => dest.write_str("-moz-")?, 489 _ => {}, 490 } 491 492 if repeating { 493 dest.write_str("repeating-")?; 494 } 495 496 match *self { 497 Gradient::Linear { 498 ref direction, 499 ref items, 500 compat_mode, 501 .. 502 } => { 503 dest.write_str("linear-gradient(")?; 504 let mut skip_comma = if !direction.points_downwards(compat_mode) { 505 direction.to_css(dest, compat_mode)?; 506 false 507 } else { 508 true 509 }; 510 for item in &**items { 511 if !skip_comma { 512 dest.write_str(", ")?; 513 } 514 skip_comma = false; 515 item.to_css(dest)?; 516 } 517 }, 518 Gradient::Radial { 519 ref shape, 520 ref position, 521 ref items, 522 compat_mode, 523 .. 524 } => { 525 dest.write_str("radial-gradient(")?; 526 let omit_shape = match *shape { 527 EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) | 528 EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true, 529 _ => false, 530 }; 531 let omit_position = position.is_center(); 532 if compat_mode == GradientCompatMode::Modern { 533 if !omit_shape { 534 shape.to_css(dest)?; 535 if !omit_position { 536 dest.write_str(" ")?; 537 } 538 } 539 if !omit_position { 540 dest.write_str("at ")?; 541 position.to_css(dest)?; 542 } 543 } else { 544 if !omit_position { 545 position.to_css(dest)?; 546 if !omit_shape { 547 dest.write_str(", ")?; 548 } 549 } 550 if !omit_shape { 551 shape.to_css(dest)?; 552 } 553 } 554 let mut skip_comma = omit_shape && omit_position; 555 for item in &**items { 556 if !skip_comma { 557 dest.write_str(", ")?; 558 } 559 skip_comma = false; 560 item.to_css(dest)?; 561 } 562 }, 563 Gradient::Conic { 564 ref angle, 565 ref position, 566 ref items, 567 .. 568 } => { 569 dest.write_str("conic-gradient(")?; 570 let omit_angle = angle.is_zero(); 571 let omit_position = position.is_center(); 572 if !omit_angle { 573 dest.write_str("from ")?; 574 angle.to_css(dest)?; 575 if !omit_position { 576 dest.write_str(" ")?; 577 } 578 } 579 if !omit_position { 580 dest.write_str("at ")?; 581 position.to_css(dest)?; 582 } 583 let mut skip_comma = omit_angle && omit_position; 584 for item in &**items { 585 if !skip_comma { 586 dest.write_str(", ")?; 587 } 588 skip_comma = false; 589 item.to_css(dest)?; 590 } 591 }, 592 } 593 dest.write_str(")") 594 } 595 } 596 597 /// The direction of a linear gradient. 598 pub trait LineDirection { 599 /// Whether this direction points towards, and thus can be omitted. points_downwards(&self, compat_mode: GradientCompatMode) -> bool600 fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool; 601 602 /// Serialises this direction according to the compatibility mode. to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result where W: Write603 fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result 604 where 605 W: Write; 606 } 607 608 impl<L> ToCss for Circle<L> 609 where 610 L: ToCss, 611 { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,612 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 613 where 614 W: Write, 615 { 616 match *self { 617 Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => { 618 dest.write_str("circle") 619 }, 620 Circle::Extent(keyword) => { 621 dest.write_str("circle ")?; 622 keyword.to_css(dest) 623 }, 624 Circle::Radius(ref length) => length.to_css(dest), 625 } 626 } 627 } 628