1use alloc::string::{String, ToString};
4use core::{
5 fmt,
6 num::{ParseFloatError, ParseIntError},
7};
8
9use crate::{
10 impl_option,
11 props::basic::{
12 direction::{
13 parse_direction, CssDirectionParseError, CssDirectionParseErrorOwned, Direction,
14 },
15 length::{PercentageParseError, PercentageValue},
16 },
17};
18
19#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
21#[repr(C)]
22pub struct ColorU {
23 pub r: u8,
24 pub g: u8,
25 pub b: u8,
26 pub a: u8,
27}
28
29impl_option!(
30 ColorU,
31 OptionColorU,
32 [Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash]
33);
34
35impl Default for ColorU {
36 fn default() -> Self {
37 ColorU::BLACK
38 }
39}
40
41impl fmt::Display for ColorU {
42 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 write!(
44 f,
45 "rgba({}, {}, {}, {})",
46 self.r,
47 self.g,
48 self.b,
49 self.a as f32 / 255.0
50 )
51 }
52}
53
54impl ColorU {
55 pub const ALPHA_TRANSPARENT: u8 = 0;
56 pub const ALPHA_OPAQUE: u8 = 255;
57 pub const RED: ColorU = ColorU {
58 r: 255,
59 g: 0,
60 b: 0,
61 a: Self::ALPHA_OPAQUE,
62 };
63 pub const GREEN: ColorU = ColorU {
64 r: 0,
65 g: 255,
66 b: 0,
67 a: Self::ALPHA_OPAQUE,
68 };
69 pub const BLUE: ColorU = ColorU {
70 r: 0,
71 g: 0,
72 b: 255,
73 a: Self::ALPHA_OPAQUE,
74 };
75 pub const WHITE: ColorU = ColorU {
76 r: 255,
77 g: 255,
78 b: 255,
79 a: Self::ALPHA_OPAQUE,
80 };
81 pub const BLACK: ColorU = ColorU {
82 r: 0,
83 g: 0,
84 b: 0,
85 a: Self::ALPHA_OPAQUE,
86 };
87 pub const TRANSPARENT: ColorU = ColorU {
88 r: 0,
89 g: 0,
90 b: 0,
91 a: Self::ALPHA_TRANSPARENT,
92 };
93
94 pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
95 Self { r, g, b, a }
96 }
97 pub const fn new_rgb(r: u8, g: u8, b: u8) -> Self {
98 Self { r, g, b, a: 255 }
99 }
100
101 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
102 Self {
103 r: libm::roundf(self.r as f32 + (other.r as f32 - self.r as f32) * t) as u8,
104 g: libm::roundf(self.g as f32 + (other.g as f32 - self.g as f32) * t) as u8,
105 b: libm::roundf(self.b as f32 + (other.b as f32 - self.b as f32) * t) as u8,
106 a: libm::roundf(self.a as f32 + (other.a as f32 - self.a as f32) * t) as u8,
107 }
108 }
109
110 pub const fn has_alpha(&self) -> bool {
111 self.a != Self::ALPHA_OPAQUE
112 }
113
114 pub fn to_hash(&self) -> String {
115 format!("#{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a)
116 }
117}
118
119#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
121pub struct ColorF {
122 pub r: f32,
123 pub g: f32,
124 pub b: f32,
125 pub a: f32,
126}
127
128impl Default for ColorF {
129 fn default() -> Self {
130 ColorF::BLACK
131 }
132}
133
134impl fmt::Display for ColorF {
135 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136 write!(
137 f,
138 "rgba({}, {}, {}, {})",
139 self.r * 255.0,
140 self.g * 255.0,
141 self.b * 255.0,
142 self.a
143 )
144 }
145}
146
147impl ColorF {
148 pub const ALPHA_TRANSPARENT: f32 = 0.0;
149 pub const ALPHA_OPAQUE: f32 = 1.0;
150 pub const WHITE: ColorF = ColorF {
151 r: 1.0,
152 g: 1.0,
153 b: 1.0,
154 a: Self::ALPHA_OPAQUE,
155 };
156 pub const BLACK: ColorF = ColorF {
157 r: 0.0,
158 g: 0.0,
159 b: 0.0,
160 a: Self::ALPHA_OPAQUE,
161 };
162 pub const TRANSPARENT: ColorF = ColorF {
163 r: 0.0,
164 g: 0.0,
165 b: 0.0,
166 a: Self::ALPHA_TRANSPARENT,
167 };
168}
169
170impl From<ColorU> for ColorF {
171 fn from(input: ColorU) -> ColorF {
172 ColorF {
173 r: (input.r as f32) / 255.0,
174 g: (input.g as f32) / 255.0,
175 b: (input.b as f32) / 255.0,
176 a: (input.a as f32) / 255.0,
177 }
178 }
179}
180
181impl From<ColorF> for ColorU {
182 fn from(input: ColorF) -> ColorU {
183 ColorU {
184 r: (input.r.min(1.0) * 255.0) as u8,
185 g: (input.g.min(1.0) * 255.0) as u8,
186 b: (input.b.min(1.0) * 255.0) as u8,
187 a: (input.a.min(1.0) * 255.0) as u8,
188 }
189 }
190}
191
192#[derive(Debug, Copy, Clone, PartialEq)]
195pub enum CssColorComponent {
196 Red,
197 Green,
198 Blue,
199 Hue,
200 Saturation,
201 Lightness,
202 Alpha,
203}
204
205#[derive(Clone, PartialEq)]
206pub enum CssColorParseError<'a> {
207 InvalidColor(&'a str),
208 InvalidFunctionName(&'a str),
209 InvalidColorComponent(u8),
210 IntValueParseErr(ParseIntError),
211 FloatValueParseErr(ParseFloatError),
212 FloatValueOutOfRange(f32),
213 MissingColorComponent(CssColorComponent),
214 ExtraArguments(&'a str),
215 UnclosedColor(&'a str),
216 EmptyInput,
217 DirectionParseError(CssDirectionParseError<'a>),
218 UnsupportedDirection(&'a str),
219 InvalidPercentage(PercentageParseError),
220}
221
222impl_debug_as_display!(CssColorParseError<'a>);
223impl_display! {CssColorParseError<'a>, {
224 InvalidColor(i) => format!("Invalid CSS color: \"{}\"", i),
225 InvalidFunctionName(i) => format!("Invalid function name, expected one of: \"rgb\", \"rgba\", \"hsl\", \"hsla\" got: \"{}\"", i),
226 InvalidColorComponent(i) => format!("Invalid color component when parsing CSS color: \"{}\"", i),
227 IntValueParseErr(e) => format!("CSS color component: Value not in range between 00 - FF: \"{}\"", e),
228 FloatValueParseErr(e) => format!("CSS color component: Value cannot be parsed as floating point number: \"{}\"", e),
229 FloatValueOutOfRange(v) => format!("CSS color component: Value not in range between 0.0 - 1.0: \"{}\"", v),
230 MissingColorComponent(c) => format!("CSS color is missing {:?} component", c),
231 ExtraArguments(a) => format!("Extra argument to CSS color: \"{}\"", a),
232 EmptyInput => format!("Empty color string."),
233 UnclosedColor(i) => format!("Unclosed color: \"{}\"", i),
234 DirectionParseError(e) => format!("Could not parse direction argument for CSS color: \"{}\"", e),
235 UnsupportedDirection(d) => format!("Unsupported direction type for CSS color: \"{}\"", d),
236 InvalidPercentage(p) => format!("Invalid percentage when parsing CSS color: \"{}\"", p),
237}}
238
239impl<'a> From<ParseIntError> for CssColorParseError<'a> {
240 fn from(e: ParseIntError) -> Self {
241 CssColorParseError::IntValueParseErr(e)
242 }
243}
244impl<'a> From<ParseFloatError> for CssColorParseError<'a> {
245 fn from(e: ParseFloatError) -> Self {
246 CssColorParseError::FloatValueParseErr(e)
247 }
248}
249impl_from!(
250 CssDirectionParseError<'a>,
251 CssColorParseError::DirectionParseError
252);
253
254#[derive(Debug, Clone, PartialEq)]
255pub enum CssColorParseErrorOwned {
256 InvalidColor(String),
257 InvalidFunctionName(String),
258 InvalidColorComponent(u8),
259 IntValueParseErr(ParseIntError),
260 FloatValueParseErr(ParseFloatError),
261 FloatValueOutOfRange(f32),
262 MissingColorComponent(CssColorComponent),
263 ExtraArguments(String),
264 UnclosedColor(String),
265 EmptyInput,
266 DirectionParseError(CssDirectionParseErrorOwned),
267 UnsupportedDirection(String),
268 InvalidPercentage(PercentageParseError),
269}
270
271impl<'a> CssColorParseError<'a> {
272 pub fn to_contained(&self) -> CssColorParseErrorOwned {
273 match self {
274 CssColorParseError::InvalidColor(s) => {
275 CssColorParseErrorOwned::InvalidColor(s.to_string())
276 }
277 CssColorParseError::InvalidFunctionName(s) => {
278 CssColorParseErrorOwned::InvalidFunctionName(s.to_string())
279 }
280 CssColorParseError::InvalidColorComponent(n) => {
281 CssColorParseErrorOwned::InvalidColorComponent(*n)
282 }
283 CssColorParseError::IntValueParseErr(e) => {
284 CssColorParseErrorOwned::IntValueParseErr(e.clone())
285 }
286 CssColorParseError::FloatValueParseErr(e) => {
287 CssColorParseErrorOwned::FloatValueParseErr(e.clone())
288 }
289 CssColorParseError::FloatValueOutOfRange(n) => {
290 CssColorParseErrorOwned::FloatValueOutOfRange(*n)
291 }
292 CssColorParseError::MissingColorComponent(c) => {
293 CssColorParseErrorOwned::MissingColorComponent(*c)
294 }
295 CssColorParseError::ExtraArguments(s) => {
296 CssColorParseErrorOwned::ExtraArguments(s.to_string())
297 }
298 CssColorParseError::UnclosedColor(s) => {
299 CssColorParseErrorOwned::UnclosedColor(s.to_string())
300 }
301 CssColorParseError::EmptyInput => CssColorParseErrorOwned::EmptyInput,
302 CssColorParseError::DirectionParseError(e) => {
303 CssColorParseErrorOwned::DirectionParseError(e.to_contained())
304 }
305 CssColorParseError::UnsupportedDirection(s) => {
306 CssColorParseErrorOwned::UnsupportedDirection(s.to_string())
307 }
308 CssColorParseError::InvalidPercentage(e) => {
309 CssColorParseErrorOwned::InvalidPercentage(e.clone())
310 }
311 }
312 }
313}
314
315impl CssColorParseErrorOwned {
316 pub fn to_shared<'a>(&'a self) -> CssColorParseError<'a> {
317 match self {
318 CssColorParseErrorOwned::InvalidColor(s) => CssColorParseError::InvalidColor(s),
319 CssColorParseErrorOwned::InvalidFunctionName(s) => {
320 CssColorParseError::InvalidFunctionName(s)
321 }
322 CssColorParseErrorOwned::InvalidColorComponent(n) => {
323 CssColorParseError::InvalidColorComponent(*n)
324 }
325 CssColorParseErrorOwned::IntValueParseErr(e) => {
326 CssColorParseError::IntValueParseErr(e.clone())
327 }
328 CssColorParseErrorOwned::FloatValueParseErr(e) => {
329 CssColorParseError::FloatValueParseErr(e.clone())
330 }
331 CssColorParseErrorOwned::FloatValueOutOfRange(n) => {
332 CssColorParseError::FloatValueOutOfRange(*n)
333 }
334 CssColorParseErrorOwned::MissingColorComponent(c) => {
335 CssColorParseError::MissingColorComponent(*c)
336 }
337 CssColorParseErrorOwned::ExtraArguments(s) => CssColorParseError::ExtraArguments(s),
338 CssColorParseErrorOwned::UnclosedColor(s) => CssColorParseError::UnclosedColor(s),
339 CssColorParseErrorOwned::EmptyInput => CssColorParseError::EmptyInput,
340 CssColorParseErrorOwned::DirectionParseError(e) => {
341 CssColorParseError::DirectionParseError(e.to_shared())
342 }
343 CssColorParseErrorOwned::UnsupportedDirection(s) => {
344 CssColorParseError::UnsupportedDirection(s)
345 }
346 CssColorParseErrorOwned::InvalidPercentage(e) => {
347 CssColorParseError::InvalidPercentage(e.clone())
348 }
349 }
350 }
351}
352
353#[cfg(feature = "parser")]
354pub fn parse_css_color<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
355 let input = input.trim();
356 if input.starts_with('#') {
357 parse_color_no_hash(&input[1..])
358 } else {
359 use crate::props::basic::parse::{parse_parentheses, ParenthesisParseError};
360 match parse_parentheses(input, &["rgba", "rgb", "hsla", "hsl"]) {
361 Ok((stopword, inner_value)) => match stopword {
362 "rgba" => parse_color_rgb(inner_value, true),
363 "rgb" => parse_color_rgb(inner_value, false),
364 "hsla" => parse_color_hsl(inner_value, true),
365 "hsl" => parse_color_hsl(inner_value, false),
366 _ => unreachable!(),
367 },
368 Err(e) => match e {
369 ParenthesisParseError::UnclosedBraces => {
370 Err(CssColorParseError::UnclosedColor(input))
371 }
372 ParenthesisParseError::EmptyInput => Err(CssColorParseError::EmptyInput),
373 ParenthesisParseError::StopWordNotFound(stopword) => {
374 Err(CssColorParseError::InvalidFunctionName(stopword))
375 }
376 ParenthesisParseError::NoClosingBraceFound => {
377 Err(CssColorParseError::UnclosedColor(input))
378 }
379 ParenthesisParseError::NoOpeningBraceFound => parse_color_builtin(input),
380 },
381 }
382 }
383}
384
385#[cfg(feature = "parser")]
386fn parse_color_no_hash<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
387 #[inline]
388 fn from_hex<'a>(c: u8) -> Result<u8, CssColorParseError<'a>> {
389 match c {
390 b'0'..=b'9' => Ok(c - b'0'),
391 b'a'..=b'f' => Ok(c - b'a' + 10),
392 b'A'..=b'F' => Ok(c - b'A' + 10),
393 _ => Err(CssColorParseError::InvalidColorComponent(c)),
394 }
395 }
396
397 match input.len() {
398 3 => {
399 let mut bytes = input.bytes();
400 let r = bytes.next().unwrap();
401 let g = bytes.next().unwrap();
402 let b = bytes.next().unwrap();
403 Ok(ColorU::new_rgb(
404 from_hex(r)? * 17,
405 from_hex(g)? * 17,
406 from_hex(b)? * 17,
407 ))
408 }
409 4 => {
410 let mut bytes = input.bytes();
411 let r = bytes.next().unwrap();
412 let g = bytes.next().unwrap();
413 let b = bytes.next().unwrap();
414 let a = bytes.next().unwrap();
415 Ok(ColorU::new(
416 from_hex(r)? * 17,
417 from_hex(g)? * 17,
418 from_hex(b)? * 17,
419 from_hex(a)? * 17,
420 ))
421 }
422 6 => {
423 let val = u32::from_str_radix(input, 16)?;
424 Ok(ColorU::new_rgb(
425 ((val >> 16) & 0xFF) as u8,
426 ((val >> 8) & 0xFF) as u8,
427 (val & 0xFF) as u8,
428 ))
429 }
430 8 => {
431 let val = u32::from_str_radix(input, 16)?;
432 Ok(ColorU::new(
433 ((val >> 24) & 0xFF) as u8,
434 ((val >> 16) & 0xFF) as u8,
435 ((val >> 8) & 0xFF) as u8,
436 (val & 0xFF) as u8,
437 ))
438 }
439 _ => Err(CssColorParseError::InvalidColor(input)),
440 }
441}
442
443#[cfg(feature = "parser")]
444fn parse_color_rgb<'a>(
445 input: &'a str,
446 parse_alpha: bool,
447) -> Result<ColorU, CssColorParseError<'a>> {
448 let mut components = input.split(',').map(|c| c.trim());
449 let rgb_color = parse_color_rgb_components(&mut components)?;
450 let a = if parse_alpha {
451 parse_alpha_component(&mut components)?
452 } else {
453 255
454 };
455 if let Some(arg) = components.next() {
456 return Err(CssColorParseError::ExtraArguments(arg));
457 }
458 Ok(ColorU { a, ..rgb_color })
459}
460
461#[cfg(feature = "parser")]
462fn parse_color_rgb_components<'a>(
463 components: &mut dyn Iterator<Item = &'a str>,
464) -> Result<ColorU, CssColorParseError<'a>> {
465 #[inline]
466 fn component_from_str<'a>(
467 components: &mut dyn Iterator<Item = &'a str>,
468 which: CssColorComponent,
469 ) -> Result<u8, CssColorParseError<'a>> {
470 let c = components
471 .next()
472 .ok_or(CssColorParseError::MissingColorComponent(which))?;
473 if c.is_empty() {
474 return Err(CssColorParseError::MissingColorComponent(which));
475 }
476 Ok(c.parse::<u8>()?)
477 }
478 Ok(ColorU {
479 r: component_from_str(components, CssColorComponent::Red)?,
480 g: component_from_str(components, CssColorComponent::Green)?,
481 b: component_from_str(components, CssColorComponent::Blue)?,
482 a: 255,
483 })
484}
485
486#[cfg(feature = "parser")]
487fn parse_color_hsl<'a>(
488 input: &'a str,
489 parse_alpha: bool,
490) -> Result<ColorU, CssColorParseError<'a>> {
491 let mut components = input.split(',').map(|c| c.trim());
492 let rgb_color = parse_color_hsl_components(&mut components)?;
493 let a = if parse_alpha {
494 parse_alpha_component(&mut components)?
495 } else {
496 255
497 };
498 if let Some(arg) = components.next() {
499 return Err(CssColorParseError::ExtraArguments(arg));
500 }
501 Ok(ColorU { a, ..rgb_color })
502}
503
504#[cfg(feature = "parser")]
505fn parse_color_hsl_components<'a>(
506 components: &mut dyn Iterator<Item = &'a str>,
507) -> Result<ColorU, CssColorParseError<'a>> {
508 #[inline]
509 fn angle_from_str<'a>(
510 components: &mut dyn Iterator<Item = &'a str>,
511 which: CssColorComponent,
512 ) -> Result<f32, CssColorParseError<'a>> {
513 let c = components
514 .next()
515 .ok_or(CssColorParseError::MissingColorComponent(which))?;
516 if c.is_empty() {
517 return Err(CssColorParseError::MissingColorComponent(which));
518 }
519 let dir = parse_direction(c)?;
520 match dir {
521 Direction::Angle(deg) => Ok(deg.to_degrees()),
522 Direction::FromTo(_) => Err(CssColorParseError::UnsupportedDirection(c)),
523 }
524 }
525
526 #[inline]
527 fn percent_from_str<'a>(
528 components: &mut dyn Iterator<Item = &'a str>,
529 which: CssColorComponent,
530 ) -> Result<f32, CssColorParseError<'a>> {
531 use crate::props::basic::parse_percentage_value;
532
533 let c = components
534 .next()
535 .ok_or(CssColorParseError::MissingColorComponent(which))?;
536 if c.is_empty() {
537 return Err(CssColorParseError::MissingColorComponent(which));
538 }
539
540 Ok(parse_percentage_value(c)
542 .map_err(CssColorParseError::InvalidPercentage)?
543 .normalized()
544 * 100.0)
545 }
546
547 #[inline]
548 fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
549 let s = s / 100.0;
550 let l = l / 100.0;
551 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
552 let h_prime = h / 60.0;
553 let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
554 let (r1, g1, b1) = if h_prime >= 0.0 && h_prime < 1.0 {
555 (c, x, 0.0)
556 } else if h_prime >= 1.0 && h_prime < 2.0 {
557 (x, c, 0.0)
558 } else if h_prime >= 2.0 && h_prime < 3.0 {
559 (0.0, c, x)
560 } else if h_prime >= 3.0 && h_prime < 4.0 {
561 (0.0, x, c)
562 } else if h_prime >= 4.0 && h_prime < 5.0 {
563 (x, 0.0, c)
564 } else {
565 (c, 0.0, x)
566 };
567 let m = l - c / 2.0;
568 (
569 ((r1 + m) * 255.0) as u8,
570 ((g1 + m) * 255.0) as u8,
571 ((b1 + m) * 255.0) as u8,
572 )
573 }
574
575 let (h, s, l) = (
576 angle_from_str(components, CssColorComponent::Hue)?,
577 percent_from_str(components, CssColorComponent::Saturation)?,
578 percent_from_str(components, CssColorComponent::Lightness)?,
579 );
580
581 let (r, g, b) = hsl_to_rgb(h, s, l);
582 Ok(ColorU { r, g, b, a: 255 })
583}
584
585#[cfg(feature = "parser")]
586fn parse_alpha_component<'a>(
587 components: &mut dyn Iterator<Item = &'a str>,
588) -> Result<u8, CssColorParseError<'a>> {
589 let a_str = components
590 .next()
591 .ok_or(CssColorParseError::MissingColorComponent(
592 CssColorComponent::Alpha,
593 ))?;
594 if a_str.is_empty() {
595 return Err(CssColorParseError::MissingColorComponent(
596 CssColorComponent::Alpha,
597 ));
598 }
599 let a = a_str.parse::<f32>()?;
600 if a < 0.0 || a > 1.0 {
601 return Err(CssColorParseError::FloatValueOutOfRange(a));
602 }
603 Ok((a * 255.0).round() as u8)
604}
605
606#[cfg(feature = "parser")]
607fn parse_color_builtin<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
608 let (r, g, b, a) = match input.to_lowercase().as_str() {
609 "aliceblue" => (240, 248, 255, 255),
610 "antiquewhite" => (250, 235, 215, 255),
611 "aqua" => (0, 255, 255, 255),
612 "aquamarine" => (127, 255, 212, 255),
613 "azure" => (240, 255, 255, 255),
614 "beige" => (245, 245, 220, 255),
615 "bisque" => (255, 228, 196, 255),
616 "black" => (0, 0, 0, 255),
617 "blanchedalmond" => (255, 235, 205, 255),
618 "blue" => (0, 0, 255, 255),
619 "blueviolet" => (138, 43, 226, 255),
620 "brown" => (165, 42, 42, 255),
621 "burlywood" => (222, 184, 135, 255),
622 "cadetblue" => (95, 158, 160, 255),
623 "chartreuse" => (127, 255, 0, 255),
624 "chocolate" => (210, 105, 30, 255),
625 "coral" => (255, 127, 80, 255),
626 "cornflowerblue" => (100, 149, 237, 255),
627 "cornsilk" => (255, 248, 220, 255),
628 "crimson" => (220, 20, 60, 255),
629 "cyan" => (0, 255, 255, 255),
630 "darkblue" => (0, 0, 139, 255),
631 "darkcyan" => (0, 139, 139, 255),
632 "darkgoldenrod" => (184, 134, 11, 255),
633 "darkgray" | "darkgrey" => (169, 169, 169, 255),
634 "darkgreen" => (0, 100, 0, 255),
635 "darkkhaki" => (189, 183, 107, 255),
636 "darkmagenta" => (139, 0, 139, 255),
637 "darkolivegreen" => (85, 107, 47, 255),
638 "darkorange" => (255, 140, 0, 255),
639 "darkorchid" => (153, 50, 204, 255),
640 "darkred" => (139, 0, 0, 255),
641 "darksalmon" => (233, 150, 122, 255),
642 "darkseagreen" => (143, 188, 143, 255),
643 "darkslateblue" => (72, 61, 139, 255),
644 "darkslategray" | "darkslategrey" => (47, 79, 79, 255),
645 "darkturquoise" => (0, 206, 209, 255),
646 "darkviolet" => (148, 0, 211, 255),
647 "deeppink" => (255, 20, 147, 255),
648 "deepskyblue" => (0, 191, 255, 255),
649 "dimgray" | "dimgrey" => (105, 105, 105, 255),
650 "dodgerblue" => (30, 144, 255, 255),
651 "firebrick" => (178, 34, 34, 255),
652 "floralwhite" => (255, 250, 240, 255),
653 "forestgreen" => (34, 139, 34, 255),
654 "fuchsia" => (255, 0, 255, 255),
655 "gainsboro" => (220, 220, 220, 255),
656 "ghostwhite" => (248, 248, 255, 255),
657 "gold" => (255, 215, 0, 255),
658 "goldenrod" => (218, 165, 32, 255),
659 "gray" | "grey" => (128, 128, 128, 255),
660 "green" => (0, 128, 0, 255),
661 "greenyellow" => (173, 255, 47, 255),
662 "honeydew" => (240, 255, 240, 255),
663 "hotpink" => (255, 105, 180, 255),
664 "indianred" => (205, 92, 92, 255),
665 "indigo" => (75, 0, 130, 255),
666 "ivory" => (255, 255, 240, 255),
667 "khaki" => (240, 230, 140, 255),
668 "lavender" => (230, 230, 250, 255),
669 "lavenderblush" => (255, 240, 245, 255),
670 "lawngreen" => (124, 252, 0, 255),
671 "lemonchiffon" => (255, 250, 205, 255),
672 "lightblue" => (173, 216, 230, 255),
673 "lightcoral" => (240, 128, 128, 255),
674 "lightcyan" => (224, 255, 255, 255),
675 "lightgoldenrodyellow" => (250, 250, 210, 255),
676 "lightgray" | "lightgrey" => (211, 211, 211, 255),
677 "lightgreen" => (144, 238, 144, 255),
678 "lightpink" => (255, 182, 193, 255),
679 "lightsalmon" => (255, 160, 122, 255),
680 "lightseagreen" => (32, 178, 170, 255),
681 "lightskyblue" => (135, 206, 250, 255),
682 "lightslategray" | "lightslategrey" => (119, 136, 153, 255),
683 "lightsteelblue" => (176, 196, 222, 255),
684 "lightyellow" => (255, 255, 224, 255),
685 "lime" => (0, 255, 0, 255),
686 "limegreen" => (50, 205, 50, 255),
687 "linen" => (250, 240, 230, 255),
688 "magenta" => (255, 0, 255, 255),
689 "maroon" => (128, 0, 0, 255),
690 "mediumaquamarine" => (102, 205, 170, 255),
691 "mediumblue" => (0, 0, 205, 255),
692 "mediumorchid" => (186, 85, 211, 255),
693 "mediumpurple" => (147, 112, 219, 255),
694 "mediumseagreen" => (60, 179, 113, 255),
695 "mediumslateblue" => (123, 104, 238, 255),
696 "mediumspringgreen" => (0, 250, 154, 255),
697 "mediumturquoise" => (72, 209, 204, 255),
698 "mediumvioletred" => (199, 21, 133, 255),
699 "midnightblue" => (25, 25, 112, 255),
700 "mintcream" => (245, 255, 250, 255),
701 "mistyrose" => (255, 228, 225, 255),
702 "moccasin" => (255, 228, 181, 255),
703 "navajowhite" => (255, 222, 173, 255),
704 "navy" => (0, 0, 128, 255),
705 "oldlace" => (253, 245, 230, 255),
706 "olive" => (128, 128, 0, 255),
707 "olivedrab" => (107, 142, 35, 255),
708 "orange" => (255, 165, 0, 255),
709 "orangered" => (255, 69, 0, 255),
710 "orchid" => (218, 112, 214, 255),
711 "palegoldenrod" => (238, 232, 170, 255),
712 "palegreen" => (152, 251, 152, 255),
713 "paleturquoise" => (175, 238, 238, 255),
714 "palevioletred" => (219, 112, 147, 255),
715 "papayawhip" => (255, 239, 213, 255),
716 "peachpuff" => (255, 218, 185, 255),
717 "peru" => (205, 133, 63, 255),
718 "pink" => (255, 192, 203, 255),
719 "plum" => (221, 160, 221, 255),
720 "powderblue" => (176, 224, 230, 255),
721 "purple" => (128, 0, 128, 255),
722 "rebeccapurple" => (102, 51, 153, 255),
723 "red" => (255, 0, 0, 255),
724 "rosybrown" => (188, 143, 143, 255),
725 "royalblue" => (65, 105, 225, 255),
726 "saddlebrown" => (139, 69, 19, 255),
727 "salmon" => (250, 128, 114, 255),
728 "sandybrown" => (244, 164, 96, 255),
729 "seagreen" => (46, 139, 87, 255),
730 "seashell" => (255, 245, 238, 255),
731 "sienna" => (160, 82, 45, 255),
732 "silver" => (192, 192, 192, 255),
733 "skyblue" => (135, 206, 235, 255),
734 "slateblue" => (106, 90, 205, 255),
735 "slategray" | "slategrey" => (112, 128, 144, 255),
736 "snow" => (255, 250, 250, 255),
737 "springgreen" => (0, 255, 127, 255),
738 "steelblue" => (70, 130, 180, 255),
739 "tan" => (210, 180, 140, 255),
740 "teal" => (0, 128, 128, 255),
741 "thistle" => (216, 191, 216, 255),
742 "tomato" => (255, 99, 71, 255),
743 "transparent" => (0, 0, 0, 0),
744 "turquoise" => (64, 224, 208, 255),
745 "violet" => (238, 130, 238, 255),
746 "wheat" => (245, 222, 179, 255),
747 "white" => (255, 255, 255, 255),
748 "whitesmoke" => (245, 245, 245, 255),
749 "yellow" => (255, 255, 0, 255),
750 "yellowgreen" => (154, 205, 50, 255),
751 _ => return Err(CssColorParseError::InvalidColor(input)),
752 };
753 Ok(ColorU { r, g, b, a })
754}
755
756#[cfg(all(test, feature = "parser"))]
757mod tests {
758 use super::*;
759
760 #[test]
761 fn test_parse_color_keywords() {
762 assert_eq!(parse_css_color("red").unwrap(), ColorU::RED);
763 assert_eq!(parse_css_color("blue").unwrap(), ColorU::BLUE);
764 assert_eq!(parse_css_color("transparent").unwrap(), ColorU::TRANSPARENT);
765 assert_eq!(
766 parse_css_color("rebeccapurple").unwrap(),
767 ColorU::new_rgb(102, 51, 153)
768 );
769 }
770
771 #[test]
772 fn test_parse_color_hex() {
773 assert_eq!(parse_css_color("#f00").unwrap(), ColorU::RED);
775 assert_eq!(
777 parse_css_color("#f008").unwrap(),
778 ColorU::new(255, 0, 0, 136)
779 );
780 assert_eq!(parse_css_color("#00ff00").unwrap(), ColorU::GREEN);
782 assert_eq!(
784 parse_css_color("#0000ff80").unwrap(),
785 ColorU::new(0, 0, 255, 128)
786 );
787 assert_eq!(
789 parse_css_color("#FFC0CB").unwrap(),
790 ColorU::new_rgb(255, 192, 203)
791 ); }
793
794 #[test]
795 fn test_parse_color_rgb() {
796 assert_eq!(parse_css_color("rgb(255, 0, 0)").unwrap(), ColorU::RED);
797 assert_eq!(
798 parse_css_color("rgba(0, 255, 0, 0.5)").unwrap(),
799 ColorU::new(0, 255, 0, 128)
800 );
801 assert_eq!(
802 parse_css_color("rgba(10, 20, 30, 1)").unwrap(),
803 ColorU::new_rgb(10, 20, 30)
804 );
805 assert_eq!(parse_css_color("rgb( 0 , 0 , 0 )").unwrap(), ColorU::BLACK);
806 }
807
808 #[test]
809 fn test_parse_color_hsl() {
810 assert_eq!(parse_css_color("hsl(0, 100%, 50%)").unwrap(), ColorU::RED);
811 assert_eq!(
812 parse_css_color("hsl(120, 100%, 50%)").unwrap(),
813 ColorU::GREEN
814 );
815 assert_eq!(
816 parse_css_color("hsla(240, 100%, 50%, 0.5)").unwrap(),
817 ColorU::new(0, 0, 255, 128)
818 );
819 assert_eq!(parse_css_color("hsl(0, 0%, 0%)").unwrap(), ColorU::BLACK);
820 }
821
822 #[test]
823 fn test_parse_color_errors() {
824 assert!(parse_css_color("redd").is_err());
825 assert!(parse_css_color("#12345").is_err()); assert!(parse_css_color("#ggg").is_err()); assert!(parse_css_color("rgb(255, 0)").is_err()); assert!(parse_css_color("rgba(255, 0, 0, 2)").is_err()); assert!(parse_css_color("rgb(256, 0, 0)").is_err()); assert!(parse_css_color("hsl(0, 100, 50%)").is_ok()); assert!(parse_css_color("rgb(255 0 0)").is_err()); }
835}