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