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