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