style/values/computed/
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//! CSS handling for the computed value of
6//! [`image`][image]s
7//!
8//! [image]: https://drafts.csswg.org/css-images/#image-values
9
10use crate::values::computed::percentage::Percentage;
11use crate::values::computed::position::Position;
12use crate::values::computed::url::ComputedUrl;
13use crate::values::computed::{Angle, Color, Context};
14use crate::values::computed::{
15    AngleOrPercentage, Length, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage,
16    Resolution, ToComputedValue,
17};
18use crate::values::generics::image::{self as generic, GradientCompatMode};
19use crate::values::specified::image as specified;
20use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPositionKeyword};
21use std::f32::consts::PI;
22use std::fmt::{self, Write};
23use style_traits::{CssWriter, ToCss};
24
25pub use specified::ImageRendering;
26
27/// Computed values for an image according to CSS-IMAGES.
28/// <https://drafts.csswg.org/css-images/#image-values>
29pub type Image = generic::GenericImage<Gradient, ComputedUrl, Color, Percentage, Resolution>;
30
31// Images should remain small, see https://github.com/servo/servo/pull/18430
32#[cfg(feature = "gecko")]
33size_of_test!(Image, 16);
34#[cfg(feature = "servo")]
35size_of_test!(Image, 24);
36
37/// Computed values for a CSS gradient.
38/// <https://drafts.csswg.org/css-images/#gradients>
39pub type Gradient = generic::GenericGradient<
40    LineDirection,
41    Length,
42    LengthPercentage,
43    Position,
44    Angle,
45    AngleOrPercentage,
46    Color,
47>;
48
49/// Computed values for CSS cross-fade
50/// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
51pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;
52
53/// A computed radial gradient ending shape.
54pub type EndingShape = generic::GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>;
55
56/// A computed gradient line direction.
57#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToResolvedValue)]
58#[repr(C, u8)]
59pub enum LineDirection {
60    /// An angle.
61    Angle(Angle),
62    /// A horizontal direction.
63    Horizontal(HorizontalPositionKeyword),
64    /// A vertical direction.
65    Vertical(VerticalPositionKeyword),
66    /// A corner.
67    Corner(HorizontalPositionKeyword, VerticalPositionKeyword),
68}
69
70/// The computed value for an `image-set()` image.
71pub type ImageSet = generic::GenericImageSet<Image, Resolution>;
72
73impl ToComputedValue for specified::ImageSet {
74    type ComputedValue = ImageSet;
75
76    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
77        let items = self.items.to_computed_value(context);
78        let dpr = context.device().device_pixel_ratio().get();
79
80        let mut supported_image = false;
81        let mut selected_index = std::usize::MAX;
82        let mut selected_resolution = 0.0;
83
84        for (i, item) in items.iter().enumerate() {
85            if item.has_mime_type && !context.device().is_supported_mime_type(&item.mime_type) {
86                // If the MIME type is not supported, we discard the ImageSetItem.
87                continue;
88            }
89
90            let candidate_resolution = item.resolution.dppx();
91            debug_assert!(
92                candidate_resolution >= 0.0,
93                "Resolutions should be non-negative"
94            );
95            if candidate_resolution == 0.0 {
96                // If the resolution is 0, we also treat it as an invalid image.
97                continue;
98            }
99
100            // https://drafts.csswg.org/css-images-4/#image-set-notation:
101            //
102            //     Make a UA-specific choice of which to load, based on whatever criteria deemed
103            //     relevant (such as the resolution of the display, connection speed, etc).
104            //
105            // For now, select the lowest resolution greater than display density, otherwise the
106            // greatest resolution available.
107            let better_candidate = || {
108                if selected_resolution < dpr && candidate_resolution > selected_resolution {
109                    return true;
110                }
111                if candidate_resolution < selected_resolution && candidate_resolution >= dpr {
112                    return true;
113                }
114                false
115            };
116
117            // The first item with a supported MIME type is obviously the current best candidate
118            if !supported_image || better_candidate() {
119                supported_image = true;
120                selected_index = i;
121                selected_resolution = candidate_resolution;
122            }
123        }
124
125        ImageSet {
126            selected_index,
127            items,
128        }
129    }
130
131    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
132        Self {
133            selected_index: std::usize::MAX,
134            items: ToComputedValue::from_computed_value(&computed.items),
135        }
136    }
137}
138
139impl generic::LineDirection for LineDirection {
140    fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool {
141        match *self {
142            LineDirection::Angle(angle) => angle.radians() == PI,
143            LineDirection::Vertical(VerticalPositionKeyword::Bottom) => {
144                compat_mode == GradientCompatMode::Modern
145            },
146            LineDirection::Vertical(VerticalPositionKeyword::Top) => {
147                compat_mode != GradientCompatMode::Modern
148            },
149            _ => false,
150        }
151    }
152
153    fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
154    where
155        W: Write,
156    {
157        match *self {
158            LineDirection::Angle(ref angle) => angle.to_css(dest),
159            LineDirection::Horizontal(x) => {
160                if compat_mode == GradientCompatMode::Modern {
161                    dest.write_str("to ")?;
162                }
163                x.to_css(dest)
164            },
165            LineDirection::Vertical(y) => {
166                if compat_mode == GradientCompatMode::Modern {
167                    dest.write_str("to ")?;
168                }
169                y.to_css(dest)
170            },
171            LineDirection::Corner(x, y) => {
172                if compat_mode == GradientCompatMode::Modern {
173                    dest.write_str("to ")?;
174                }
175                x.to_css(dest)?;
176                dest.write_char(' ')?;
177                y.to_css(dest)
178            },
179        }
180    }
181}
182
183impl ToComputedValue for specified::LineDirection {
184    type ComputedValue = LineDirection;
185
186    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
187        match *self {
188            specified::LineDirection::Angle(ref angle) => {
189                LineDirection::Angle(angle.to_computed_value(context))
190            },
191            specified::LineDirection::Horizontal(x) => LineDirection::Horizontal(x),
192            specified::LineDirection::Vertical(y) => LineDirection::Vertical(y),
193            specified::LineDirection::Corner(x, y) => LineDirection::Corner(x, y),
194        }
195    }
196
197    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
198        match *computed {
199            LineDirection::Angle(ref angle) => {
200                specified::LineDirection::Angle(ToComputedValue::from_computed_value(angle))
201            },
202            LineDirection::Horizontal(x) => specified::LineDirection::Horizontal(x),
203            LineDirection::Vertical(y) => specified::LineDirection::Vertical(y),
204            LineDirection::Corner(x, y) => specified::LineDirection::Corner(x, y),
205        }
206    }
207}
208
209impl ToComputedValue for specified::Image {
210    type ComputedValue = Image;
211
212    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
213        match self {
214            Self::None => Image::None,
215            Self::Url(u) => Image::Url(u.to_computed_value(context)),
216            Self::Gradient(g) => Image::Gradient(g.to_computed_value(context)),
217            #[cfg(feature = "gecko")]
218            Self::Element(e) => Image::Element(e.to_computed_value(context)),
219            #[cfg(feature = "gecko")]
220            Self::MozSymbolicIcon(e) => Image::MozSymbolicIcon(e.to_computed_value(context)),
221            #[cfg(feature = "servo")]
222            Self::PaintWorklet(w) => Image::PaintWorklet(w.to_computed_value(context)),
223            Self::CrossFade(f) => Image::CrossFade(f.to_computed_value(context)),
224            Self::ImageSet(s) => Image::ImageSet(s.to_computed_value(context)),
225            Self::LightDark(ld) => ld.compute(context),
226        }
227    }
228
229    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
230        match computed {
231            Image::None => Self::None,
232            Image::Url(u) => Self::Url(ToComputedValue::from_computed_value(u)),
233            Image::Gradient(g) => Self::Gradient(ToComputedValue::from_computed_value(g)),
234            #[cfg(feature = "gecko")]
235            Image::Element(e) => Self::Element(ToComputedValue::from_computed_value(e)),
236            #[cfg(feature = "gecko")]
237            Image::MozSymbolicIcon(e) => {
238                Self::MozSymbolicIcon(ToComputedValue::from_computed_value(e))
239            },
240            #[cfg(feature = "servo")]
241            Image::PaintWorklet(w) => Self::PaintWorklet(ToComputedValue::from_computed_value(w)),
242            Image::CrossFade(f) => Self::CrossFade(ToComputedValue::from_computed_value(f)),
243            Image::ImageSet(s) => Self::ImageSet(ToComputedValue::from_computed_value(s)),
244            Image::LightDark(_) => unreachable!("Shouldn't have computed image-set values"),
245        }
246    }
247}