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