/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Generic types for the handling of [images]. //! //! [images]: https://drafts.csswg.org/css-images/#image-values use crate::color::mix::ColorInterpolationMethod; use crate::custom_properties; use crate::values::generics::position::PositionComponent; use crate::values::generics::Optional; use crate::values::serialize_atom_identifier; use crate::Atom; use crate::Zero; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; /// An ` | none` value. /// /// https://drafts.csswg.org/css-images/#image-values #[derive( Clone, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericImage { /// `none` variant. None, /// A `` image. Url(ImageUrl), /// A `` image. Gradients are rather large, and not nearly as /// common as urls, so we box them here to keep the size of this enum sane. Gradient(Box), /// A `-moz-element(# )` #[cfg(feature = "gecko")] #[css(function = "-moz-element")] Element(Atom), /// A paint worklet image. /// #[cfg(feature = "servo")] PaintWorklet(PaintWorklet), /// A `` image. Storing this directly inside of /// GenericImage increases the size by 8 bytes so we box it here /// and store images directly inside of cross-fade instead of /// boxing them there. CrossFade(Box>), /// An `image-set()` function. ImageSet(#[compute(field_bound)] Box>), } pub use self::GenericImage as Image; /// #[derive( Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, ToComputedValue, )] #[css(comma, function = "cross-fade")] #[repr(C)] pub struct GenericCrossFade { /// All of the image percent pairings passed as arguments to /// cross-fade. #[css(iterable)] pub elements: crate::OwnedSlice>, } /// An optional percent and a cross fade image. #[derive( Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, )] #[repr(C)] pub struct GenericCrossFadeElement { /// The percent of the final image that `image` will be. pub percent: Optional, /// A color or image that will be blended when cross-fade is /// evaluated. pub image: GenericCrossFadeImage, } /// An image or a color. `cross-fade` takes either when blending /// images together. #[derive( Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, )] #[repr(C, u8)] pub enum GenericCrossFadeImage { /// A boxed image value. Boxing provides indirection so images can /// be cross-fades and cross-fades can be images. Image(I), /// A color value. Color(C), } pub use self::GenericCrossFade as CrossFade; pub use self::GenericCrossFadeElement as CrossFadeElement; pub use self::GenericCrossFadeImage as CrossFadeImage; /// https://drafts.csswg.org/css-images-4/#image-set-notation #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)] #[css(comma, function = "image-set")] #[repr(C)] pub struct GenericImageSet { /// The index of the selected candidate. usize::MAX for specified values or invalid images. #[css(skip)] pub selected_index: usize, /// All of the image and resolution pairs. #[css(iterable)] pub items: crate::OwnedSlice>, } /// An optional percent and a cross fade image. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] pub struct GenericImageSetItem { /// ``. `` is converted to `Image::Url` at parse time. pub image: Image, /// The ``. /// /// TODO: Skip serialization if it is 1x. pub resolution: Resolution, /// The `type()` /// (Optional) Specify the image's MIME type pub mime_type: crate::OwnedStr, /// True if mime_type has been specified pub has_mime_type: bool, } impl ToCss for GenericImageSetItem { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { self.image.to_css(dest)?; dest.write_char(' ')?; self.resolution.to_css(dest)?; if self.has_mime_type { dest.write_char(' ')?; dest.write_str("type(")?; self.mime_type.to_css(dest)?; dest.write_char(')')?; } Ok(()) } } pub use self::GenericImageSet as ImageSet; pub use self::GenericImageSetItem as ImageSetItem; /// State flags stored on each variant of a Gradient. #[derive( Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct GradientFlags(u8); bitflags! { impl GradientFlags: u8 { /// Set if this is a repeating gradient. const REPEATING = 1 << 0; /// Set if the color interpolation method matches the default for the items. const HAS_DEFAULT_COLOR_INTERPOLATION_METHOD = 1 << 1; } } /// A CSS gradient. /// #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] pub enum GenericGradient< LineDirection, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, Position, Angle, AngleOrPercentage, Color, > { /// A linear gradient. Linear { /// Line direction direction: LineDirection, /// Method to use for color interpolation. color_interpolation_method: ColorInterpolationMethod, /// The color stops and interpolation hints. items: crate::OwnedSlice>, /// State flags for the gradient. flags: GradientFlags, /// Compatibility mode. compat_mode: GradientCompatMode, }, /// A radial gradient. Radial { /// Shape of gradient shape: GenericEndingShape, /// Center of gradient position: Position, /// Method to use for color interpolation. color_interpolation_method: ColorInterpolationMethod, /// The color stops and interpolation hints. items: crate::OwnedSlice>, /// State flags for the gradient. flags: GradientFlags, /// Compatibility mode. compat_mode: GradientCompatMode, }, /// A conic gradient. Conic { /// Start angle of gradient angle: Angle, /// Center of gradient position: Position, /// Method to use for color interpolation. color_interpolation_method: ColorInterpolationMethod, /// The color stops and interpolation hints. items: crate::OwnedSlice>, /// State flags for the gradient. flags: GradientFlags, }, } pub use self::GenericGradient as Gradient; #[derive( Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(u8)] /// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes. pub enum GradientCompatMode { /// Modern syntax. Modern, /// `-webkit` prefix. WebKit, /// `-moz` prefix Moz, } /// A radial gradient's ending shape. #[derive( Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericEndingShape { /// A circular gradient. Circle(GenericCircle), /// An elliptic gradient. Ellipse(GenericEllipse), } pub use self::GenericEndingShape as EndingShape; /// A circle shape. #[derive( Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericCircle { /// A circle radius. Radius(NonNegativeLength), /// A circle extent. Extent(ShapeExtent), } pub use self::GenericCircle as Circle; /// An ellipse shape. #[derive( Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericEllipse { /// An ellipse pair of radii. Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage), /// An ellipse extent. Extent(ShapeExtent), } pub use self::GenericEllipse as Ellipse; /// #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum ShapeExtent { ClosestSide, FarthestSide, ClosestCorner, FarthestCorner, Contain, Cover, } /// A gradient item. /// #[derive( Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(C, u8)] pub enum GenericGradientItem { /// A simple color stop, without position. SimpleColorStop(Color), /// A complex color stop, with a position. ComplexColorStop { /// The color for the stop. color: Color, /// The position for the stop. position: T, }, /// An interpolation hint. InterpolationHint(T), } pub use self::GenericGradientItem as GradientItem; /// A color stop. /// #[derive( Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] pub struct ColorStop { /// The color of this stop. pub color: Color, /// The position of this stop. pub position: Option, } impl ColorStop { /// Convert the color stop into an appropriate `GradientItem`. #[inline] pub fn into_item(self) -> GradientItem { match self.position { Some(position) => GradientItem::ComplexColorStop { color: self.color, position, }, None => GradientItem::SimpleColorStop(self.color), } } } /// Specified values for a paint worklet. /// #[cfg_attr(feature = "servo", derive(MallocSizeOf))] #[derive(Clone, Debug, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] pub struct PaintWorklet { /// The name the worklet was registered with. pub name: Atom, /// The arguments for the worklet. /// TODO: store a parsed representation of the arguments. #[compute(no_field_bound)] #[resolve(no_field_bound)] pub arguments: Vec, } impl ::style_traits::SpecifiedValueInfo for PaintWorklet {} impl ToCss for PaintWorklet { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_str("paint(")?; serialize_atom_identifier(&self.name, dest)?; for argument in &self.arguments { dest.write_str(", ")?; argument.to_css(dest)?; } dest.write_char(')') } } impl fmt::Debug for Image where Image: ToCss, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(&mut CssWriter::new(f)) } } impl ToCss for Image where G: ToCss, U: ToCss, C: ToCss, P: ToCss, Resolution: ToCss, { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { Image::None => dest.write_str("none"), Image::Url(ref url) => url.to_css(dest), Image::Gradient(ref gradient) => gradient.to_css(dest), #[cfg(feature = "servo")] Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest), #[cfg(feature = "gecko")] Image::Element(ref selector) => { dest.write_str("-moz-element(#")?; serialize_atom_identifier(selector, dest)?; dest.write_char(')') }, Image::ImageSet(ref is) => is.to_css(dest), Image::CrossFade(ref cf) => cf.to_css(dest), } } } impl ToCss for Gradient where D: LineDirection, LP: ToCss, NL: ToCss, NLP: ToCss, P: PositionComponent + ToCss, A: ToCss, AoP: ToCss, C: ToCss, { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { let (compat_mode, repeating, has_default_color_interpolation_method) = match *self { Gradient::Linear { compat_mode, flags, .. } | Gradient::Radial { compat_mode, flags, .. } => ( compat_mode, flags.contains(GradientFlags::REPEATING), flags.contains(GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD), ), Gradient::Conic { flags, .. } => ( GradientCompatMode::Modern, flags.contains(GradientFlags::REPEATING), flags.contains(GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD), ), }; match compat_mode { GradientCompatMode::WebKit => dest.write_str("-webkit-")?, GradientCompatMode::Moz => dest.write_str("-moz-")?, _ => {}, } if repeating { dest.write_str("repeating-")?; } match *self { Gradient::Linear { ref direction, ref color_interpolation_method, ref items, compat_mode, .. } => { dest.write_str("linear-gradient(")?; let mut skip_comma = true; if !direction.points_downwards(compat_mode) { direction.to_css(dest, compat_mode)?; skip_comma = false; } if !has_default_color_interpolation_method { if !skip_comma { dest.write_char(' ')?; } color_interpolation_method.to_css(dest)?; skip_comma = false; } for item in &**items { if !skip_comma { dest.write_str(", ")?; } skip_comma = false; item.to_css(dest)?; } }, Gradient::Radial { ref shape, ref position, ref color_interpolation_method, ref items, compat_mode, .. } => { dest.write_str("radial-gradient(")?; let omit_shape = match *shape { EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) | EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true, _ => false, }; let omit_position = position.is_center(); if compat_mode == GradientCompatMode::Modern { if !omit_shape { shape.to_css(dest)?; if !omit_position { dest.write_char(' ')?; } } if !omit_position { dest.write_str("at ")?; position.to_css(dest)?; } } else { if !omit_position { position.to_css(dest)?; if !omit_shape { dest.write_str(", ")?; } } if !omit_shape { shape.to_css(dest)?; } } if !has_default_color_interpolation_method { if !omit_shape || !omit_position { dest.write_char(' ')?; } color_interpolation_method.to_css(dest)?; } let mut skip_comma = omit_shape && omit_position && has_default_color_interpolation_method; for item in &**items { if !skip_comma { dest.write_str(", ")?; } skip_comma = false; item.to_css(dest)?; } }, Gradient::Conic { ref angle, ref position, ref color_interpolation_method, ref items, .. } => { dest.write_str("conic-gradient(")?; let omit_angle = angle.is_zero(); let omit_position = position.is_center(); if !omit_angle { dest.write_str("from ")?; angle.to_css(dest)?; if !omit_position { dest.write_char(' ')?; } } if !omit_position { dest.write_str("at ")?; position.to_css(dest)?; } if !has_default_color_interpolation_method { if !omit_angle || !omit_position { dest.write_char(' ')?; } color_interpolation_method.to_css(dest)?; } let mut skip_comma = omit_angle && omit_position && has_default_color_interpolation_method; for item in &**items { if !skip_comma { dest.write_str(", ")?; } skip_comma = false; item.to_css(dest)?; } }, } dest.write_char(')') } } /// The direction of a linear gradient. pub trait LineDirection { /// Whether this direction points towards, and thus can be omitted. fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool; /// Serialises this direction according to the compatibility mode. fn to_css(&self, dest: &mut CssWriter, compat_mode: GradientCompatMode) -> fmt::Result where W: Write; } impl ToCss for Circle where L: ToCss, { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => { dest.write_str("circle") }, Circle::Extent(keyword) => { dest.write_str("circle ")?; keyword.to_css(dest) }, Circle::Radius(ref length) => length.to_css(dest), } } }