1use cssparser::{ParseErrorKind, Parser};
4use cssparser_color as cssc;
5use cssparser_color::{hsl_to_rgb, hwb_to_rgb};
6
7use crate::error::*;
8use crate::parsers::Parse;
9use crate::unit_interval::UnitInterval;
10use crate::util;
11
12#[derive(Clone, Copy, PartialEq, Debug)]
14pub enum Color {
15 CurrentColor,
17 Rgba(RGBA),
19 Hsl(Hsl),
21 Hwb(Hwb),
23}
24
25#[allow(clippy::upper_case_acronyms)]
27#[derive(Clone, Copy, PartialEq, Debug)]
28pub struct RGBA {
29 pub red: u8,
31 pub green: u8,
33 pub blue: u8,
35 pub alpha: f32,
37}
38
39#[derive(Clone, Copy, PartialEq, Debug)]
41pub struct Hsl {
42 pub hue: Option<f32>,
44 pub saturation: Option<f32>,
46 pub lightness: Option<f32>,
48 pub alpha: Option<f32>,
50}
51
52#[derive(Clone, Copy, PartialEq, Debug)]
54pub struct Hwb {
55 pub hue: Option<f32>,
57 pub whiteness: Option<f32>,
59 pub blackness: Option<f32>,
61 pub alpha: Option<f32>,
63}
64
65const OPAQUE: f32 = 1.0;
66
67impl RGBA {
68 #[inline]
72 fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
73 Self::new(
74 clamp_unit_f32(red),
75 clamp_unit_f32(green),
76 clamp_unit_f32(blue),
77 alpha.clamp(0.0, OPAQUE),
78 )
79 }
80
81 #[inline]
83 pub const fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
84 Self {
85 red,
86 green,
87 blue,
88 alpha,
89 }
90 }
91}
92
93impl From<cssc::RgbaLegacy> for RGBA {
94 fn from(c: cssc::RgbaLegacy) -> RGBA {
95 RGBA {
96 red: c.red,
97 green: c.green,
98 blue: c.blue,
99 alpha: c.alpha,
100 }
101 }
102}
103
104impl From<cssc::Hsl> for Hsl {
105 fn from(c: cssc::Hsl) -> Hsl {
106 Hsl {
107 hue: c.hue,
108 saturation: c.saturation,
109 lightness: c.lightness,
110 alpha: c.alpha,
111 }
112 }
113}
114
115impl From<cssc::Hwb> for Hwb {
116 fn from(c: cssc::Hwb) -> Hwb {
117 Hwb {
118 hue: c.hue,
119 whiteness: c.whiteness,
120 blackness: c.blackness,
121 alpha: c.alpha,
122 }
123 }
124}
125
126fn clamp_unit_f32(val: f32) -> u8 {
127 clamp_floor_256_f32(val * 255.)
142}
143
144fn clamp_floor_256_f32(val: f32) -> u8 {
145 val.round().clamp(0., 255.) as u8
146}
147
148fn map_color_parse_error(err: cssparser::ParseError<'_, ()>) -> ParseError<'_> {
154 let string_err = match err.kind {
155 ParseErrorKind::Basic(ref e) => format!("{}", e),
156 ParseErrorKind::Custom(()) => {
157 "could not parse color".to_string()
171 }
172 };
173
174 ParseError {
175 kind: ParseErrorKind::Custom(ValueErrorKind::Parse(string_err)),
176 location: err.location,
177 }
178}
179
180fn parse_plain_color<'i>(parser: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i>> {
181 let loc = parser.current_source_location();
182
183 let csscolor = cssc::Color::parse(parser).map_err(map_color_parse_error)?;
184
185 match csscolor {
187 cssc::Color::CurrentColor => Ok(Color::CurrentColor),
188
189 cssc::Color::Rgba(rgba) => Ok(Color::Rgba(rgba.into())),
190
191 cssc::Color::Hsl(hsl) => Ok(Color::Hsl(hsl.into())),
192
193 cssc::Color::Hwb(hwb) => Ok(Color::Hwb(hwb.into())),
194
195 _ => Err(ParseError {
196 kind: ParseErrorKind::Custom(ValueErrorKind::parse_error("unsupported color syntax")),
197 location: loc,
198 }),
199 }
200}
201
202fn parse_name(s: &str) -> Result<&str, ()> {
206 if s.starts_with("--") && s.len() > 2 {
207 Ok(&s[2..])
208 } else {
209 Err(())
210 }
211}
212
213fn parse_var_with_fallback<'i>(parser: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i>> {
214 let name = parser.expect_ident_cloned()?;
215
216 let _name = parse_name(&name).map_err(|()| {
219 parser.new_custom_error(ValueErrorKind::parse_error(&format!(
220 "unexpected identifier {}",
221 name
222 )))
223 })?;
224
225 parser.expect_comma()?;
226
227 parse_plain_color(parser)
236}
237
238impl Parse for Color {
239 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i>> {
240 if let Ok(c) = parser.try_parse(|p| {
241 p.expect_function_matching("var")?;
242 p.parse_nested_block(parse_var_with_fallback)
243 }) {
244 Ok(c)
245 } else {
246 parse_plain_color(parser)
247 }
248 }
249}
250
251fn normalize_hue(h: f32) -> f32 {
258 h.rem_euclid(360.0) / 360.0
259}
260
261pub fn color_to_rgba(color: &Color) -> RGBA {
262 match color {
263 Color::Rgba(rgba) => *rgba,
264
265 Color::Hsl(hsl) => {
266 let hue = normalize_hue(hsl.hue.unwrap_or(0.0));
267 let (red, green, blue) = hsl_to_rgb(
268 hue,
269 hsl.saturation.unwrap_or(0.0),
270 hsl.lightness.unwrap_or(0.0),
271 );
272
273 RGBA::from_floats(red, green, blue, hsl.alpha.unwrap_or(OPAQUE))
274 }
275
276 Color::Hwb(hwb) => {
277 let hue = normalize_hue(hwb.hue.unwrap_or(0.0));
278 let (red, green, blue) = hwb_to_rgb(
279 hue,
280 hwb.whiteness.unwrap_or(0.0),
281 hwb.blackness.unwrap_or(0.0),
282 );
283
284 RGBA::from_floats(red, green, blue, hwb.alpha.unwrap_or(OPAQUE))
285 }
286
287 _ => unimplemented!(),
288 }
289}
290
291fn resolve_alpha(opacity: UnitInterval, alpha: Option<f32>) -> f32 {
296 let UnitInterval(o) = opacity;
297
298 let alpha = f64::from(alpha.unwrap_or(0.0)) * o;
299 let alpha = util::clamp(alpha, 0.0, 1.0);
300 cast::f32(alpha).unwrap()
301}
302
303fn black() -> Color {
304 Color::Rgba(RGBA::new(0, 0, 0, 1.0))
305}
306
307pub fn resolve_color(color: &Color, opacity: UnitInterval, current_color: &Color) -> Color {
315 let without_opacity_applied = match color {
316 Color::CurrentColor => {
317 if let Color::CurrentColor = current_color {
318 black()
319 } else {
320 *current_color
321 }
322 }
323
324 _ => *color,
325 };
326
327 match without_opacity_applied {
328 Color::CurrentColor => unreachable!(),
329
330 Color::Rgba(rgba) => Color::Rgba(RGBA {
331 alpha: resolve_alpha(opacity, Some(rgba.alpha)),
332 ..rgba
333 }),
334
335 Color::Hsl(hsl) => Color::Hsl(Hsl {
336 alpha: Some(resolve_alpha(opacity, hsl.alpha)),
337 ..hsl
338 }),
339
340 Color::Hwb(hwb) => Color::Hwb(Hwb {
341 alpha: Some(resolve_alpha(opacity, hwb.alpha)),
342 ..hwb
343 }),
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn parses_plain_color() {
353 assert_eq!(
354 Color::parse_str("#112233").unwrap(),
355 Color::Rgba(RGBA::new(0x11, 0x22, 0x33, 1.0))
356 );
357 }
358
359 #[test]
360 fn var_with_fallback_parses_as_color() {
361 assert_eq!(
362 Color::parse_str("var(--foo, #112233)").unwrap(),
363 Color::Rgba(RGBA::new(0x11, 0x22, 0x33, 1.0))
364 );
365
366 assert_eq!(
367 Color::parse_str("var(--foo, rgb(100% 50% 25%)").unwrap(),
368 Color::Rgba(RGBA::new(0xff, 0x80, 0x40, 1.0))
369 );
370 }
371
372 #[test]
375 fn var_without_fallback_yields_error() {
376 assert!(Color::parse_str("var(--foo)").is_err());
377 assert!(Color::parse_str("var(--foo,)").is_err());
378 assert!(Color::parse_str("var(--foo, )").is_err());
379 assert!(Color::parse_str("var(--foo, this is not a color)").is_err());
380 assert!(Color::parse_str("var(--foo, #112233, blah)").is_err());
381 }
382
383 #[test]
384 fn normalizes_hue() {
385 assert_eq!(normalize_hue(0.0), 0.0);
386 assert_eq!(normalize_hue(360.0), 0.0);
387 assert_eq!(normalize_hue(90.0), 0.25);
388 assert_eq!(normalize_hue(-90.0), 0.75);
389 assert_eq!(normalize_hue(450.0), 0.25); assert_eq!(normalize_hue(-450.0), 0.75);
391 }
392
393 #[test]
395 fn large_hue_value() {
396 let _ = color_to_rgba(&Color::parse_str("hsla(70000000000000,4%,10%,.2)").unwrap());
397 }
398}