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)]
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                    // Percentages are enforced to be resolvable at parse time for the specified
128                    // colors, and are already resolved for computed colors.
129                    let a = a.to_percentage().unwrap();
130                    let b = b.to_percentage().unwrap();
131                    if a == 0.5 {
132                        return b == 0.5;
133                    }
134                    if is_left {
135                        return false;
136                    }
137                    (1.0 - a - b).abs() <= f32::EPSILON
138                };
139
140                let other = &self.items[1 - index].percentage;
141                can_omit(&item.percentage, other, index == 0)
142            } else {
143                !item.percentage.is_calc()
144                    && uniform
145                    && item.percentage.to_percentage() == Some(uniform_value)
146            };
147
148            if !omit {
149                dest.write_char(' ')?;
150                item.percentage.to_css(dest)?;
151            }
152        }
153
154        dest.write_char(')')
155    }
156}
157
158impl<Percentage> ColorMix<GenericColor<Percentage>, Percentage> {
159    /// Mix the colors so that we get a single color. If any of the 2 colors are
160    /// not mixable (perhaps not absolute?), then return None.
161    pub fn mix_to_absolute(&self) -> Option<AbsoluteColor>
162    where
163        Percentage: ToPercentage,
164    {
165        use crate::color::mix;
166
167        let mut items = ColorMixItemList::with_capacity(self.items.len());
168        for item in self.items.iter() {
169            items.push(mix::ColorMixItem::new(
170                *item.color.as_absolute()?,
171                item.percentage.to_percentage()?,
172            ))
173        }
174
175        Some(mix::mix_many(self.interpolation, items, self.flags))
176    }
177}
178
179pub use self::GenericColor as Color;
180
181impl<Percentage> Color<Percentage> {
182    /// If this color is absolute return it's value, otherwise return None.
183    pub fn as_absolute(&self) -> Option<&AbsoluteColor> {
184        match *self {
185            Self::Absolute(ref absolute) => Some(absolute),
186            _ => None,
187        }
188    }
189
190    /// Returns a color value representing currentcolor.
191    pub fn currentcolor() -> Self {
192        Self::CurrentColor
193    }
194
195    /// Whether it is a currentcolor value (no numeric color component).
196    pub fn is_currentcolor(&self) -> bool {
197        matches!(*self, Self::CurrentColor)
198    }
199
200    /// Whether this color is an absolute color.
201    pub fn is_absolute(&self) -> bool {
202        matches!(*self, Self::Absolute(..))
203    }
204}
205
206/// Either `<color>` or `auto`.
207#[derive(
208    Animate,
209    Clone,
210    ComputeSquaredDistance,
211    Copy,
212    Debug,
213    MallocSizeOf,
214    PartialEq,
215    Parse,
216    SpecifiedValueInfo,
217    ToAnimatedValue,
218    ToAnimatedZero,
219    ToComputedValue,
220    ToResolvedValue,
221    ToCss,
222    ToShmem,
223    ToTyped,
224)]
225#[repr(C, u8)]
226pub enum GenericColorOrAuto<C> {
227    /// A `<color>`.
228    Color(C),
229    /// `auto`
230    Auto,
231}
232
233pub use self::GenericColorOrAuto as ColorOrAuto;
234
235/// Caret color is effectively a ColorOrAuto, but resolves `auto` to
236/// currentColor.
237#[derive(
238    Animate,
239    Clone,
240    ComputeSquaredDistance,
241    Copy,
242    Debug,
243    MallocSizeOf,
244    PartialEq,
245    SpecifiedValueInfo,
246    ToAnimatedValue,
247    ToAnimatedZero,
248    ToComputedValue,
249    ToCss,
250    ToShmem,
251    ToTyped,
252)]
253#[repr(transparent)]
254pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>);
255
256impl<C> GenericCaretColor<C> {
257    /// Returns the `auto` value.
258    pub fn auto() -> Self {
259        GenericCaretColor(GenericColorOrAuto::Auto)
260    }
261}
262
263pub use self::GenericCaretColor as CaretColor;
264
265/// A light-dark(<light>, <dark>) function.
266#[derive(
267    Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem, ToCss, ToResolvedValue,
268)]
269#[css(function = "light-dark", comma)]
270#[repr(C)]
271pub struct GenericLightDark<T> {
272    /// The value returned when using a light theme.
273    pub light: T,
274    /// The value returned when using a dark theme.
275    pub dark: T,
276}
277
278impl<T> GenericLightDark<T> {
279    /// Parse the arguments of the light-dark() function.
280    pub fn parse_args_with<'i>(
281        input: &mut Parser<'i, '_>,
282        mut parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>,
283    ) -> Result<Self, ParseError<'i>> {
284        let light = parse_one(input)?;
285        input.expect_comma()?;
286        let dark = parse_one(input)?;
287        Ok(Self { light, dark })
288    }
289
290    /// Parse the light-dark() function.
291    pub fn parse_with<'i>(
292        input: &mut Parser<'i, '_>,
293        parse_one: impl FnMut(&mut Parser<'i, '_>) -> Result<T, ParseError<'i>>,
294    ) -> Result<Self, ParseError<'i>> {
295        input.expect_function_matching("light-dark")?;
296        input.parse_nested_block(|input| Self::parse_args_with(input, parse_one))
297    }
298}
299
300impl<T: ToComputedValue> GenericLightDark<T> {
301    /// Choose the light or dark version of this value for computation purposes, and compute it.
302    pub fn compute(&self, cx: &crate::values::computed::Context) -> T::ComputedValue {
303        let dark = cx.device().is_dark_color_scheme(cx.builder.color_scheme);
304        if cx.for_non_inherited_property {
305            cx.rule_cache_conditions
306                .borrow_mut()
307                .set_color_scheme_dependency(cx.builder.color_scheme);
308        }
309        let chosen = if dark { &self.dark } else { &self.light };
310        chosen.to_computed_value(cx)
311    }
312}