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