css_color/
lib.rs

1#![allow(clippy::get_first)]
2#![allow(clippy::int_plus_one)]
3#![allow(clippy::len_zero)]
4#![cfg_attr(feature = "bench", feature(test))]
5
6use std::f32;
7use std::str::{self, FromStr};
8
9const NONE: f32 = 0_f32;
10
11#[doc(hidden)]
12pub type Rgba = Srgb;
13
14/// A color in the sRGB color space.
15#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct Srgb {
17    /// The red component.
18    pub red: f32,
19    /// The green component.
20    pub green: f32,
21    /// The blue component.
22    pub blue: f32,
23    /// The alpha component.
24    pub alpha: f32,
25}
26
27impl Srgb {
28    pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Srgb {
29        Srgb {
30            red,
31            green,
32            blue,
33            alpha,
34        }
35    }
36
37    fn from_rgb8(red: u8, green: u8, blue: u8) -> Srgb {
38        Srgb::from_rgba8(red, green, blue, 255)
39    }
40
41    fn from_rgba8(red: u8, green: u8, blue: u8, alpha: u8) -> Srgb {
42        Srgb {
43            red: red as f32 / 255.,
44            green: green as f32 / 255.,
45            blue: blue as f32 / 255.,
46            alpha: alpha as f32 / 255.,
47        }
48    }
49}
50
51#[derive(Debug)]
52pub struct ParseColorError;
53
54impl FromStr for Srgb {
55    type Err = ParseColorError;
56
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        parse_css_color(s.as_bytes()).map_err(|_| ParseColorError)
59    }
60}
61
62// https://www.w3.org/TR/css-color-4/
63fn parse_css_color(input: &[u8]) -> Result<Srgb, ()> {
64    if let Ok(input) = consume_byte(input, b'#') {
65        parse_hex(input)
66    } else if let Ok(input) = consume_function(input, b"rgb") {
67        parse_rgb(input)
68    } else if let Ok(input) = consume_function(input, b"rgba") {
69        parse_rgb(input)
70    } else if let Ok(input) = consume_function(input, b"hsl") {
71        parse_hsl(input)
72    } else if let Ok(input) = consume_function(input, b"hsla") {
73        parse_hsl(input)
74    } else if let Ok(input) = consume_function(input, b"hwb") {
75        parse_hwb(input)
76    } else {
77        parse_named(input)
78    }
79}
80
81fn clamp_unit_f32(value: f32) -> f32 {
82    value.max(0.).min(1.)
83}
84
85fn normalize_hue(value: f32) -> f32 {
86    value - value.floor()
87}
88
89struct Hsla {
90    pub hue: f32,
91    pub saturation: f32,
92    pub lightness: f32,
93    pub alpha: f32,
94}
95
96// https://www.w3.org/TR/css-color-4/#hsl-to-rgb
97impl From<Hsla> for Srgb {
98    fn from(hsla: Hsla) -> Self {
99        let t2 = if hsla.lightness <= 0.5 {
100            hsla.lightness * (hsla.saturation + 1.)
101        } else {
102            hsla.lightness + hsla.saturation - hsla.lightness * hsla.saturation
103        };
104        let t1 = hsla.lightness * 2. - t2;
105
106        let hue_to_rgb = |h6: f32| -> f32 {
107            if h6 < 1. {
108                (t2 - t1) * h6 + t1
109            } else if h6 < 3. {
110                t2
111            } else if h6 < 4. {
112                (t2 - t1) * (4. - h6) + t1
113            } else {
114                t1
115            }
116        };
117        let h6 = hsla.hue * 6.;
118        let h6_red = if h6 + 2. < 6. { h6 + 2. } else { h6 - 4. };
119        let h6_blue = if h6 - 2. >= 0. { h6 - 2. } else { h6 + 4. };
120        Srgb {
121            red: hue_to_rgb(h6_red),
122            green: hue_to_rgb(h6),
123            blue: hue_to_rgb(h6_blue),
124            alpha: hsla.alpha,
125        }
126    }
127}
128
129struct Hwba {
130    pub hue: f32,
131    pub whiteness: f32,
132    pub blackness: f32,
133    pub alpha: f32,
134}
135
136// https://www.w3.org/TR/css-color-4/#hwb-to-rgb
137impl From<Hwba> for Srgb {
138    fn from(hwba: Hwba) -> Self {
139        // If the sum of these two arguments is greater than 100%, then at computed-value time they
140        // are further normalized to add up to 100%, with the same relative ratio.
141        if hwba.whiteness + hwba.blackness >= 1. {
142            let gray = hwba.whiteness / (hwba.whiteness + hwba.blackness);
143            Srgb {
144                red: gray,
145                green: gray,
146                blue: gray,
147                alpha: hwba.alpha,
148            }
149        } else {
150            fn hue_to_rgb(h6: f32) -> f32 {
151                if h6 < 1. {
152                    h6
153                } else if h6 < 3. {
154                    1.
155                } else if h6 < 4. {
156                    4. - h6
157                } else {
158                    0.
159                }
160            }
161            let h6 = hwba.hue * 6.;
162            let h6_red = if h6 + 2. < 6. { h6 + 2. } else { h6 - 4. };
163            let h6_blue = if h6 - 2. >= 0. { h6 - 2. } else { h6 + 4. };
164            let x = 1. - hwba.whiteness - hwba.blackness;
165            Srgb {
166                red: hue_to_rgb(h6_red) * x + hwba.whiteness,
167                green: hue_to_rgb(h6) * x + hwba.whiteness,
168                blue: hue_to_rgb(h6_blue) * x + hwba.whiteness,
169                alpha: hwba.alpha,
170            }
171        }
172    }
173}
174
175fn is_ident_start(input: &[u8]) -> bool {
176    match input.get(0) {
177        Some(b'-') => match input.get(1) {
178            Some(b'-') => true,
179            Some(c) => is_name_start(*c),
180            _ => false,
181        },
182        Some(c) => is_name_start(*c),
183        _ => false,
184    }
185}
186
187fn is_name_start(c: u8) -> bool {
188    match c {
189        b'a'..=b'z' | b'A'..=b'Z' | b'_' => true,
190        c => !c.is_ascii(),
191    }
192}
193
194fn is_name(c: u8) -> bool {
195    match c {
196        b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'-' => true,
197        c => !c.is_ascii(),
198    }
199}
200
201fn is_whitespace(c: u8) -> bool {
202    c <= b' ' && (c == b' ' || c == b'\n' || c == b'\t' || c == b'\r' || c == b'\x0C')
203}
204
205fn digit(c: u8) -> Result<u8, ()> {
206    match c {
207        b'0'..=b'9' => Ok(c - b'0'),
208        _ => Err(()),
209    }
210}
211
212fn hex_digit(c: u8) -> Result<u8, ()> {
213    match c {
214        b'0'..=b'9' => Ok(c - b'0'),
215        b'A'..=b'F' => Ok(c - b'A' + 10),
216        b'a'..=b'f' => Ok(c - b'a' + 10),
217        _ => Err(()),
218    }
219}
220
221fn skip_ws(mut input: &[u8]) -> &[u8] {
222    while input.len() > 0 && is_whitespace(input[0]) {
223        input = &input[1..];
224    }
225    input
226}
227
228fn consume_byte(input: &[u8], b: u8) -> Result<&[u8], ()> {
229    match input.get(0) {
230        Some(c) if *c == b => Ok(&input[1..]),
231        _ => Err(()),
232    }
233}
234
235/// Consumes a function-token matching the given identifier.
236///
237/// Any whitespace following the function-token is also consumed.
238fn consume_function<'a>(input: &'a [u8], name: &[u8]) -> Result<&'a [u8], ()> {
239    debug_assert!(is_ident_start(name));
240
241    let n = name.len();
242    if input.len() >= n + 1 && input[..n].eq_ignore_ascii_case(name) && input[n] == b'(' {
243        Ok(skip_ws(&input[n + 1..]))
244    } else {
245        Err(())
246    }
247}
248
249#[inline]
250fn consume_name<'a>(input: &'a [u8], name: &[u8]) -> Result<&'a [u8], ()> {
251    debug_assert!(is_ident_start(name));
252
253    let n = name.len();
254    if input.len() >= n
255        && input[..n].eq_ignore_ascii_case(name)
256        && input.get(n).filter(|c| is_name(**c)).is_none()
257    {
258        Ok(&input[n..])
259    } else {
260        Err(())
261    }
262}
263
264fn consume_none(input: &[u8]) -> Result<&[u8], ()> {
265    consume_name(input, b"none")
266}
267
268fn consume_number(mut input: &[u8]) -> Result<&[u8], ()> {
269    fn skip_sign(input: &[u8]) -> &[u8] {
270        match input.get(0) {
271            Some(b'+') | Some(b'-') => &input[1..],
272            _ => input,
273        }
274    }
275    fn consume_digits(mut input: &[u8]) -> Result<&[u8], ()> {
276        match input.get(0).map(|c| digit(*c)) {
277            Some(Ok(_)) => {
278                while let Some(Ok(_)) = input.get(0).map(|c| digit(*c)) {
279                    input = &input[1..];
280                }
281                Ok(input)
282            }
283            _ => Err(()),
284        }
285    }
286
287    input = skip_sign(input);
288    match input.get(0) {
289        Some(b'.') => {}
290        _ => {
291            input = consume_digits(input)?;
292        }
293    }
294    if let Some(b'.') = input.get(0) {
295        input = consume_digits(&input[1..])?;
296    }
297    match input.get(0) {
298        Some(b'E') | Some(b'e') => {
299            input = skip_sign(&input[1..]);
300            input = consume_digits(input)?;
301        }
302        _ => {}
303    }
304    Ok(input)
305}
306
307fn parse_number(input: &[u8]) -> Result<(&[u8], f32), ()> {
308    let pos = input.len() - consume_number(input)?.len();
309    Ok((
310        &input[pos..],
311        str::from_utf8(&input[..pos])
312            .unwrap()
313            .parse()
314            .map_err(|_| ())?,
315    ))
316}
317
318// <percentage> = <number> %
319fn parse_percentage(input: &[u8]) -> Result<(&[u8], f32), ()> {
320    let (input, value) = parse_number(input)?;
321    let input = consume_byte(input, b'%')?;
322    Ok((input, value / 100.))
323}
324
325enum NumberOrPercentage {
326    Number,
327    Percentage,
328}
329use NumberOrPercentage::*;
330
331trait Frac {
332    fn frac(&self, denom: f32) -> f32;
333}
334
335impl Frac for (NumberOrPercentage, f32) {
336    fn frac(&self, denom: f32) -> f32 {
337        match self.0 {
338            Number => self.1 / denom,
339            Percentage => self.1,
340        }
341    }
342}
343
344fn parse_number_or_percentage(input: &[u8]) -> Result<(&[u8], (NumberOrPercentage, f32)), ()> {
345    let (input, value) = parse_number(input)?;
346
347    if let Ok(input) = consume_byte(input, b'%') {
348        Ok((input, (Percentage, value / 100.)))
349    } else {
350        Ok((input, (Number, value)))
351    }
352}
353
354// <alpha-value> = <number> | <percentage>
355fn parse_alpha_value(input: &[u8]) -> Result<(&[u8], f32), ()> {
356    let (input, (_, alpha)) = parse_number_or_percentage(input)?;
357    Ok((input, clamp_unit_f32(alpha)))
358}
359
360// <hue> = <number> | <angle>
361fn parse_hue(input: &[u8]) -> Result<(&[u8], f32), ()> {
362    let (input, value) = parse_number(input)?;
363
364    if !is_ident_start(input) {
365        Ok((input, value / 360.))
366    } else if let Ok(input) = consume_name(input, b"deg") {
367        Ok((input, value / 360.))
368    } else if let Ok(input) = consume_name(input, b"grad") {
369        Ok((input, value / 400.))
370    } else if let Ok(input) = consume_name(input, b"rad") {
371        Ok((input, value / (2. * f32::consts::PI)))
372    } else if let Ok(input) = consume_name(input, b"turn") {
373        Ok((input, value))
374    } else {
375        Err(())
376    }
377}
378
379/// Parse sRGB hex colors.
380fn parse_hex(input: &[u8]) -> Result<Srgb, ()> {
381    match input.len() {
382        8 => Ok(Srgb::from_rgba8(
383            hex_digit(input[0])? * 16 + hex_digit(input[1])?,
384            hex_digit(input[2])? * 16 + hex_digit(input[3])?,
385            hex_digit(input[4])? * 16 + hex_digit(input[5])?,
386            hex_digit(input[6])? * 16 + hex_digit(input[7])?,
387        )),
388        6 => Ok(Srgb::from_rgb8(
389            hex_digit(input[0])? * 16 + hex_digit(input[1])?,
390            hex_digit(input[2])? * 16 + hex_digit(input[3])?,
391            hex_digit(input[4])? * 16 + hex_digit(input[5])?,
392        )),
393        4 => Ok(Srgb::from_rgba8(
394            hex_digit(input[0])? * 17,
395            hex_digit(input[1])? * 17,
396            hex_digit(input[2])? * 17,
397            hex_digit(input[3])? * 17,
398        )),
399        3 => Ok(Srgb::from_rgb8(
400            hex_digit(input[0])? * 17,
401            hex_digit(input[1])? * 17,
402            hex_digit(input[2])? * 17,
403        )),
404        _ => Err(()),
405    }
406}
407
408// hsl()  = [ <legacy-hsl-syntax>  | <modern-hsl-syntax>  ]
409// hsla() = [ <legacy-hsla-syntax> | <modern-hsla-syntax> ]
410// <legacy-hsl-syntax>  = hsl(  <hue>, <percentage>, <percentage>, <alpha-value>? )
411// <legacy-hsla-syntax> = hsla( <hue>, <percentage>, <percentage>, <alpha-value>? )
412// <modern-hsl-syntax>  = hsl(  [<hue> | none]
413//                              [<percentage> | <number> | none]
414//                              [<percentage> | <number> | none]
415//                              [ / [<alpha-value> | none] ]? )
416// <modern-hsla-syntax> = hsla( [<hue> | none]
417//                              [<percentage> | <number> | none]
418//                              [<percentage> | <number> | none]
419//                              [ / [<alpha-value> | none] ]? )
420fn parse_hsl(input: &[u8]) -> Result<Srgb, ()> {
421    let (input, hue, legacy_syntax) = if let Ok((input, hue)) = parse_hue(input) {
422        let input = skip_ws(input);
423        match input.get(0) {
424            Some(b',') => (skip_ws(&input[1..]), hue, true),
425            _ => (input, hue, false),
426        }
427    } else {
428        (skip_ws(consume_none(input)?), NONE, false)
429    };
430
431    let (input, saturation, lightness) = if legacy_syntax {
432        let (mut input, saturation) = parse_percentage(input)?;
433        input = skip_ws(input);
434        input = skip_ws(consume_byte(input, b',')?);
435        let (mut input, lightness) = parse_percentage(input)?;
436        input = skip_ws(input);
437        (input, saturation, lightness)
438    } else {
439        let (input, saturation) = if let Ok((input, saturation)) = parse_number_or_percentage(input)
440        {
441            (skip_ws(input), saturation.frac(100.))
442        } else {
443            (skip_ws(consume_none(input)?), NONE)
444        };
445        let (input, lightness) = if let Ok((input, lightness)) = parse_number_or_percentage(input) {
446            (skip_ws(input), lightness.frac(100.))
447        } else {
448            (skip_ws(consume_none(input)?), NONE)
449        };
450        (input, saturation, lightness)
451    };
452
453    let (input, alpha) = match (input.get(0), legacy_syntax) {
454        (Some(b'/'), false) | (Some(b','), true) => {
455            let input = skip_ws(&input[1..]);
456            if let Ok((input, alpha)) = parse_alpha_value(input) {
457                (skip_ws(input), alpha)
458            } else if !legacy_syntax {
459                (skip_ws(consume_none(input)?), NONE)
460            } else {
461                return Err(());
462            }
463        }
464        _ => (input, 1.),
465    };
466
467    if input != b")" {
468        return Err(());
469    }
470
471    Ok(Srgb::from(Hsla {
472        hue: normalize_hue(hue),
473        saturation: clamp_unit_f32(saturation),
474        lightness: clamp_unit_f32(lightness),
475        alpha,
476    }))
477}
478
479// hwb() = hwb( [<hue> | none]
480//              [<percentage> | <number> | none]
481//              [<percentage> | <number> | none]
482//              [ / [<alpha-value> | none] ]? )
483fn parse_hwb(input: &[u8]) -> Result<Srgb, ()> {
484    let (input, hue) = if let Ok((input, hue)) = parse_hue(input) {
485        (skip_ws(input), hue)
486    } else {
487        (skip_ws(consume_none(input)?), NONE)
488    };
489
490    let (input, whiteness) = if let Ok((input, whiteness)) = parse_number_or_percentage(input) {
491        (skip_ws(input), whiteness.frac(100.))
492    } else {
493        (skip_ws(consume_none(input)?), NONE)
494    };
495
496    let (input, blackness) = if let Ok((input, blackness)) = parse_number_or_percentage(input) {
497        (skip_ws(input), blackness.frac(100.))
498    } else {
499        (skip_ws(consume_none(input)?), NONE)
500    };
501
502    let (input, alpha) = match input.get(0) {
503        Some(b'/') => {
504            let input = skip_ws(&input[1..]);
505            if let Ok((input, alpha)) = parse_alpha_value(input) {
506                (skip_ws(input), alpha)
507            } else {
508                (skip_ws(consume_none(input)?), NONE)
509            }
510        }
511        _ => (input, 1.),
512    };
513
514    if input != b")" {
515        return Err(());
516    }
517
518    Ok(Srgb::from(Hwba {
519        hue: normalize_hue(hue),
520        whiteness: clamp_unit_f32(whiteness),
521        blackness: clamp_unit_f32(blackness),
522        alpha,
523    }))
524}
525
526// rgb()  = [ <legacy-rgb-syntax>  | <modern-rgb-syntax>  ]
527// rgba() = [ <legacy-rgba-syntax> | <modern-rgba-syntax> ]
528// <legacy-rgb-syntax>  = rgb(  <percentage>#{3} , <alpha-value>? ) |
529//                        rgb(  <number>#{3}     , <alpha-value>? )
530// <legacy-rgba-syntax> = rgba( <percentage>#{3} , <alpha-value>? ) |
531//                        rgba( <number>#{3}     , <alpha-value>? )
532// <modern-rgb-syntax>  = rgb(  [ <number> | <percentage> | none]{3} [ / [<alpha-value> | none] ]? )
533// <modern-rgba-syntax> = rgba( [ <number> | <percentage> | none]{3} [ / [<alpha-value> | none] ]? )
534fn parse_rgb(input: &[u8]) -> Result<Srgb, ()> {
535    let (input, red, legacy_syntax) = if let Ok((input, red)) = parse_number_or_percentage(input) {
536        let input = skip_ws(input);
537        match input.get(0) {
538            Some(b',') => (skip_ws(&input[1..]), Some(red), true),
539            _ => (input, Some(red), false),
540        }
541    } else {
542        (skip_ws(consume_none(input)?), None, false)
543    };
544
545    let (input, red, green, blue) = if legacy_syntax {
546        match red.unwrap() {
547            (Number, red) => {
548                let (mut input, green) = parse_number(input)?;
549                input = skip_ws(input);
550                input = skip_ws(consume_byte(input, b',')?);
551                let (mut input, blue) = parse_number(input)?;
552                input = skip_ws(input);
553                (input, red / 255., green / 255., blue / 255.)
554            }
555            (Percentage, red) => {
556                let (mut input, green) = parse_percentage(input)?;
557                input = skip_ws(input);
558                input = skip_ws(consume_byte(input, b',')?);
559                let (mut input, blue) = parse_percentage(input)?;
560                input = skip_ws(input);
561                (input, red, green, blue)
562            }
563        }
564    } else {
565        let red = red.map_or(NONE, |red| red.frac(255.));
566        let (input, green) = if let Ok((input, green)) = parse_number_or_percentage(input) {
567            (skip_ws(input), green.frac(255.))
568        } else {
569            (skip_ws(consume_none(input)?), NONE)
570        };
571        let (input, blue) = if let Ok((input, blue)) = parse_number_or_percentage(input) {
572            (skip_ws(input), blue.frac(255.))
573        } else {
574            (skip_ws(consume_none(input)?), NONE)
575        };
576        (input, red, green, blue)
577    };
578
579    let (input, alpha) = match (input.get(0), legacy_syntax) {
580        (Some(b'/'), false) | (Some(b','), true) => {
581            let input = skip_ws(&input[1..]);
582            if let Ok((input, alpha)) = parse_alpha_value(input) {
583                (skip_ws(input), alpha)
584            } else if !legacy_syntax {
585                (skip_ws(consume_none(input)?), NONE)
586            } else {
587                return Err(());
588            }
589        }
590        _ => (input, 1.),
591    };
592
593    if input != b")" {
594        return Err(());
595    }
596
597    Ok(Srgb::new(
598        clamp_unit_f32(red),
599        clamp_unit_f32(green),
600        clamp_unit_f32(blue),
601        alpha,
602    ))
603}
604
605macro_rules! rgb {
606    ($red: expr, $green: expr, $blue: expr) => {
607        Srgb::from_rgb8($red, $green, $blue)
608    };
609}
610
611fn parse_named(input: &[u8]) -> Result<Srgb, ()> {
612    const NAMED_MAX_LEN: usize = 20;
613    if input.len() > NAMED_MAX_LEN {
614        return Err(());
615    }
616    let mut name = [b'\0'; NAMED_MAX_LEN];
617    let name = &mut name[..input.len()];
618    for (i, c) in input.iter().enumerate() {
619        name[i] = c.to_ascii_lowercase();
620    }
621    Ok(match &*name {
622        b"aliceblue" => rgb!(240, 248, 255),
623        b"antiquewhite" => rgb!(250, 235, 215),
624        b"aqua" => rgb!(0, 255, 255),
625        b"aquamarine" => rgb!(127, 255, 212),
626        b"azure" => rgb!(240, 255, 255),
627        b"beige" => rgb!(245, 245, 220),
628        b"bisque" => rgb!(255, 228, 196),
629        b"black" => rgb!(0, 0, 0),
630        b"blanchedalmond" => rgb!(255, 235, 205),
631        b"blue" => rgb!(0, 0, 255),
632        b"blueviolet" => rgb!(138, 43, 226),
633        b"brown" => rgb!(165, 42, 42),
634        b"burlywood" => rgb!(222, 184, 135),
635        b"cadetblue" => rgb!(95, 158, 160),
636        b"chartreuse" => rgb!(127, 255, 0),
637        b"chocolate" => rgb!(210, 105, 30),
638        b"coral" => rgb!(255, 127, 80),
639        b"cornflowerblue" => rgb!(100, 149, 237),
640        b"cornsilk" => rgb!(255, 248, 220),
641        b"crimson" => rgb!(220, 20, 60),
642        b"cyan" => rgb!(0, 255, 255),
643        b"darkblue" => rgb!(0, 0, 139),
644        b"darkcyan" => rgb!(0, 139, 139),
645        b"darkgoldenrod" => rgb!(184, 134, 11),
646        b"darkgray" => rgb!(169, 169, 169),
647        b"darkgreen" => rgb!(0, 100, 0),
648        b"darkgrey" => rgb!(169, 169, 169),
649        b"darkkhaki" => rgb!(189, 183, 107),
650        b"darkmagenta" => rgb!(139, 0, 139),
651        b"darkolivegreen" => rgb!(85, 107, 47),
652        b"darkorange" => rgb!(255, 140, 0),
653        b"darkorchid" => rgb!(153, 50, 204),
654        b"darkred" => rgb!(139, 0, 0),
655        b"darksalmon" => rgb!(233, 150, 122),
656        b"darkseagreen" => rgb!(143, 188, 143),
657        b"darkslateblue" => rgb!(72, 61, 139),
658        b"darkslategray" => rgb!(47, 79, 79),
659        b"darkslategrey" => rgb!(47, 79, 79),
660        b"darkturquoise" => rgb!(0, 206, 209),
661        b"darkviolet" => rgb!(148, 0, 211),
662        b"deeppink" => rgb!(255, 20, 147),
663        b"deepskyblue" => rgb!(0, 191, 255),
664        b"dimgray" => rgb!(105, 105, 105),
665        b"dimgrey" => rgb!(105, 105, 105),
666        b"dodgerblue" => rgb!(30, 144, 255),
667        b"firebrick" => rgb!(178, 34, 34),
668        b"floralwhite" => rgb!(255, 250, 240),
669        b"forestgreen" => rgb!(34, 139, 34),
670        b"fuchsia" => rgb!(255, 0, 255),
671        b"gainsboro" => rgb!(220, 220, 220),
672        b"ghostwhite" => rgb!(248, 248, 255),
673        b"gold" => rgb!(255, 215, 0),
674        b"goldenrod" => rgb!(218, 165, 32),
675        b"gray" => rgb!(128, 128, 128),
676        b"green" => rgb!(0, 128, 0),
677        b"greenyellow" => rgb!(173, 255, 47),
678        b"grey" => rgb!(128, 128, 128),
679        b"honeydew" => rgb!(240, 255, 240),
680        b"hotpink" => rgb!(255, 105, 180),
681        b"indianred" => rgb!(205, 92, 92),
682        b"indigo" => rgb!(75, 0, 130),
683        b"ivory" => rgb!(255, 255, 240),
684        b"khaki" => rgb!(240, 230, 140),
685        b"lavender" => rgb!(230, 230, 250),
686        b"lavenderblush" => rgb!(255, 240, 245),
687        b"lawngreen" => rgb!(124, 252, 0),
688        b"lemonchiffon" => rgb!(255, 250, 205),
689        b"lightblue" => rgb!(173, 216, 230),
690        b"lightcoral" => rgb!(240, 128, 128),
691        b"lightcyan" => rgb!(224, 255, 255),
692        b"lightgoldenrodyellow" => rgb!(250, 250, 210),
693        b"lightgray" => rgb!(211, 211, 211),
694        b"lightgreen" => rgb!(144, 238, 144),
695        b"lightgrey" => rgb!(211, 211, 211),
696        b"lightpink" => rgb!(255, 182, 193),
697        b"lightsalmon" => rgb!(255, 160, 122),
698        b"lightseagreen" => rgb!(32, 178, 170),
699        b"lightskyblue" => rgb!(135, 206, 250),
700        b"lightslategray" => rgb!(119, 136, 153),
701        b"lightslategrey" => rgb!(119, 136, 153),
702        b"lightsteelblue" => rgb!(176, 196, 222),
703        b"lightyellow" => rgb!(255, 255, 224),
704        b"lime" => rgb!(0, 255, 0),
705        b"limegreen" => rgb!(50, 205, 50),
706        b"linen" => rgb!(250, 240, 230),
707        b"magenta" => rgb!(255, 0, 255),
708        b"maroon" => rgb!(128, 0, 0),
709        b"mediumaquamarine" => rgb!(102, 205, 170),
710        b"mediumblue" => rgb!(0, 0, 205),
711        b"mediumorchid" => rgb!(186, 85, 211),
712        b"mediumpurple" => rgb!(147, 112, 219),
713        b"mediumseagreen" => rgb!(60, 179, 113),
714        b"mediumslateblue" => rgb!(123, 104, 238),
715        b"mediumspringgreen" => rgb!(0, 250, 154),
716        b"mediumturquoise" => rgb!(72, 209, 204),
717        b"mediumvioletred" => rgb!(199, 21, 133),
718        b"midnightblue" => rgb!(25, 25, 112),
719        b"mintcream" => rgb!(245, 255, 250),
720        b"mistyrose" => rgb!(255, 228, 225),
721        b"moccasin" => rgb!(255, 228, 181),
722        b"navajowhite" => rgb!(255, 222, 173),
723        b"navy" => rgb!(0, 0, 128),
724        b"oldlace" => rgb!(253, 245, 230),
725        b"olive" => rgb!(128, 128, 0),
726        b"olivedrab" => rgb!(107, 142, 35),
727        b"orange" => rgb!(255, 165, 0),
728        b"orangered" => rgb!(255, 69, 0),
729        b"orchid" => rgb!(218, 112, 214),
730        b"palegoldenrod" => rgb!(238, 232, 170),
731        b"palegreen" => rgb!(152, 251, 152),
732        b"paleturquoise" => rgb!(175, 238, 238),
733        b"palevioletred" => rgb!(219, 112, 147),
734        b"papayawhip" => rgb!(255, 239, 213),
735        b"peachpuff" => rgb!(255, 218, 185),
736        b"peru" => rgb!(205, 133, 63),
737        b"pink" => rgb!(255, 192, 203),
738        b"plum" => rgb!(221, 160, 221),
739        b"powderblue" => rgb!(176, 224, 230),
740        b"purple" => rgb!(128, 0, 128),
741        b"rebeccapurple" => rgb!(102, 51, 153),
742        b"red" => rgb!(255, 0, 0),
743        b"rosybrown" => rgb!(188, 143, 143),
744        b"royalblue" => rgb!(65, 105, 225),
745        b"saddlebrown" => rgb!(139, 69, 19),
746        b"salmon" => rgb!(250, 128, 114),
747        b"sandybrown" => rgb!(244, 164, 96),
748        b"seagreen" => rgb!(46, 139, 87),
749        b"seashell" => rgb!(255, 245, 238),
750        b"sienna" => rgb!(160, 82, 45),
751        b"silver" => rgb!(192, 192, 192),
752        b"skyblue" => rgb!(135, 206, 235),
753        b"slateblue" => rgb!(106, 90, 205),
754        b"slategray" => rgb!(112, 128, 144),
755        b"slategrey" => rgb!(112, 128, 144),
756        b"snow" => rgb!(255, 250, 250),
757        b"springgreen" => rgb!(0, 255, 127),
758        b"steelblue" => rgb!(70, 130, 180),
759        b"tan" => rgb!(210, 180, 140),
760        b"teal" => rgb!(0, 128, 128),
761        b"thistle" => rgb!(216, 191, 216),
762        b"tomato" => rgb!(255, 99, 71),
763        b"turquoise" => rgb!(64, 224, 208),
764        b"violet" => rgb!(238, 130, 238),
765        b"wheat" => rgb!(245, 222, 179),
766        b"white" => rgb!(255, 255, 255),
767        b"whitesmoke" => rgb!(245, 245, 245),
768        b"yellow" => rgb!(255, 255, 0),
769        b"yellowgreen" => rgb!(154, 205, 50),
770        b"transparent" => Srgb::new(0., 0., 0., 0.),
771        _ => return Err(()),
772    })
773}
774
775#[cfg(test)]
776mod tests;