style/values/generics/
image.rs

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
9use crate::color::mix::ColorInterpolationMethod;
10use crate::custom_properties;
11use crate::values::generics::{color::GenericLightDark, position::PositionComponent, Optional};
12use crate::values::serialize_atom_identifier;
13use crate::Atom;
14use crate::Zero;
15use servo_arc::Arc;
16use std::fmt::{self, Write};
17use style_traits::{CssWriter, ToCss};
18/// An `<image> | none` value.
19///
20/// https://drafts.csswg.org/css-images/#image-values
21#[derive(Clone, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToResolvedValue, ToShmem)]
22#[repr(C, u8)]
23pub enum GenericImage<G, ImageUrl, Color, Percentage, Resolution> {
24    /// `none` variant.
25    None,
26
27    /// A `<url()>` image.
28    Url(ImageUrl),
29
30    /// A `<gradient>` image.  Gradients are rather large, and not nearly as
31    /// common as urls, so we box them here to keep the size of this enum sane.
32    Gradient(Box<G>),
33
34    /// A `-moz-element(# <element-id>)`
35    #[cfg(feature = "gecko")]
36    #[css(function = "-moz-element")]
37    Element(Atom),
38
39    /// A `-moz-symbolic-icon(<icon-id>)`
40    /// NOTE(emilio): #[css(skip)] only really affects SpecifiedValueInfo, which we want because
41    /// this is chrome-only.
42    #[cfg(feature = "gecko")]
43    #[css(function, skip)]
44    MozSymbolicIcon(Atom),
45
46    /// A paint worklet image.
47    /// <https://drafts.css-houdini.org/css-paint-api/>
48    #[cfg(feature = "servo")]
49    PaintWorklet(Box<PaintWorklet>),
50
51    /// A `<cross-fade()>` image. Storing this directly inside of
52    /// GenericImage increases the size by 8 bytes so we box it here
53    /// and store images directly inside of cross-fade instead of
54    /// boxing them there.
55    CrossFade(Box<GenericCrossFade<Self, Color, Percentage>>),
56
57    /// An `image-set()` function.
58    ImageSet(Box<GenericImageSet<Self, Resolution>>),
59
60    /// A `light-dark()` function.
61    /// NOTE(emilio): #[css(skip)] only affects SpecifiedValueInfo. Remove or make conditional
62    /// if/when shipping light-dark() for content.
63    LightDark(#[css(skip)] Box<GenericLightDark<Self>>),
64}
65
66pub use self::GenericImage as Image;
67
68/// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
69#[derive(
70    Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, ToComputedValue,
71)]
72#[css(comma, function = "cross-fade")]
73#[repr(C)]
74pub struct GenericCrossFade<Image, Color, Percentage> {
75    /// All of the image percent pairings passed as arguments to
76    /// cross-fade.
77    #[css(iterable)]
78    pub elements: crate::OwnedSlice<GenericCrossFadeElement<Image, Color, Percentage>>,
79}
80
81/// An optional percent and a cross fade image.
82#[derive(
83    Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
84)]
85#[repr(C)]
86pub struct GenericCrossFadeElement<Image, Color, Percentage> {
87    /// The percent of the final image that `image` will be.
88    pub percent: Optional<Percentage>,
89    /// A color or image that will be blended when cross-fade is
90    /// evaluated.
91    pub image: GenericCrossFadeImage<Image, Color>,
92}
93
94/// An image or a color. `cross-fade` takes either when blending
95/// images together.
96#[derive(
97    Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
98)]
99#[repr(C, u8)]
100pub enum GenericCrossFadeImage<I, C> {
101    /// A boxed image value. Boxing provides indirection so images can
102    /// be cross-fades and cross-fades can be images.
103    Image(I),
104    /// A color value.
105    Color(C),
106}
107
108pub use self::GenericCrossFade as CrossFade;
109pub use self::GenericCrossFadeElement as CrossFadeElement;
110pub use self::GenericCrossFadeImage as CrossFadeImage;
111
112/// https://drafts.csswg.org/css-images-4/#image-set-notation
113#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)]
114#[css(comma, function = "image-set")]
115#[repr(C)]
116pub struct GenericImageSet<Image, Resolution> {
117    /// The index of the selected candidate. usize::MAX for specified values or invalid images.
118    #[css(skip)]
119    pub selected_index: usize,
120
121    /// All of the image and resolution pairs.
122    #[css(iterable)]
123    pub items: crate::OwnedSlice<GenericImageSetItem<Image, Resolution>>,
124}
125
126/// An optional percent and a cross fade image.
127#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
128#[repr(C)]
129pub struct GenericImageSetItem<Image, Resolution> {
130    /// `<image>`. `<string>` is converted to `Image::Url` at parse time.
131    pub image: Image,
132    /// The `<resolution>`.
133    ///
134    /// TODO: Skip serialization if it is 1x.
135    pub resolution: Resolution,
136
137    /// The `type(<string>)`
138    /// (Optional) Specify the image's MIME type
139    pub mime_type: crate::OwnedStr,
140
141    /// True if mime_type has been specified
142    pub has_mime_type: bool,
143}
144
145impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R> {
146    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
147    where
148        W: fmt::Write,
149    {
150        self.image.to_css(dest)?;
151        dest.write_char(' ')?;
152        self.resolution.to_css(dest)?;
153
154        if self.has_mime_type {
155            dest.write_char(' ')?;
156            dest.write_str("type(")?;
157            self.mime_type.to_css(dest)?;
158            dest.write_char(')')?;
159        }
160        Ok(())
161    }
162}
163
164pub use self::GenericImageSet as ImageSet;
165pub use self::GenericImageSetItem as ImageSetItem;
166
167/// State flags stored on each variant of a Gradient.
168#[derive(
169    Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
170)]
171#[repr(C)]
172pub struct GradientFlags(u8);
173bitflags! {
174    impl GradientFlags: u8 {
175        /// Set if this is a repeating gradient.
176        const REPEATING = 1 << 0;
177        /// Set if the color interpolation method matches the default for the items.
178        const HAS_DEFAULT_COLOR_INTERPOLATION_METHOD = 1 << 1;
179    }
180}
181
182/// A CSS gradient.
183/// <https://drafts.csswg.org/css-images/#gradients>
184#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
185#[repr(C)]
186pub enum GenericGradient<
187    LineDirection,
188    LengthPercentage,
189    NonNegativeLength,
190    NonNegativeLengthPercentage,
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<NonNegativeLength, NonNegativeLengthPercentage>,
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
239pub 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.
246pub 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)]
260pub enum GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage> {
261    /// A circular gradient.
262    Circle(GenericCircle<NonNegativeLength>),
263    /// An elliptic gradient.
264    Ellipse(GenericEllipse<NonNegativeLengthPercentage>),
265}
266
267pub 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)]
274pub enum GenericCircle<NonNegativeLength> {
275    /// A circle radius.
276    Radius(NonNegativeLength),
277    /// A circle extent.
278    Extent(ShapeExtent),
279}
280
281pub 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)]
288pub enum GenericEllipse<NonNegativeLengthPercentage> {
289    /// An ellipse pair of radii.
290    Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage),
291    /// An ellipse extent.
292    Extent(ShapeExtent),
293}
294
295pub 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)]
314pub 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)]
329pub 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
343pub 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)]
350pub 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
357impl<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)]
375pub 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
386impl ::style_traits::SpecifiedValueInfo for PaintWorklet {}
387
388impl 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
403impl<G, U, C, P, Resolution> fmt::Debug for Image<G, U, C, P, Resolution>
404where
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
412impl<G, U, C, P, Resolution> ToCss for Image<G, U, C, P, Resolution>
413where
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
449impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C>
450where
451    D: LineDirection,
452    LP: ToCss,
453    NL: ToCss,
454    NLP: ToCss,
455    P: PositionComponent + ToCss,
456    A: ToCss,
457    AoP: ToCss,
458    C: ToCss,
459{
460    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
461    where
462        W: Write,
463    {
464        let (compat_mode, repeating, has_default_color_interpolation_method) = match *self {
465            Gradient::Linear {
466                compat_mode, flags, ..
467            }
468            | Gradient::Radial {
469                compat_mode, flags, ..
470            } => (
471                compat_mode,
472                flags.contains(GradientFlags::REPEATING),
473                flags.contains(GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD),
474            ),
475            Gradient::Conic { flags, .. } => (
476                GradientCompatMode::Modern,
477                flags.contains(GradientFlags::REPEATING),
478                flags.contains(GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD),
479            ),
480        };
481
482        match compat_mode {
483            GradientCompatMode::WebKit => dest.write_str("-webkit-")?,
484            GradientCompatMode::Moz => dest.write_str("-moz-")?,
485            _ => {},
486        }
487
488        if repeating {
489            dest.write_str("repeating-")?;
490        }
491
492        match *self {
493            Gradient::Linear {
494                ref direction,
495                ref color_interpolation_method,
496                ref items,
497                compat_mode,
498                ..
499            } => {
500                dest.write_str("linear-gradient(")?;
501                let mut skip_comma = true;
502                if !direction.points_downwards(compat_mode) {
503                    direction.to_css(dest, compat_mode)?;
504                    skip_comma = false;
505                }
506                if !has_default_color_interpolation_method {
507                    if !skip_comma {
508                        dest.write_char(' ')?;
509                    }
510                    color_interpolation_method.to_css(dest)?;
511                    skip_comma = false;
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 color_interpolation_method,
525                ref items,
526                compat_mode,
527                ..
528            } => {
529                dest.write_str("radial-gradient(")?;
530                let omit_shape = match *shape {
531                    EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover))
532                    | EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true,
533                    _ => false,
534                };
535                let omit_position = position.is_center();
536                if compat_mode == GradientCompatMode::Modern {
537                    if !omit_shape {
538                        shape.to_css(dest)?;
539                        if !omit_position {
540                            dest.write_char(' ')?;
541                        }
542                    }
543                    if !omit_position {
544                        dest.write_str("at ")?;
545                        position.to_css(dest)?;
546                    }
547                } else {
548                    if !omit_position {
549                        position.to_css(dest)?;
550                        if !omit_shape {
551                            dest.write_str(", ")?;
552                        }
553                    }
554                    if !omit_shape {
555                        shape.to_css(dest)?;
556                    }
557                }
558                if !has_default_color_interpolation_method {
559                    if !omit_shape || !omit_position {
560                        dest.write_char(' ')?;
561                    }
562                    color_interpolation_method.to_css(dest)?;
563                }
564
565                let mut skip_comma =
566                    omit_shape && omit_position && has_default_color_interpolation_method;
567                for item in &**items {
568                    if !skip_comma {
569                        dest.write_str(", ")?;
570                    }
571                    skip_comma = false;
572                    item.to_css(dest)?;
573                }
574            },
575            Gradient::Conic {
576                ref angle,
577                ref position,
578                ref color_interpolation_method,
579                ref items,
580                ..
581            } => {
582                dest.write_str("conic-gradient(")?;
583                let omit_angle = angle.is_zero();
584                let omit_position = position.is_center();
585                if !omit_angle {
586                    dest.write_str("from ")?;
587                    angle.to_css(dest)?;
588                    if !omit_position {
589                        dest.write_char(' ')?;
590                    }
591                }
592                if !omit_position {
593                    dest.write_str("at ")?;
594                    position.to_css(dest)?;
595                }
596                if !has_default_color_interpolation_method {
597                    if !omit_angle || !omit_position {
598                        dest.write_char(' ')?;
599                    }
600                    color_interpolation_method.to_css(dest)?;
601                }
602                let mut skip_comma =
603                    omit_angle && omit_position && has_default_color_interpolation_method;
604                for item in &**items {
605                    if !skip_comma {
606                        dest.write_str(", ")?;
607                    }
608                    skip_comma = false;
609                    item.to_css(dest)?;
610                }
611            },
612        }
613        dest.write_char(')')
614    }
615}
616
617/// The direction of a linear gradient.
618pub trait LineDirection {
619    /// Whether this direction points towards, and thus can be omitted.
620    fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool;
621
622    /// Serialises this direction according to the compatibility mode.
623    fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
624    where
625        W: Write;
626}
627
628impl<L> ToCss for Circle<L>
629where
630    L: ToCss,
631{
632    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
633    where
634        W: Write,
635    {
636        match *self {
637            Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => {
638                dest.write_str("circle")
639            },
640            Circle::Extent(keyword) => {
641                dest.write_str("circle ")?;
642                keyword.to_css(dest)
643            },
644            Circle::Radius(ref length) => length.to_css(dest),
645        }
646    }
647}