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 
10 use crate::values::computed::percentage::Percentage;
11 use crate::values::computed::position::Position;
12 use crate::values::computed::url::ComputedImageUrl;
13 #[cfg(feature = "gecko")]
14 use crate::values::computed::NumberOrPercentage;
15 use crate::values::computed::{Angle, Color, Context};
16 use crate::values::computed::{
17     AngleOrPercentage, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage,
18     Resolution, ToComputedValue,
19 };
20 use crate::values::generics::image::{self as generic, GradientCompatMode};
21 use crate::values::specified::image as specified;
22 use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPositionKeyword};
23 use std::f32::consts::PI;
24 use std::fmt::{self, Write};
25 use style_traits::{CssWriter, ToCss};
26 
27 pub use specified::ImageRendering;
28 
29 /// Computed values for an image according to CSS-IMAGES.
30 /// <https://drafts.csswg.org/css-images/#image-values>
31 pub type Image =
32     generic::GenericImage<Gradient, MozImageRect, ComputedImageUrl, Color, Percentage, Resolution>;
33 
34 /// Computed values for a CSS gradient.
35 /// <https://drafts.csswg.org/css-images/#gradients>
36 pub type Gradient = generic::GenericGradient<
37     LineDirection,
38     LengthPercentage,
39     NonNegativeLength,
40     NonNegativeLengthPercentage,
41     Position,
42     Angle,
43     AngleOrPercentage,
44     Color,
45 >;
46 
47 /// Computed values for CSS cross-fade
48 /// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
49 pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;
50 /// A computed percentage or nothing.
51 pub type PercentOrNone = generic::PercentOrNone<Percentage>;
52 
53 /// A computed radial gradient ending shape.
54 pub 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)]
59 pub 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.
71 pub type ImageSet = generic::GenericImageSet<Image, Resolution>;
72 
73 impl ToComputedValue for specified::ImageSet {
74     type ComputedValue = ImageSet;
75 
to_computed_value(&self, context: &Context) -> Self::ComputedValue76     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         // If no item have a supported MIME type, the behavior is undefined by the standard
81         // By default, we select the first item
82         let mut supported_image = false;
83         let mut selected_index = 0;
84         let mut selected_resolution = items[0].resolution.dppx();
85 
86         for (i, item) in items.iter().enumerate() {
87             // If the MIME type is not supported, we discard the ImageSetItem
88             if item.has_mime_type && !context.device().is_supported_mime_type(&item.mime_type) {
89                 continue;
90             }
91 
92             let candidate_resolution = item.resolution.dppx();
93 
94             // https://drafts.csswg.org/css-images-4/#image-set-notation:
95             //
96             //     Make a UA-specific choice of which to load, based on whatever
97             //     criteria deemed relevant (such as the resolution of the
98             //     display, connection speed, etc).
99             //
100             // For now, select the lowest resolution greater than display
101             // density, otherwise the greatest resolution available
102             let better_candidate = || {
103                 if selected_resolution < dpr && candidate_resolution > selected_resolution {
104                     return true;
105                 }
106                 if candidate_resolution < selected_resolution && candidate_resolution >= dpr {
107                     return true;
108                 }
109                 false
110             };
111 
112             // The first item with a supported MIME type is obviously the current best candidate
113             if !supported_image || better_candidate() {
114                 supported_image = true;
115                 selected_index = i;
116                 selected_resolution = candidate_resolution;
117             }
118         }
119 
120         ImageSet {
121             selected_index,
122             items,
123         }
124     }
125 
from_computed_value(computed: &Self::ComputedValue) -> Self126     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
127         Self {
128             selected_index: 0,
129             items: ToComputedValue::from_computed_value(&computed.items),
130         }
131     }
132 }
133 
134 /// Computed values for `-moz-image-rect(...)`.
135 #[cfg(feature = "gecko")]
136 pub type MozImageRect = generic::GenericMozImageRect<NumberOrPercentage, ComputedImageUrl>;
137 
138 /// Empty enum on non-gecko
139 #[cfg(not(feature = "gecko"))]
140 pub type MozImageRect = specified::MozImageRect;
141 
142 impl generic::LineDirection for LineDirection {
points_downwards(&self, compat_mode: GradientCompatMode) -> bool143     fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool {
144         match *self {
145             LineDirection::Angle(angle) => angle.radians() == PI,
146             LineDirection::Vertical(VerticalPositionKeyword::Bottom) => {
147                 compat_mode == GradientCompatMode::Modern
148             },
149             LineDirection::Vertical(VerticalPositionKeyword::Top) => {
150                 compat_mode != GradientCompatMode::Modern
151             },
152             _ => false,
153         }
154     }
155 
to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result where W: Write,156     fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
157     where
158         W: Write,
159     {
160         match *self {
161             LineDirection::Angle(ref angle) => angle.to_css(dest),
162             LineDirection::Horizontal(x) => {
163                 if compat_mode == GradientCompatMode::Modern {
164                     dest.write_str("to ")?;
165                 }
166                 x.to_css(dest)
167             },
168             LineDirection::Vertical(y) => {
169                 if compat_mode == GradientCompatMode::Modern {
170                     dest.write_str("to ")?;
171                 }
172                 y.to_css(dest)
173             },
174             LineDirection::Corner(x, y) => {
175                 if compat_mode == GradientCompatMode::Modern {
176                     dest.write_str("to ")?;
177                 }
178                 x.to_css(dest)?;
179                 dest.write_str(" ")?;
180                 y.to_css(dest)
181             },
182         }
183     }
184 }
185 
186 impl ToComputedValue for specified::LineDirection {
187     type ComputedValue = LineDirection;
188 
to_computed_value(&self, context: &Context) -> Self::ComputedValue189     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
190         match *self {
191             specified::LineDirection::Angle(ref angle) => {
192                 LineDirection::Angle(angle.to_computed_value(context))
193             },
194             specified::LineDirection::Horizontal(x) => LineDirection::Horizontal(x),
195             specified::LineDirection::Vertical(y) => LineDirection::Vertical(y),
196             specified::LineDirection::Corner(x, y) => LineDirection::Corner(x, y),
197         }
198     }
199 
from_computed_value(computed: &Self::ComputedValue) -> Self200     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
201         match *computed {
202             LineDirection::Angle(ref angle) => {
203                 specified::LineDirection::Angle(ToComputedValue::from_computed_value(angle))
204             },
205             LineDirection::Horizontal(x) => specified::LineDirection::Horizontal(x),
206             LineDirection::Vertical(y) => specified::LineDirection::Vertical(y),
207             LineDirection::Corner(x, y) => specified::LineDirection::Corner(x, y),
208         }
209     }
210 }
211