1
//! CSS color values.
2

            
3
use cssparser::{hsl_to_rgb, hwb_to_rgb, Color, ParseErrorKind, Parser, RGBA};
4

            
5
use crate::error::*;
6
use crate::parsers::Parse;
7

            
8
/// Turn a short-lived [`cssparser::ParseError`] into a long-lived [`ParseError`].
9
///
10
/// cssparser's error type has a lifetime equal to the string being parsed.  We want
11
/// a long-lived error so we can store it away if needed.  Basically, here we turn
12
/// a `&str` into a `String`.
13
17
fn map_color_parse_error(err: cssparser::ParseError<'_, ()>) -> ParseError<'_> {
14
17
    let string_err = match err.kind {
15
17
        ParseErrorKind::Basic(ref e) => format!("{}", e),
16
        ParseErrorKind::Custom(()) => {
17
            // In cssparser 0.31, the error type for Color::parse is defined like this:
18
            //
19
            //   pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i, ()>> {
20
            //
21
            // The ParseError<'i, ()> means that the ParseErrorKind::Custom(T) variant will have
22
            // T be the () type.
23
            //
24
            // So, here we match for () inside the Custom variant.  If cssparser
25
            // changes its error API, this match will hopefully catch errors.
26
            //
27
            // Implementation detail: Color::parse() does not ever return Custom errors, only
28
            // Basic ones.  So the match for Basic above handles everything, and this one
29
            // for () is a dummy case.
30
            "could not parse color".to_string()
31
        }
32
    };
33

            
34
17
    ParseError {
35
17
        kind: ParseErrorKind::Custom(ValueErrorKind::Parse(string_err)),
36
17
        location: err.location,
37
    }
38
17
}
39

            
40
1030317
fn parse_plain_color<'i>(parser: &mut Parser<'i, '_>) -> Result<cssparser::Color, ParseError<'i>> {
41
1030317
    let loc = parser.current_source_location();
42

            
43
1030317
    let color = cssparser::Color::parse(parser).map_err(map_color_parse_error)?;
44

            
45
    // Return only supported color types, and mark the others as errors.
46
1030300
    match color {
47
1030290
        Color::CurrentColor | Color::Rgba(_) | Color::Hsl(_) | Color::Hwb(_) => Ok(color),
48

            
49
10
        _ => Err(ParseError {
50
10
            kind: ParseErrorKind::Custom(ValueErrorKind::parse_error("unsupported color syntax")),
51
            location: loc,
52
10
        }),
53
    }
54
1030317
}
55

            
56
/// Parse a custom property name.
57
///
58
/// <https://drafts.csswg.org/css-variables/#typedef-custom-property-name>
59
7
fn parse_name(s: &str) -> Result<&str, ()> {
60
7
    if s.starts_with("--") && s.len() > 2 {
61
7
        Ok(&s[2..])
62
    } else {
63
        Err(())
64
    }
65
7
}
66

            
67
7
fn parse_var_with_fallback<'i>(
68
    parser: &mut Parser<'i, '_>,
69
) -> Result<cssparser::Color, ParseError<'i>> {
70
7
    let name = parser.expect_ident_cloned()?;
71

            
72
    // ignore the name for now; we'll use it later when we actually
73
    // process the names of custom variables
74
7
    let _name = parse_name(&name).map_err(|()| {
75
        parser.new_custom_error(ValueErrorKind::parse_error(&format!(
76
            "unexpected identifier {}",
77
            name
78
        )))
79
    })?;
80

            
81
14
    parser.expect_comma()?;
82

            
83
    // FIXME: when fixing #459 (full support for var()), note that
84
    // https://drafts.csswg.org/css-variables/#using-variables indicates that var(--a,) is
85
    // a valid function, which means that the fallback value is an empty set of tokens.
86
    //
87
    // Also, see Servo's extra code to handle semicolons and stuff in toplevel rules.
88
    //
89
    // Also, tweak the tests tagged with "FIXME: var()" below.
90

            
91
6
    parse_plain_color(parser)
92
7
}
93

            
94
impl Parse for cssparser::Color {
95
1030312
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<cssparser::Color, ParseError<'i>> {
96
2060638
        if let Ok(c) = parser.try_parse(|p| {
97
1030326
            p.expect_function_matching("var")?;
98
7
            p.parse_nested_block(parse_var_with_fallback)
99
1030326
        }) {
100
2
            Ok(c)
101
        } else {
102
1030310
            parse_plain_color(parser)
103
        }
104
1030312
    }
105
}
106

            
107
949775
pub fn color_to_rgba(color: &Color) -> RGBA {
108
949775
    match color {
109
949763
        Color::Rgba(rgba) => *rgba,
110

            
111
6
        Color::Hsl(hsl) => {
112
6
            let (red, green, blue) = hsl_to_rgb(
113
6
                hsl.hue.unwrap_or(0.0) / 360.0,
114
6
                hsl.saturation.unwrap_or(0.0),
115
6
                hsl.lightness.unwrap_or(0.0),
116
            );
117

            
118
6
            RGBA::from_floats(Some(red), Some(green), Some(blue), hsl.alpha)
119
        }
120

            
121
6
        Color::Hwb(hwb) => {
122
6
            let (red, green, blue) = hwb_to_rgb(
123
6
                hwb.hue.unwrap_or(0.0) / 360.0,
124
6
                hwb.whiteness.unwrap_or(0.0),
125
6
                hwb.blackness.unwrap_or(0.0),
126
            );
127

            
128
6
            RGBA::from_floats(Some(red), Some(green), Some(blue), hwb.alpha)
129
        }
130

            
131
        _ => unimplemented!(),
132
    }
133
949775
}
134

            
135
#[cfg(test)]
136
mod tests {
137
    use super::*;
138

            
139
    #[test]
140
2
    fn parses_plain_color() {
141
1
        assert_eq!(
142
1
            Color::parse_str("#112233").unwrap(),
143
1
            Color::Rgba(RGBA::new(Some(0x11), Some(0x22), Some(0x33), Some(1.0)))
144
        );
145
2
    }
146

            
147
    #[test]
148
2
    fn var_with_fallback_parses_as_color() {
149
1
        assert_eq!(
150
1
            Color::parse_str("var(--foo, #112233)").unwrap(),
151
1
            Color::Rgba(RGBA::new(Some(0x11), Some(0x22), Some(0x33), Some(1.0)))
152
        );
153

            
154
1
        assert_eq!(
155
1
            Color::parse_str("var(--foo, rgb(100% 50% 25%)").unwrap(),
156
1
            Color::Rgba(RGBA::new(Some(0xff), Some(0x80), Some(0x40), Some(1.0)))
157
        );
158
2
    }
159

            
160
    // FIXME: var() - when fixing #459, see the note in the code above.  All the syntaxes
161
    // in this test function will become valid once we have full support for var().
162
    #[test]
163
2
    fn var_without_fallback_yields_error() {
164
1
        assert!(Color::parse_str("var(--foo)").is_err());
165
1
        assert!(Color::parse_str("var(--foo,)").is_err());
166
1
        assert!(Color::parse_str("var(--foo, )").is_err());
167
1
        assert!(Color::parse_str("var(--foo, this is not a color)").is_err());
168
1
        assert!(Color::parse_str("var(--foo, #112233, blah)").is_err());
169
2
    }
170
}