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