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