1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! CSS handling for the computed value of
//! [`image`][image]s
//!
//! [image]: https://drafts.csswg.org/css-images/#image-values

use crate::values::computed::percentage::Percentage;
use crate::values::computed::position::Position;
use crate::values::computed::url::ComputedImageUrl;
use crate::values::computed::{Angle, Color, Context};
use crate::values::computed::{
    AngleOrPercentage, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage,
    Resolution, ToComputedValue,
};
use crate::values::generics::image::{self as generic, GradientCompatMode};
use crate::values::specified::image as specified;
use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPositionKeyword};
use std::f32::consts::PI;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};

pub use specified::ImageRendering;

/// Computed values for an image according to CSS-IMAGES.
/// <https://drafts.csswg.org/css-images/#image-values>
pub type Image = generic::GenericImage<Gradient, ComputedImageUrl, Color, Percentage, Resolution>;

// Images should remain small, see https://github.com/servo/servo/pull/18430
size_of_test!(Image, 40);

/// Computed values for a CSS gradient.
/// <https://drafts.csswg.org/css-images/#gradients>
pub type Gradient = generic::GenericGradient<
    LineDirection,
    LengthPercentage,
    NonNegativeLength,
    NonNegativeLengthPercentage,
    Position,
    Angle,
    AngleOrPercentage,
    Color,
>;

/// Computed values for CSS cross-fade
/// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;

/// A computed radial gradient ending shape.
pub type EndingShape = generic::GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>;

/// A computed gradient line direction.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToResolvedValue)]
#[repr(C, u8)]
pub enum LineDirection {
    /// An angle.
    Angle(Angle),
    /// A horizontal direction.
    Horizontal(HorizontalPositionKeyword),
    /// A vertical direction.
    Vertical(VerticalPositionKeyword),
    /// A corner.
    Corner(HorizontalPositionKeyword, VerticalPositionKeyword),
}

/// The computed value for an `image-set()` image.
pub type ImageSet = generic::GenericImageSet<Image, Resolution>;

impl ToComputedValue for specified::ImageSet {
    type ComputedValue = ImageSet;

    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        let items = self.items.to_computed_value(context);
        let dpr = context.device().device_pixel_ratio().get();

        let mut supported_image = false;
        let mut selected_index = std::usize::MAX;
        let mut selected_resolution = 0.0;

        for (i, item) in items.iter().enumerate() {
            if item.has_mime_type && !context.device().is_supported_mime_type(&item.mime_type) {
                // If the MIME type is not supported, we discard the ImageSetItem.
                continue;
            }

            let candidate_resolution = item.resolution.dppx();
            debug_assert!(
                candidate_resolution >= 0.0,
                "Resolutions should be non-negative"
            );
            if candidate_resolution == 0.0 {
                // If the resolution is 0, we also treat it as an invalid image.
                continue;
            }

            // https://drafts.csswg.org/css-images-4/#image-set-notation:
            //
            //     Make a UA-specific choice of which to load, based on whatever criteria deemed
            //     relevant (such as the resolution of the display, connection speed, etc).
            //
            // For now, select the lowest resolution greater than display density, otherwise the
            // greatest resolution available.
            let better_candidate = || {
                if selected_resolution < dpr && candidate_resolution > selected_resolution {
                    return true;
                }
                if candidate_resolution < selected_resolution && candidate_resolution >= dpr {
                    return true;
                }
                false
            };

            // The first item with a supported MIME type is obviously the current best candidate
            if !supported_image || better_candidate() {
                supported_image = true;
                selected_index = i;
                selected_resolution = candidate_resolution;
            }
        }

        ImageSet {
            selected_index,
            items,
        }
    }

    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        Self {
            selected_index: std::usize::MAX,
            items: ToComputedValue::from_computed_value(&computed.items),
        }
    }
}

impl generic::LineDirection for LineDirection {
    fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool {
        match *self {
            LineDirection::Angle(angle) => angle.radians() == PI,
            LineDirection::Vertical(VerticalPositionKeyword::Bottom) => {
                compat_mode == GradientCompatMode::Modern
            },
            LineDirection::Vertical(VerticalPositionKeyword::Top) => {
                compat_mode != GradientCompatMode::Modern
            },
            _ => false,
        }
    }

    fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result
    where
        W: Write,
    {
        match *self {
            LineDirection::Angle(ref angle) => angle.to_css(dest),
            LineDirection::Horizontal(x) => {
                if compat_mode == GradientCompatMode::Modern {
                    dest.write_str("to ")?;
                }
                x.to_css(dest)
            },
            LineDirection::Vertical(y) => {
                if compat_mode == GradientCompatMode::Modern {
                    dest.write_str("to ")?;
                }
                y.to_css(dest)
            },
            LineDirection::Corner(x, y) => {
                if compat_mode == GradientCompatMode::Modern {
                    dest.write_str("to ")?;
                }
                x.to_css(dest)?;
                dest.write_char(' ')?;
                y.to_css(dest)
            },
        }
    }
}

impl ToComputedValue for specified::LineDirection {
    type ComputedValue = LineDirection;

    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
        match *self {
            specified::LineDirection::Angle(ref angle) => {
                LineDirection::Angle(angle.to_computed_value(context))
            },
            specified::LineDirection::Horizontal(x) => LineDirection::Horizontal(x),
            specified::LineDirection::Vertical(y) => LineDirection::Vertical(y),
            specified::LineDirection::Corner(x, y) => LineDirection::Corner(x, y),
        }
    }

    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
        match *computed {
            LineDirection::Angle(ref angle) => {
                specified::LineDirection::Angle(ToComputedValue::from_computed_value(angle))
            },
            LineDirection::Horizontal(x) => specified::LineDirection::Horizontal(x),
            LineDirection::Vertical(y) => specified::LineDirection::Vertical(y),
            LineDirection::Corner(x, y) => specified::LineDirection::Corner(x, y),
        }
    }
}