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