Skip to main content

style/values/computed/
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//! Computed color values.
6
7use crate::color::AbsoluteColor;
8use crate::values::animated::ToAnimatedZero;
9use crate::values::computed::percentage::Percentage;
10use crate::values::generics::color::{
11    GenericCaretColor, GenericColor, GenericColorMix, GenericColorOrAuto,
12};
13use std::fmt::{self, Write};
14use style_traits::{CssWriter, ToCss};
15
16pub use crate::values::specified::color::{ColorScheme, ForcedColorAdjust, PrintColorAdjust};
17
18/// The computed value of the `color` property.
19pub type ColorPropertyValue = AbsoluteColor;
20
21/// A computed value for `<color>`.
22pub type Color = GenericColor<Percentage>;
23
24/// A computed color-mix().
25pub type ColorMix = GenericColorMix<Color, Percentage>;
26
27impl ToCss for Color {
28    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
29    where
30        W: fmt::Write,
31    {
32        match *self {
33            Self::Absolute(ref c) => c.to_css(dest),
34            Self::ColorFunction(ref color_function) => color_function.to_css(dest),
35            Self::CurrentColor => dest.write_str("currentcolor"),
36            Self::ColorMix(ref m) => m.to_css(dest),
37            Self::ContrastColor(ref c) => {
38                dest.write_str("contrast-color(")?;
39                c.to_css(dest)?;
40                dest.write_char(')')
41            },
42        }
43    }
44}
45
46impl Color {
47    /// A fully transparent color.
48    pub const TRANSPARENT_BLACK: Self = Self::Absolute(AbsoluteColor::TRANSPARENT_BLACK);
49
50    /// An opaque black color.
51    pub const BLACK: Self = Self::Absolute(AbsoluteColor::BLACK);
52
53    /// An opaque white color.
54    pub const WHITE: Self = Self::Absolute(AbsoluteColor::WHITE);
55
56    /// Create a new computed [`Color`] from a given color-mix, simplifying it to an absolute color
57    /// if possible.
58    pub fn from_color_mix(color_mix: ColorMix) -> Self {
59        if let Some(absolute) = color_mix.mix_to_absolute() {
60            Self::Absolute(absolute)
61        } else {
62            Self::ColorMix(Box::new(color_mix))
63        }
64    }
65
66    /// Combine this complex color with the given foreground color into an
67    /// absolute color.
68    pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
69        use crate::values::specified::percentage::ToPercentage;
70
71        match *self {
72            Self::Absolute(c) => c,
73            Self::ColorFunction(ref color_function) => {
74                color_function.resolve_to_absolute(current_color)
75            },
76            Self::CurrentColor => *current_color,
77            Self::ColorMix(ref mix) => {
78                use crate::color::mix;
79
80                mix::mix_many(
81                    mix.interpolation,
82                    mix.items.iter().map(|item| {
83                        mix::ColorMixItem::new(
84                            item.color.resolve_to_absolute(current_color),
85                            item.percentage.to_percentage(),
86                        )
87                    }),
88                    mix.flags,
89                )
90            },
91            Self::ContrastColor(ref c) => {
92                let bg_color = c.resolve_to_absolute(current_color);
93                if Self::contrast_ratio(&bg_color, &AbsoluteColor::BLACK)
94                    > Self::contrast_ratio(&bg_color, &AbsoluteColor::WHITE)
95                {
96                    AbsoluteColor::BLACK
97                } else {
98                    AbsoluteColor::WHITE
99                }
100            },
101        }
102    }
103
104    fn contrast_ratio(a: &AbsoluteColor, b: &AbsoluteColor) -> f32 {
105        // TODO: This just implements the WCAG 2.1 algorithm,
106        // https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
107        // Consider using a more sophisticated contrast algorithm, e.g. see
108        // https://apcacontrast.com
109        let compute = |c| -> f32 {
110            if c <= 0.04045 {
111                c / 12.92
112            } else {
113                f32::powf((c + 0.055) / 1.055, 2.4)
114            }
115        };
116        let luminance = |r, g, b| -> f32 { 0.2126 * r + 0.7152 * g + 0.0722 * b };
117        let a = a.into_srgb_legacy();
118        let b = b.into_srgb_legacy();
119        let a = a.raw_components();
120        let b = b.raw_components();
121        let la = luminance(compute(a[0]), compute(a[1]), compute(a[2])) + 0.05;
122        let lb = luminance(compute(b[0]), compute(b[1]), compute(b[2])) + 0.05;
123        if la > lb {
124            la / lb
125        } else {
126            lb / la
127        }
128    }
129}
130
131impl ToAnimatedZero for AbsoluteColor {
132    fn to_animated_zero(&self) -> Result<Self, ()> {
133        Ok(Self::TRANSPARENT_BLACK)
134    }
135}
136
137/// auto | <color>
138pub type ColorOrAuto = GenericColorOrAuto<Color>;
139
140/// caret-color
141pub type CaretColor = GenericCaretColor<Color>;