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