Skip to main content

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