Skip to main content

style/values/generics/
color.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//! Generic types for color properties.
6
7use crate::color::ColorMixItemList;
8use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorFunction};
9use crate::derives::*;
10use crate::values::{
11    computed::ToComputedValue, specified::percentage::ToPercentage, ParseError, Parser,
12};
13use std::fmt::{self, Write};
14use style_traits::{owned_slice::OwnedSlice, CssWriter, ToCss};
15
16/// This struct represents a combined color from a numeric color and
17/// the current foreground color (currentcolor keyword).
18#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem, ToTyped)]
19#[repr(C)]
20pub enum GenericColor<Percentage> {
21    /// The actual numeric color.
22    Absolute(AbsoluteColor),
23    /// A unresolvable color.
24    ColorFunction(Box<ColorFunction<Self>>),
25    /// The `CurrentColor` keyword.
26    CurrentColor,
27    /// The color-mix() function.
28    ColorMix(Box<GenericColorMix<Self, Percentage>>),
29    /// The contrast-color() function.
30    ContrastColor(Box<Self>),
31}
32
33/// Flags used to modify the calculation of a color mix result.
34#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
35#[repr(C)]
36pub struct ColorMixFlags(u8);
37bitflags! {
38    impl ColorMixFlags : u8 {
39        /// Normalize the weights of the mix.
40        const NORMALIZE_WEIGHTS = 1 << 0;
41        /// The result should always be converted to the modern color syntax.
42        const RESULT_IN_MODERN_SYNTAX = 1 << 1;
43    }
44}
45
46/// One `(color, percentage)` component of a `color-mix()` expression.
47#[derive(
48    Clone,
49    Debug,
50    MallocSizeOf,
51    PartialEq,
52    ToAnimatedValue,
53    ToComputedValue,
54    ToResolvedValue,
55    ToShmem,
56)]
57#[allow(missing_docs)]
58#[repr(C)]
59pub struct GenericColorMixItem<Color, Percentage> {
60    pub color: Color,
61    pub percentage: Percentage,
62}
63
64/// A restricted version of the css `color-mix()` function, which only supports
65/// percentages.
66///
67/// https://drafts.csswg.org/css-color-5/#color-mix
68#[derive(
69    Clone,
70    Debug,
71    MallocSizeOf,
72    PartialEq,
73    ToAnimatedValue,
74    ToComputedValue,
75    ToResolvedValue,
76    ToShmem,
77)]
78#[allow(missing_docs)]
79#[repr(C)]
80pub struct GenericColorMix<Color, Percentage> {
81    pub interpolation: ColorInterpolationMethod,
82    pub items: OwnedSlice<GenericColorMixItem<Color, Percentage>>,
83    pub flags: ColorMixFlags,
84}
85
86pub use self::GenericColorMix as ColorMix;
87
88impl<Color: ToCss, Percentage: ToCss + ToPercentage> ToCss for ColorMix<Color, Percentage> {
89    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
90    where
91        W: Write,
92    {
93        dest.write_str("color-mix(")?;
94
95        // If the color interpolation method is oklab (which is now the default),
96        // it can be omitted.
97        // See: https://github.com/web-platform-tests/interop/issues/1166
98        if !self.interpolation.is_default() {
99            self.interpolation.to_css(dest)?;
100            dest.write_str(", ")?;
101        }
102
103        let uniform = self
104            .items
105            .split_first()
106            .map(|(first, rest)| {
107                rest.iter()
108                    .all(|item| item.percentage.to_percentage() == first.percentage.to_percentage())
109            })
110            .unwrap_or(false);
111        let uniform_value = 1.0 / self.items.len() as f32;
112
113        let is_pair = self.items.len() == 2;
114
115        for (index, item) in self.items.iter().enumerate() {
116            if index != 0 {
117                dest.write_str(", ")?;
118            }
119
120            item.color.to_css(dest)?;
121
122            let omit = if is_pair {
123                let can_omit = |a: &Percentage, b: &Percentage, is_left| {
124                    if a.is_calc() {
125                        return false;
126                    }
127                    if a.to_percentage() == 0.5 {
128                        return b.to_percentage() == 0.5;
129                    }
130                    if is_left {
131                        return false;
132                    }
133                    (1.0 - a.to_percentage() - b.to_percentage()).abs() <= f32::EPSILON
134                };
135
136                let other = &self.items[1 - index].percentage;
137                can_omit(&item.percentage, other, index == 0)
138            } else {
139                !item.percentage.is_calc()
140                    && uniform
141                    && item.percentage.to_percentage() == uniform_value
142            };
143
144            if !omit {
145                dest.write_char(' ')?;
146                item.percentage.to_css(dest)?;
147            }
148        }
149
150        dest.write_char(')')
151    }
152}
153
154impl<Percentage> ColorMix<GenericColor<Percentage>, Percentage> {
155    /// Mix the colors so that we get a single color. If any of the 2 colors are
156    /// not mixable (perhaps not absolute?), then return None.
157    pub fn mix_to_absolute(&self) -> Option<AbsoluteColor>
158    where
159        Percentage: ToPercentage,
160    {
161        use crate::color::mix;
162
163        let mut items = ColorMixItemList::with_capacity(self.items.len());
164        for item in self.items.iter() {
165            items.push(mix::ColorMixItem::new(
166                *item.color.as_absolute()?,
167                item.percentage.to_percentage(),
168            ))
169        }
170
171        Some(mix::mix_many(self.interpolation, items, self.flags))
172    }
173}
174
175pub use self::GenericColor as Color;
176
177impl<Percentage> Color<Percentage> {
178    /// If this color is absolute return it's value, otherwise return None.
179    pub fn as_absolute(&self) -> Option<&AbsoluteColor> {
180        match *self {
181            Self::Absolute(ref absolute) => Some(absolute),
182            _ => None,
183        }
184    }
185
186    /// Returns a color value representing currentcolor.
187    pub fn currentcolor() -> Self {
188        Self::CurrentColor
189    }
190
191    /// Whether it is a currentcolor value (no numeric color component).
192    pub fn is_currentcolor(&self) -> bool {
193        matches!(*self, Self::CurrentColor)
194    }
195
196    /// Whether this color is an absolute color.
197    pub fn is_absolute(&self) -> bool {
198        matches!(*self, Self::Absolute(..))
199    }
200}
201
202/// Either `<color>` or `auto`.
203#[derive(
204    Animate,
205    Clone,
206    ComputeSquaredDistance,
207    Copy,
208    Debug,
209    MallocSizeOf,
210    PartialEq,
211    Parse,
212    SpecifiedValueInfo,
213    ToAnimatedValue,
214    ToAnimatedZero,
215    ToComputedValue,
216    ToResolvedValue,
217    ToCss,
218    ToShmem,
219    ToTyped,
220)]
221#[repr(C, u8)]
222pub enum GenericColorOrAuto<C> {
223    /// A `<color>`.
224    Color(C),
225    /// `auto`
226    Auto,
227}
228
229pub use self::GenericColorOrAuto as ColorOrAuto;
230
231/// Caret color is effectively a ColorOrAuto, but resolves `auto` to
232/// currentColor.
233#[derive(
234    Animate,
235    Clone,
236    ComputeSquaredDistance,
237    Copy,
238    Debug,
239    MallocSizeOf,
240    PartialEq,
241    SpecifiedValueInfo,
242    ToAnimatedValue,
243    ToAnimatedZero,
244    ToComputedValue,
245    ToCss,
246    ToShmem,
247    ToTyped,
248)]
249#[repr(transparent)]
250pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>);
251
252impl<C> GenericCaretColor<C> {
253    /// Returns the `auto` value.
254    pub fn auto() -> Self {
255        GenericCaretColor(GenericColorOrAuto::Auto)
256    }
257}
258
259pub use self::GenericCaretColor as CaretColor;
260
261/// A light-dark(<light>, <dark>) function.
262#[derive(
263    Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem, ToCss, ToResolvedValue,
264)]
265#[css(function = "light-dark", comma)]
266#[repr(C)]
267pub struct GenericLightDark<T> {
268    /// The value returned when using a light theme.
269    pub light: T,
270    /// The value returned when using a dark theme.
271    pub dark: T,
272}
273
274impl<T> GenericLightDark<T> {
275    /// Parse the arguments of the light-dark() function.
276    pub fn parse_args_with<'i>(
277        input: &mut Parser<'i, '_>,
278        mut parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>,
279    ) -> Result<Self, ParseError<'i>> {
280        let light = parse_one(input)?;
281        input.expect_comma()?;
282        let dark = parse_one(input)?;
283        Ok(Self { light, dark })
284    }
285
286    /// Parse the light-dark() function.
287    pub fn parse_with<'i>(
288        input: &mut Parser<'i, '_>,
289        parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>,
290    ) -> Result<Self, ParseError<'i>> {
291        input.expect_function_matching("light-dark")?;
292        input.parse_nested_block(|input| Self::parse_args_with(input, parse_one))
293    }
294}
295
296impl<T: ToComputedValue> GenericLightDark<T> {
297    /// Choose the light or dark version of this value for computation purposes, and compute it.
298    pub fn compute(&self, cx: &crate::values::computed::Context) -> T::ComputedValue {
299        let dark = cx.device().is_dark_color_scheme(cx.builder.color_scheme);
300        if cx.for_non_inherited_property {
301            cx.rule_cache_conditions
302                .borrow_mut()
303                .set_color_scheme_dependency(cx.builder.color_scheme);
304        }
305        let chosen = if dark { &self.dark } else { &self.light };
306        chosen.to_computed_value(cx)
307    }
308}