1#[cfg(feature="parser")]
6use std::{fmt, f32::consts::PI};
7
8#[cfg(feature="parser")]
9use cssparser::{BasicParseError, ParseError, Parser, ToCss, Token};
10
11#[derive(Clone, Copy, PartialEq, Debug)]
13pub struct Color {
14 pub red: u8,
16 pub green: u8,
18 pub blue: u8,
20 pub alpha: u8,
22}
23
24impl Default for Color {
25 fn default() -> Color {
26 Color { red: 0, green: 0, blue: 0, alpha: 0 }
27 }
28}
29
30impl Color {
31 #[inline]
35 pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
36 Self::new(
37 clamp_unit_f32(red),
38 clamp_unit_f32(green),
39 clamp_unit_f32(blue),
40 clamp_unit_f32(alpha),
41 )
42 }
43
44 #[inline]
46 pub fn transparent() -> Self {
47 Self::new(0, 0, 0, 0)
48 }
49
50 #[inline]
52 pub fn new(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
53 Color {
54 red: red,
55 green: green,
56 blue: blue,
57 alpha: alpha,
58 }
59 }
60
61 #[inline]
63 pub fn red_f32(&self) -> f32 {
64 self.red as f32 / 255.0
65 }
66
67 #[inline]
69 pub fn green_f32(&self) -> f32 {
70 self.green as f32 / 255.0
71 }
72
73 #[inline]
75 pub fn blue_f32(&self) -> f32 {
76 self.blue as f32 / 255.0
77 }
78
79 #[inline]
81 pub fn alpha_f32(&self) -> f32 {
82 self.alpha as f32 / 255.0
83 }
84
85 #[cfg(feature="parser")]
89 pub fn parse_with<'i, 't, ComponentParser>(
90 component_parser: &ComponentParser,
91 input: &mut Parser<'i, 't>,
92 ) -> Result<Color, ParseError<'i, ComponentParser::Error>>
93 where
94 ComponentParser: ColorComponentParser<'i>,
95 {
96 let location = input.current_source_location();
98 let token = input.next()?.clone();
99 match token {
100 Token::Hash(ref value) | Token::IDHash(ref value) => {
101 Color::parse_hash(value.as_bytes())
102 }
103 Token::Ident(ref value) => parse_color_keyword(&*value),
104 Token::Function(ref name) => {
105 return input.parse_nested_block(|arguments| {
106 parse_color_function(component_parser, &*name, arguments)
107 })
108 }
109 _ => Err(()),
110 }
111 .map_err(|()| location.new_unexpected_token_error(token))
112 }
113
114 #[cfg(feature="parser")]
116 pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Color, BasicParseError<'i>> {
117 let component_parser = DefaultComponentParser;
118 Self::parse_with(&component_parser, input).map_err(ParseError::basic)
119 }
120
121 #[cfg(feature="parser")]
123 #[inline]
124 pub fn parse_hash(value: &[u8]) -> Result<Self, ()> {
125 match value.len() {
126 8 => Ok(rgba(
127 from_hex(value[0])? * 16 + from_hex(value[1])?,
128 from_hex(value[2])? * 16 + from_hex(value[3])?,
129 from_hex(value[4])? * 16 + from_hex(value[5])?,
130 from_hex(value[6])? * 16 + from_hex(value[7])?,
131 )),
132 6 => Ok(rgb(
133 from_hex(value[0])? * 16 + from_hex(value[1])?,
134 from_hex(value[2])? * 16 + from_hex(value[3])?,
135 from_hex(value[4])? * 16 + from_hex(value[5])?,
136 )),
137 4 => Ok(rgba(
138 from_hex(value[0])? * 17,
139 from_hex(value[1])? * 17,
140 from_hex(value[2])? * 17,
141 from_hex(value[3])? * 17,
142 )),
143 3 => Ok(rgb(
144 from_hex(value[0])? * 17,
145 from_hex(value[1])? * 17,
146 from_hex(value[2])? * 17,
147 )),
148 _ => Err(()),
149 }
150 }
151
152}
153
154#[cfg(feature="parser")]
155impl ToCss for Color {
156 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
157 where
158 W: fmt::Write,
159 {
160 let serialize_alpha = self.alpha != 255;
161
162 dest.write_str(if serialize_alpha { "rgba(" } else { "rgb(" })?;
163 self.red.to_css(dest)?;
164 dest.write_str(", ")?;
165 self.green.to_css(dest)?;
166 dest.write_str(", ")?;
167 self.blue.to_css(dest)?;
168 if serialize_alpha {
169 dest.write_str(", ")?;
170
171 let mut rounded_alpha = (self.alpha_f32() * 100.).round() / 100.;
173 if clamp_unit_f32(rounded_alpha) != self.alpha {
174 rounded_alpha = (self.alpha_f32() * 1000.).round() / 1000.;
175 }
176
177 rounded_alpha.to_css(dest)?;
178 }
179 dest.write_char(')')
180 }
181}
182
183#[cfg(feature="parser")]
185pub enum NumberOrPercentage {
186 Number {
188 value: f32,
190 },
191 Percentage {
193 unit_value: f32,
196 },
197}
198
199#[cfg(feature="parser")]
200impl NumberOrPercentage {
201 fn unit_value(&self) -> f32 {
202 match *self {
203 NumberOrPercentage::Number { value } => value,
204 NumberOrPercentage::Percentage { unit_value } => unit_value,
205 }
206 }
207}
208
209#[cfg(feature="parser")]
211pub enum AngleOrNumber {
212 Number {
214 value: f32,
216 },
217 Angle {
219 degrees: f32,
221 },
222}
223
224#[cfg(feature="parser")]
225impl AngleOrNumber {
226 fn degrees(&self) -> f32 {
227 match *self {
228 AngleOrNumber::Number { value } => value,
229 AngleOrNumber::Angle { degrees } => degrees,
230 }
231 }
232}
233
234#[cfg(feature="parser")]
239pub trait ColorComponentParser<'i> {
240 type Error: 'i;
242
243 fn parse_angle_or_number<'t>(
247 &self,
248 input: &mut Parser<'i, 't>,
249 ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
250 let location = input.current_source_location();
251 Ok(match *input.next()? {
252 Token::Number { value, .. } => AngleOrNumber::Number { value },
253 Token::Dimension {
254 value: v, ref unit, ..
255 } => {
256 let degrees = match_ignore_ascii_case! { &*unit,
257 "deg" => v,
258 "grad" => v * 360. / 400.,
259 "rad" => v * 360. / (2. * PI),
260 "turn" => v * 360.,
261 _ => return Err(location.new_unexpected_token_error(Token::Ident(unit.clone()))),
262 };
263
264 AngleOrNumber::Angle { degrees }
265 }
266 ref t => return Err(location.new_unexpected_token_error(t.clone())),
267 })
268 }
269
270 fn parse_percentage<'t>(
274 &self,
275 input: &mut Parser<'i, 't>,
276 ) -> Result<f32, ParseError<'i, Self::Error>> {
277 input.expect_percentage().map_err(From::from)
278 }
279
280 fn parse_number<'t>(
282 &self,
283 input: &mut Parser<'i, 't>,
284 ) -> Result<f32, ParseError<'i, Self::Error>> {
285 input.expect_number().map_err(From::from)
286 }
287
288 fn parse_number_or_percentage<'t>(
290 &self,
291 input: &mut Parser<'i, 't>,
292 ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
293 let location = input.current_source_location();
294 Ok(match *input.next()? {
295 Token::Number { value, .. } => NumberOrPercentage::Number { value },
296 Token::Percentage { unit_value, .. } => NumberOrPercentage::Percentage { unit_value },
297 ref t => return Err(location.new_unexpected_token_error(t.clone())),
298 })
299 }
300}
301
302#[cfg(feature="parser")]
303struct DefaultComponentParser;
304
305#[cfg(feature="parser")]
306impl<'i> ColorComponentParser<'i> for DefaultComponentParser {
307 type Error = ();
308}
309
310#[cfg(feature="parser")]
311#[inline]
312fn rgb(red: u8, green: u8, blue: u8) -> Color {
313 rgba(red, green, blue, 255)
314}
315
316#[cfg(feature="parser")]
317#[inline]
318fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
319 Color::new(red, green, blue, alpha)
320}
321
322#[cfg(feature="parser")]
328#[inline]
329pub fn parse_color_keyword(ident: &str) -> Result<Color, ()> {
330 macro_rules! rgb {
331 ($red: expr, $green: expr, $blue: expr) => {
332 Color {
333 red: $red,
334 green: $green,
335 blue: $blue,
336 alpha: 255,
337 }
338 };
339 }
340
341 ascii_case_insensitive_phf_map! {
342 keyword -> Color = {
343 "black" => rgb!(0, 0, 0),
344 "silver" => rgb!(192, 192, 192),
345 "gray" => rgb!(128, 128, 128),
346 "white" => rgb!(255, 255, 255),
347 "maroon" => rgb!(128, 0, 0),
348 "red" => rgb!(255, 0, 0),
349 "purple" => rgb!(128, 0, 128),
350 "fuchsia" => rgb!(255, 0, 255),
351 "green" => rgb!(0, 128, 0),
352 "lime" => rgb!(0, 255, 0),
353 "olive" => rgb!(128, 128, 0),
354 "yellow" => rgb!(255, 255, 0),
355 "navy" => rgb!(0, 0, 128),
356 "blue" => rgb!(0, 0, 255),
357 "teal" => rgb!(0, 128, 128),
358 "aqua" => rgb!(0, 255, 255),
359
360 "aliceblue" => rgb!(240, 248, 255),
361 "antiquewhite" => rgb!(250, 235, 215),
362 "aquamarine" => rgb!(127, 255, 212),
363 "azure" => rgb!(240, 255, 255),
364 "beige" => rgb!(245, 245, 220),
365 "bisque" => rgb!(255, 228, 196),
366 "blanchedalmond" => rgb!(255, 235, 205),
367 "blueviolet" => rgb!(138, 43, 226),
368 "brown" => rgb!(165, 42, 42),
369 "burlywood" => rgb!(222, 184, 135),
370 "cadetblue" => rgb!(95, 158, 160),
371 "chartreuse" => rgb!(127, 255, 0),
372 "chocolate" => rgb!(210, 105, 30),
373 "coral" => rgb!(255, 127, 80),
374 "cornflowerblue" => rgb!(100, 149, 237),
375 "cornsilk" => rgb!(255, 248, 220),
376 "crimson" => rgb!(220, 20, 60),
377 "cyan" => rgb!(0, 255, 255),
378 "darkblue" => rgb!(0, 0, 139),
379 "darkcyan" => rgb!(0, 139, 139),
380 "darkgoldenrod" => rgb!(184, 134, 11),
381 "darkgray" => rgb!(169, 169, 169),
382 "darkgreen" => rgb!(0, 100, 0),
383 "darkgrey" => rgb!(169, 169, 169),
384 "darkkhaki" => rgb!(189, 183, 107),
385 "darkmagenta" => rgb!(139, 0, 139),
386 "darkolivegreen" => rgb!(85, 107, 47),
387 "darkorange" => rgb!(255, 140, 0),
388 "darkorchid" => rgb!(153, 50, 204),
389 "darkred" => rgb!(139, 0, 0),
390 "darksalmon" => rgb!(233, 150, 122),
391 "darkseagreen" => rgb!(143, 188, 143),
392 "darkslateblue" => rgb!(72, 61, 139),
393 "darkslategray" => rgb!(47, 79, 79),
394 "darkslategrey" => rgb!(47, 79, 79),
395 "darkturquoise" => rgb!(0, 206, 209),
396 "darkviolet" => rgb!(148, 0, 211),
397 "deeppink" => rgb!(255, 20, 147),
398 "deepskyblue" => rgb!(0, 191, 255),
399 "dimgray" => rgb!(105, 105, 105),
400 "dimgrey" => rgb!(105, 105, 105),
401 "dodgerblue" => rgb!(30, 144, 255),
402 "firebrick" => rgb!(178, 34, 34),
403 "floralwhite" => rgb!(255, 250, 240),
404 "forestgreen" => rgb!(34, 139, 34),
405 "gainsboro" => rgb!(220, 220, 220),
406 "ghostwhite" => rgb!(248, 248, 255),
407 "gold" => rgb!(255, 215, 0),
408 "goldenrod" => rgb!(218, 165, 32),
409 "greenyellow" => rgb!(173, 255, 47),
410 "grey" => rgb!(128, 128, 128),
411 "honeydew" => rgb!(240, 255, 240),
412 "hotpink" => rgb!(255, 105, 180),
413 "indianred" => rgb!(205, 92, 92),
414 "indigo" => rgb!(75, 0, 130),
415 "ivory" => rgb!(255, 255, 240),
416 "khaki" => rgb!(240, 230, 140),
417 "lavender" => rgb!(230, 230, 250),
418 "lavenderblush" => rgb!(255, 240, 245),
419 "lawngreen" => rgb!(124, 252, 0),
420 "lemonchiffon" => rgb!(255, 250, 205),
421 "lightblue" => rgb!(173, 216, 230),
422 "lightcoral" => rgb!(240, 128, 128),
423 "lightcyan" => rgb!(224, 255, 255),
424 "lightgoldenrodyellow" => rgb!(250, 250, 210),
425 "lightgray" => rgb!(211, 211, 211),
426 "lightgreen" => rgb!(144, 238, 144),
427 "lightgrey" => rgb!(211, 211, 211),
428 "lightpink" => rgb!(255, 182, 193),
429 "lightsalmon" => rgb!(255, 160, 122),
430 "lightseagreen" => rgb!(32, 178, 170),
431 "lightskyblue" => rgb!(135, 206, 250),
432 "lightslategray" => rgb!(119, 136, 153),
433 "lightslategrey" => rgb!(119, 136, 153),
434 "lightsteelblue" => rgb!(176, 196, 222),
435 "lightyellow" => rgb!(255, 255, 224),
436 "limegreen" => rgb!(50, 205, 50),
437 "linen" => rgb!(250, 240, 230),
438 "magenta" => rgb!(255, 0, 255),
439 "mediumaquamarine" => rgb!(102, 205, 170),
440 "mediumblue" => rgb!(0, 0, 205),
441 "mediumorchid" => rgb!(186, 85, 211),
442 "mediumpurple" => rgb!(147, 112, 219),
443 "mediumseagreen" => rgb!(60, 179, 113),
444 "mediumslateblue" => rgb!(123, 104, 238),
445 "mediumspringgreen" => rgb!(0, 250, 154),
446 "mediumturquoise" => rgb!(72, 209, 204),
447 "mediumvioletred" => rgb!(199, 21, 133),
448 "midnightblue" => rgb!(25, 25, 112),
449 "mintcream" => rgb!(245, 255, 250),
450 "mistyrose" => rgb!(255, 228, 225),
451 "moccasin" => rgb!(255, 228, 181),
452 "navajowhite" => rgb!(255, 222, 173),
453 "oldlace" => rgb!(253, 245, 230),
454 "olivedrab" => rgb!(107, 142, 35),
455 "orange" => rgb!(255, 165, 0),
456 "orangered" => rgb!(255, 69, 0),
457 "orchid" => rgb!(218, 112, 214),
458 "palegoldenrod" => rgb!(238, 232, 170),
459 "palegreen" => rgb!(152, 251, 152),
460 "paleturquoise" => rgb!(175, 238, 238),
461 "palevioletred" => rgb!(219, 112, 147),
462 "papayawhip" => rgb!(255, 239, 213),
463 "peachpuff" => rgb!(255, 218, 185),
464 "peru" => rgb!(205, 133, 63),
465 "pink" => rgb!(255, 192, 203),
466 "plum" => rgb!(221, 160, 221),
467 "powderblue" => rgb!(176, 224, 230),
468 "rebeccapurple" => rgb!(102, 51, 153),
469 "rosybrown" => rgb!(188, 143, 143),
470 "royalblue" => rgb!(65, 105, 225),
471 "saddlebrown" => rgb!(139, 69, 19),
472 "salmon" => rgb!(250, 128, 114),
473 "sandybrown" => rgb!(244, 164, 96),
474 "seagreen" => rgb!(46, 139, 87),
475 "seashell" => rgb!(255, 245, 238),
476 "sienna" => rgb!(160, 82, 45),
477 "skyblue" => rgb!(135, 206, 235),
478 "slateblue" => rgb!(106, 90, 205),
479 "slategray" => rgb!(112, 128, 144),
480 "slategrey" => rgb!(112, 128, 144),
481 "snow" => rgb!(255, 250, 250),
482 "springgreen" => rgb!(0, 255, 127),
483 "steelblue" => rgb!(70, 130, 180),
484 "tan" => rgb!(210, 180, 140),
485 "thistle" => rgb!(216, 191, 216),
486 "tomato" => rgb!(255, 99, 71),
487 "turquoise" => rgb!(64, 224, 208),
488 "violet" => rgb!(238, 130, 238),
489 "wheat" => rgb!(245, 222, 179),
490 "whitesmoke" => rgb!(245, 245, 245),
491 "yellowgreen" => rgb!(154, 205, 50),
492
493 "transparent" => Color { red: 0, green: 0, blue: 0, alpha: 0 }
494 }
495 }
496
497 keyword(ident).cloned().ok_or(())
498}
499
500#[cfg(feature="parser")]
501#[inline]
502fn from_hex(c: u8) -> Result<u8, ()> {
503 match c {
504 b'0'...b'9' => Ok(c - b'0'),
505 b'a'...b'f' => Ok(c - b'a' + 10),
506 b'A'...b'F' => Ok(c - b'A' + 10),
507 _ => Err(()),
508 }
509}
510
511fn clamp_unit_f32(val: f32) -> u8 {
512 clamp_floor_256_f32(val * 255.)
527}
528
529fn clamp_floor_256_f32(val: f32) -> u8 {
530 val.round().max(0.).min(255.) as u8
531}
532
533#[cfg(feature="parser")]
534#[inline]
535fn parse_color_function<'i, 't, ComponentParser>(
536 component_parser: &ComponentParser,
537 name: &str,
538 arguments: &mut Parser<'i, 't>,
539) -> Result<Color, ParseError<'i, ComponentParser::Error>>
540where
541 ComponentParser: ColorComponentParser<'i>,
542{
543 let (red, green, blue, uses_commas) = match_ignore_ascii_case! { name,
544 "rgb" | "rgba" => parse_rgb_components_rgb(component_parser, arguments)?,
545 "hsl" | "hsla" => parse_rgb_components_hsl(component_parser, arguments)?,
546 _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name.to_owned().into()))),
547 };
548
549 let alpha = if !arguments.is_exhausted() {
550 if uses_commas {
551 arguments.expect_comma()?;
552 } else {
553 arguments.expect_delim('/')?;
554 };
555 clamp_unit_f32(
556 component_parser
557 .parse_number_or_percentage(arguments)?
558 .unit_value(),
559 )
560 } else {
561 255
562 };
563
564 arguments.expect_exhausted()?;
565 Ok(rgba(red, green, blue, alpha))
566}
567
568#[cfg(feature="parser")]
569#[inline]
570fn parse_rgb_components_rgb<'i, 't, ComponentParser>(
571 component_parser: &ComponentParser,
572 arguments: &mut Parser<'i, 't>,
573) -> Result<(u8, u8, u8, bool), ParseError<'i, ComponentParser::Error>>
574where
575 ComponentParser: ColorComponentParser<'i>,
576{
577 let (red, is_number) = match component_parser.parse_number_or_percentage(arguments)? {
580 NumberOrPercentage::Number { value } => (clamp_floor_256_f32(value), true),
581 NumberOrPercentage::Percentage { unit_value } => (clamp_unit_f32(unit_value), false),
582 };
583
584 let uses_commas = arguments.try_parse(|i| i.expect_comma()).is_ok();
585
586 let green;
587 let blue;
588 if is_number {
589 green = clamp_floor_256_f32(component_parser.parse_number(arguments)?);
590 if uses_commas {
591 arguments.expect_comma()?;
592 }
593 blue = clamp_floor_256_f32(component_parser.parse_number(arguments)?);
594 } else {
595 green = clamp_unit_f32(component_parser.parse_percentage(arguments)?);
596 if uses_commas {
597 arguments.expect_comma()?;
598 }
599 blue = clamp_unit_f32(component_parser.parse_percentage(arguments)?);
600 }
601
602 Ok((red, green, blue, uses_commas))
603}
604
605#[cfg(feature="parser")]
606#[inline]
607fn parse_rgb_components_hsl<'i, 't, ComponentParser>(
608 component_parser: &ComponentParser,
609 arguments: &mut Parser<'i, 't>,
610) -> Result<(u8, u8, u8, bool), ParseError<'i, ComponentParser::Error>>
611where
612 ComponentParser: ColorComponentParser<'i>,
613{
614 let hue_degrees = component_parser.parse_angle_or_number(arguments)?.degrees();
617
618 let hue_normalized_degrees = hue_degrees - 360. * (hue_degrees / 360.).floor();
620 let hue = hue_normalized_degrees / 360.;
621
622 let uses_commas = arguments.try_parse(|i| i.expect_comma()).is_ok();
625
626 let saturation = component_parser.parse_percentage(arguments)?;
627 let saturation = saturation.max(0.).min(1.);
628
629 if uses_commas {
630 arguments.expect_comma()?;
631 }
632
633 let lightness = component_parser.parse_percentage(arguments)?;
634 let lightness = lightness.max(0.).min(1.);
635
636 fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
639 if h3 < 0. {
640 h3 += 3.
641 }
642 if h3 > 3. {
643 h3 -= 3.
644 }
645
646 if h3 * 2. < 1. {
647 m1 + (m2 - m1) * h3 * 2.
648 } else if h3 * 2. < 3. {
649 m2
650 } else if h3 < 2. {
651 m1 + (m2 - m1) * (2. - h3) * 2.
652 } else {
653 m1
654 }
655 }
656 let m2 = if lightness <= 0.5 {
657 lightness * (saturation + 1.)
658 } else {
659 lightness + saturation - lightness * saturation
660 };
661 let m1 = lightness * 2. - m2;
662 let hue_times_3 = hue * 3.;
663 let red = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3 + 1.));
664 let green = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3));
665 let blue = clamp_unit_f32(hue_to_rgb(m1, m2, hue_times_3 - 1.));
666 return Ok((red, green, blue, uses_commas));
667}