Skip to main content

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::derives::*;
12use crate::values::generics::NonNegative;
13use crate::values::generics::{color::GenericLightDark, position::PositionComponent, Optional};
14use crate::values::serialize_atom_identifier;
15use crate::{Atom, Zero};
16use servo_arc::Arc;
17use std::fmt::{self, Write};
18use 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#[typed(todo_derive_fields)]
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    Length,
191    LengthPercentage,
192    Position,
193    Angle,
194    AngleOrPercentage,
195    Color,
196> {
197    /// A linear gradient.
198    Linear {
199        /// Line direction
200        direction: LineDirection,
201        /// Method to use for color interpolation.
202        color_interpolation_method: ColorInterpolationMethod,
203        /// The color stops and interpolation hints.
204        items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>,
205        /// State flags for the gradient.
206        flags: GradientFlags,
207        /// Compatibility mode.
208        compat_mode: GradientCompatMode,
209    },
210    /// A radial gradient.
211    Radial {
212        /// Shape of gradient
213        shape: GenericEndingShape<NonNegative<Length>, NonNegative<LengthPercentage>>,
214        /// Center of gradient
215        position: Position,
216        /// Method to use for color interpolation.
217        color_interpolation_method: ColorInterpolationMethod,
218        /// The color stops and interpolation hints.
219        items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>,
220        /// State flags for the gradient.
221        flags: GradientFlags,
222        /// Compatibility mode.
223        compat_mode: GradientCompatMode,
224    },
225    /// A conic gradient.
226    Conic {
227        /// Start angle of gradient
228        angle: Angle,
229        /// Center of gradient
230        position: Position,
231        /// Method to use for color interpolation.
232        color_interpolation_method: ColorInterpolationMethod,
233        /// The color stops and interpolation hints.
234        items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>,
235        /// State flags for the gradient.
236        flags: GradientFlags,
237    },
238}
239
240pub use self::GenericGradient as Gradient;
241
242#[derive(
243    Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
244)]
245#[repr(u8)]
246/// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes.
247pub enum GradientCompatMode {
248    /// Modern syntax.
249    Modern,
250    /// `-webkit` prefix.
251    WebKit,
252    /// `-moz` prefix
253    Moz,
254}
255
256/// A radial gradient's ending shape.
257#[derive(
258    Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
259)]
260#[repr(C, u8)]
261pub enum GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage> {
262    /// A circular gradient.
263    Circle(GenericCircle<NonNegativeLength>),
264    /// An elliptic gradient.
265    Ellipse(GenericEllipse<NonNegativeLengthPercentage>),
266}
267
268pub use self::GenericEndingShape as EndingShape;
269
270/// A circle shape.
271#[derive(
272    Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
273)]
274#[repr(C, u8)]
275pub enum GenericCircle<NonNegativeLength> {
276    /// A circle radius.
277    Radius(NonNegativeLength),
278    /// A circle extent.
279    Extent(ShapeExtent),
280}
281
282pub use self::GenericCircle as Circle;
283
284/// An ellipse shape.
285#[derive(
286    Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
287)]
288#[repr(C, u8)]
289pub enum GenericEllipse<NonNegativeLengthPercentage> {
290    /// An ellipse pair of radii.
291    Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage),
292    /// An ellipse extent.
293    Extent(ShapeExtent),
294}
295
296pub use self::GenericEllipse as Ellipse;
297
298/// <https://drafts.csswg.org/css-images/#typedef-extent-keyword>
299#[allow(missing_docs)]
300#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
301#[derive(
302    Clone,
303    Copy,
304    Debug,
305    Eq,
306    MallocSizeOf,
307    Parse,
308    PartialEq,
309    ToComputedValue,
310    ToCss,
311    ToResolvedValue,
312    ToShmem,
313)]
314#[repr(u8)]
315pub enum ShapeExtent {
316    ClosestSide,
317    FarthestSide,
318    ClosestCorner,
319    FarthestCorner,
320    Contain,
321    Cover,
322}
323
324/// A gradient item.
325/// <https://drafts.csswg.org/css-images-4/#color-stop-syntax>
326#[derive(
327    Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
328)]
329#[repr(C, u8)]
330pub enum GenericGradientItem<Color, T> {
331    /// A simple color stop, without position.
332    SimpleColorStop(Color),
333    /// A complex color stop, with a position.
334    ComplexColorStop {
335        /// The color for the stop.
336        color: Color,
337        /// The position for the stop.
338        position: T,
339    },
340    /// An interpolation hint.
341    InterpolationHint(T),
342}
343
344pub use self::GenericGradientItem as GradientItem;
345
346/// A color stop.
347/// <https://drafts.csswg.org/css-images/#typedef-color-stop-list>
348#[derive(
349    Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem,
350)]
351pub struct ColorStop<Color, T> {
352    /// The color of this stop.
353    pub color: Color,
354    /// The position of this stop.
355    pub position: Option<T>,
356}
357
358impl<Color, T> ColorStop<Color, T> {
359    /// Convert the color stop into an appropriate `GradientItem`.
360    #[inline]
361    pub fn into_item(self) -> GradientItem<Color, T> {
362        match self.position {
363            Some(position) => GradientItem::ComplexColorStop {
364                color: self.color,
365                position,
366            },
367            None => GradientItem::SimpleColorStop(self.color),
368        }
369    }
370}
371
372/// Specified values for a paint worklet.
373/// <https://drafts.css-houdini.org/css-paint-api/>
374#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
375#[derive(Clone, Debug, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
376pub struct PaintWorklet {
377    /// The name the worklet was registered with.
378    pub name: Atom,
379    /// The arguments for the worklet.
380    /// TODO: store a parsed representation of the arguments.
381    #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
382    #[compute(no_field_bound)]
383    #[resolve(no_field_bound)]
384    pub arguments: Vec<Arc<custom_properties::SpecifiedValue>>,
385}
386
387impl ::style_traits::SpecifiedValueInfo for PaintWorklet {}
388
389impl ToCss for PaintWorklet {
390    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
391    where
392        W: Write,
393    {
394        dest.write_str("paint(")?;
395        serialize_atom_identifier(&self.name, dest)?;
396        for argument in &self.arguments {
397            dest.write_str(", ")?;
398            argument.to_css(dest)?;
399        }
400        dest.write_char(')')
401    }
402}
403
404impl<G, U, C, P, Resolution> fmt::Debug for Image<G, U, C, P, Resolution>
405where
406    Image<G, U, C, P, Resolution>: ToCss,
407{
408    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
409        self.to_css(&mut CssWriter::new(f))
410    }
411}
412
413impl<G, U, C, P, Resolution> ToCss for Image<G, U, C, P, Resolution>
414where
415    G: ToCss,
416    U: ToCss,
417    C: ToCss,
418    P: ToCss,
419    Resolution: ToCss,
420{
421    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
422    where
423        W: Write,
424    {
425        match *self {
426            Image::None => dest.write_str("none"),
427            Image::Url(ref url) => url.to_css(dest),
428            Image::Gradient(ref gradient) => gradient.to_css(dest),
429            #[cfg(feature = "servo")]
430            Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest),
431            #[cfg(feature = "gecko")]
432            Image::Element(ref selector) => {
433                dest.write_str("-moz-element(#")?;
434                serialize_atom_identifier(selector, dest)?;
435                dest.write_char(')')
436            },
437            #[cfg(feature = "gecko")]
438            Image::MozSymbolicIcon(ref id) => {
439                dest.write_str("-moz-symbolic-icon(")?;
440                serialize_atom_identifier(id, dest)?;
441                dest.write_char(')')
442            },
443            Image::ImageSet(ref is) => is.to_css(dest),
444            Image::CrossFade(ref cf) => cf.to_css(dest),
445            Image::LightDark(ref ld) => ld.to_css(dest),
446        }
447    }
448}
449
450impl<D, L, LP, P, A: Zero, AoP, C> ToCss for Gradient<D, L, LP, P, A, AoP, C>
451where
452    D: LineDirection,
453    L: ToCss,
454    LP: 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}