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)]
23#[repr(C, u8)]
24pub 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    LightDark(Box<GenericLightDark<Self>>),
63
64    /// An `image(<color>)` function.
65    #[css(function)]
66    Image(#[value_info(skip)] Box<Color>),
67}
68
69pub use self::GenericImage as Image;
70
71/// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
72#[derive(
73    Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, ToComputedValue,
74)]
75#[css(comma, function = "cross-fade")]
76#[repr(C)]
77pub struct GenericCrossFade<Image, Color, Percentage> {
78    /// All of the image percent pairings passed as arguments to
79    /// cross-fade.
80    #[css(iterable)]
81    pub elements: crate::OwnedSlice<GenericCrossFadeElement<Image, Color, Percentage>>,
82}
83
84/// An optional percent and a cross fade image.
85#[derive(
86    Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
87)]
88#[repr(C)]
89pub struct GenericCrossFadeElement<Image, Color, Percentage> {
90    /// The percent of the final image that `image` will be.
91    pub percent: Optional<Percentage>,
92    /// A color or image that will be blended when cross-fade is
93    /// evaluated.
94    pub image: GenericCrossFadeImage<Image, Color>,
95}
96
97/// An image or a color. `cross-fade` takes either when blending
98/// images together.
99#[derive(
100    Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
101)]
102#[repr(C, u8)]
103pub enum GenericCrossFadeImage<I, C> {
104    /// A boxed image value. Boxing provides indirection so images can
105    /// be cross-fades and cross-fades can be images.
106    Image(I),
107    /// A color value.
108    Color(C),
109}
110
111pub use self::GenericCrossFade as CrossFade;
112pub use self::GenericCrossFadeElement as CrossFadeElement;
113pub use self::GenericCrossFadeImage as CrossFadeImage;
114
115/// https://drafts.csswg.org/css-images-4/#image-set-notation
116#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)]
117#[css(comma, function = "image-set")]
118#[repr(C)]
119pub struct GenericImageSet<Image, Resolution> {
120    /// The index of the selected candidate. usize::MAX for specified values or invalid images.
121    #[css(skip)]
122    pub selected_index: usize,
123
124    /// All of the image and resolution pairs.
125    #[css(iterable)]
126    pub items: crate::OwnedSlice<GenericImageSetItem<Image, Resolution>>,
127}
128
129/// An optional percent and a cross fade image.
130#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
131#[repr(C)]
132pub struct GenericImageSetItem<Image, Resolution> {
133    /// `<image>`. `<string>` is converted to `Image::Url` at parse time.
134    pub image: Image,
135    /// The `<resolution>`.
136    ///
137    /// TODO: Skip serialization if it is 1x.
138    pub resolution: Resolution,
139
140    /// The `type(<string>)`
141    /// (Optional) Specify the image's MIME type
142    pub mime_type: crate::OwnedStr,
143
144    /// True if mime_type has been specified
145    pub has_mime_type: bool,
146}
147
148impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R> {
149    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
150    where
151        W: fmt::Write,
152    {
153        self.image.to_css(dest)?;
154        dest.write_char(' ')?;
155        self.resolution.to_css(dest)?;
156
157        if self.has_mime_type {
158            dest.write_char(' ')?;
159            dest.write_str("type(")?;
160            self.mime_type.to_css(dest)?;
161            dest.write_char(')')?;
162        }
163        Ok(())
164    }
165}
166
167pub use self::GenericImageSet as ImageSet;
168pub use self::GenericImageSetItem as ImageSetItem;
169
170/// State flags stored on each variant of a Gradient.
171#[derive(
172    Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
173)]
174#[repr(C)]
175pub struct GradientFlags(u8);
176bitflags! {
177    impl GradientFlags: u8 {
178        /// Set if this is a repeating gradient.
179        const REPEATING = 1 << 0;
180        /// Set if the color interpolation method matches the default for the items.
181        const HAS_DEFAULT_COLOR_INTERPOLATION_METHOD = 1 << 1;
182    }
183}
184
185/// A CSS gradient.
186/// <https://drafts.csswg.org/css-images/#gradients>
187#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
188#[repr(C)]
189pub enum GenericGradient<
190    LineDirection,
191    Length,
192    LengthPercentage,
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<NonNegative<Length>, NonNegative<LengthPercentage>>,
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::Image(ref color) => {
445                dest.write_str("image(")?;
446                color.to_css(dest)?;
447                dest.write_char(')')
448            },
449            Image::ImageSet(ref is) => is.to_css(dest),
450            Image::CrossFade(ref cf) => cf.to_css(dest),
451            Image::LightDark(ref ld) => ld.to_css(dest),
452        }
453    }
454}
455
456impl<D, L, LP, P, A: Zero, AoP, C> ToCss for Gradient<D, L, LP, P, A, AoP, C>
457where
458    D: LineDirection,
459    L: ToCss,
460    LP: ToCss,
461    P: PositionComponent + ToCss,
462    A: ToCss,
463    AoP: ToCss,
464    C: ToCss,
465{
466    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
467    where
468        W: Write,
469    {
470        let (compat_mode, repeating, has_default_color_interpolation_method) = match *self {
471            Gradient::Linear {
472                compat_mode, flags, ..
473            }
474            | Gradient::Radial {
475                compat_mode, flags, ..
476            } => (
477                compat_mode,
478                flags.contains(GradientFlags::REPEATING),
479                flags.contains(GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD),
480            ),
481            Gradient::Conic { flags, .. } => (
482                GradientCompatMode::Modern,
483                flags.contains(GradientFlags::REPEATING),
484                flags.contains(GradientFlags::HAS_DEFAULT_COLOR_INTERPOLATION_METHOD),
485            ),
486        };
487
488        match compat_mode {
489            GradientCompatMode::WebKit => dest.write_str("-webkit-")?,
490            GradientCompatMode::Moz => dest.write_str("-moz-")?,
491            _ => {},
492        }
493
494        if repeating {
495            dest.write_str("repeating-")?;
496        }
497
498        match *self {
499            Gradient::Linear {
500                ref direction,
501                ref color_interpolation_method,
502                ref items,
503                compat_mode,
504                ..
505            } => {
506                dest.write_str("linear-gradient(")?;
507                let mut skip_comma = true;
508                if !direction.points_downwards(compat_mode) {
509                    direction.to_css(dest, compat_mode)?;
510                    skip_comma = false;
511                }
512                if !has_default_color_interpolation_method {
513                    if !skip_comma {
514                        dest.write_char(' ')?;
515                    }
516                    color_interpolation_method.to_css(dest)?;
517                    skip_comma = false;
518                }
519                for item in &**items {
520                    if !skip_comma {
521                        dest.write_str(", ")?;
522                    }
523                    skip_comma = false;
524                    item.to_css(dest)?;
525                }
526            },
527            Gradient::Radial {
528                ref shape,
529                ref position,
530                ref color_interpolation_method,
531                ref items,
532                compat_mode,
533                ..
534            } => {
535                dest.write_str("radial-gradient(")?;
536                let omit_shape = match *shape {
537                    EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover))
538                    | EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true,
539                    _ => false,
540                };
541                let omit_position = position.is_center();
542                if compat_mode == GradientCompatMode::Modern {
543                    if !omit_shape {
544                        shape.to_css(dest)?;
545                        if !omit_position {
546                            dest.write_char(' ')?;
547                        }
548                    }
549                    if !omit_position {
550                        dest.write_str("at ")?;
551                        position.to_css(dest)?;
552                    }
553                } else {
554                    if !omit_position {
555                        position.to_css(dest)?;
556                        if !omit_shape {
557                            dest.write_str(", ")?;
558                        }
559                    }
560                    if !omit_shape {
561                        shape.to_css(dest)?;
562                    }
563                }
564                if !has_default_color_interpolation_method {
565                    if !omit_shape || !omit_position {
566                        dest.write_char(' ')?;
567                    }
568                    color_interpolation_method.to_css(dest)?;
569                }
570
571                let mut skip_comma =
572                    omit_shape && omit_position && has_default_color_interpolation_method;
573                for item in &**items {
574                    if !skip_comma {
575                        dest.write_str(", ")?;
576                    }
577                    skip_comma = false;
578                    item.to_css(dest)?;
579                }
580            },
581            Gradient::Conic {
582                ref angle,
583                ref position,
584                ref color_interpolation_method,
585                ref items,
586                ..
587            } => {
588                dest.write_str("conic-gradient(")?;
589                let omit_angle = angle.is_zero();
590                let omit_position = position.is_center();
591                if !omit_angle {
592                    dest.write_str("from ")?;
593                    angle.to_css(dest)?;
594                    if !omit_position {
595                        dest.write_char(' ')?;
596                    }
597                }
598                if !omit_position {
599                    dest.write_str("at ")?;
600                    position.to_css(dest)?;
601                }
602                if !has_default_color_interpolation_method {
603                    if !omit_angle || !omit_position {
604                        dest.write_char(' ')?;
605                    }
606                    color_interpolation_method.to_css(dest)?;
607                }
608                let mut skip_comma =
609                    omit_angle && omit_position && has_default_color_interpolation_method;
610                for item in &**items {
611                    if !skip_comma {
612                        dest.write_str(", ")?;
613                    }
614                    skip_comma = false;
615                    item.to_css(dest)?;
616                }
617            },
618        }
619        dest.write_char(')')
620    }
621}
622
623/// The direction of a linear gradient.
624pub trait LineDirection {
625    /// Whether this direction points towards, and thus can be omitted.
626    fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool;
627
628    /// Serialises this direction according to the compatibility mode.
629    fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
630    where
631        W: Write;
632}
633
634impl<L> ToCss for Circle<L>
635where
636    L: ToCss,
637{
638    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
639    where
640        W: Write,
641    {
642        match *self {
643            Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => {
644                dest.write_str("circle")
645            },
646            Circle::Extent(keyword) => {
647                dest.write_str("circle ")?;
648                keyword.to_css(dest)
649            },
650            Circle::Radius(ref length) => length.to_css(dest),
651        }
652    }
653}