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