Skip to main content

retroglyph_core/color/
named.rs

1//! `Color`'s string-name/hex constructors: [`Color::from_named`] (the CSS named-color table) and
2//! [`Color::from_hex`], both `gem`-backed.
3
4use gem::space::Srgb;
5
6use super::Color;
7
8impl Color {
9    /// Looks up a CSS named color by name (case-insensitive).
10    ///
11    /// Supports all 147 CSS Color Module Level 4 named colors.
12    /// Returns `None` for unrecognized names.
13    ///
14    /// # Examples
15    ///
16    /// ```
17    /// use retroglyph_core::color::Color;
18    ///
19    /// let gold = Color::from_named("gold");
20    /// assert_eq!(gold, Some(Color::Rgb { r: 255, g: 215, b: 0 }));
21    ///
22    /// assert_eq!(Color::from_named("not-a-color"), None);
23    /// ```
24    #[must_use]
25    #[allow(clippy::too_many_lines)]
26    pub fn from_named(name: &str) -> Option<Self> {
27        let lower = name.to_ascii_lowercase();
28        let srgb = match lower.as_str() {
29            "aliceblue" => gem::named::ALICE_BLUE,
30            "antiquewhite" => gem::named::ANTIQUE_WHITE,
31            "aqua" => gem::named::AQUA,
32            "aquamarine" => gem::named::AQUAMARINE,
33            "azure" => gem::named::AZURE,
34            "beige" => gem::named::BEIGE,
35            "bisque" => gem::named::BISQUE,
36            "black" => gem::named::BLACK,
37            "blanchedalmond" => gem::named::BLANCHED_ALMOND,
38            "blue" => gem::named::BLUE,
39            "blueviolet" => gem::named::BLUE_VIOLET,
40            "brown" => gem::named::BROWN,
41            "burlywood" => gem::named::BURLY_WOOD,
42            "cadetblue" => gem::named::CADET_BLUE,
43            "chartreuse" => gem::named::CHARTREUSE,
44            "chocolate" => gem::named::CHOCOLATE,
45            "coral" => gem::named::CORAL,
46            "cornflowerblue" => gem::named::CORNFLOWER_BLUE,
47            "cornsilk" => gem::named::CORNSILK,
48            "crimson" => gem::named::CRIMSON,
49            "cyan" => gem::named::CYAN,
50            "darkblue" => gem::named::DARK_BLUE,
51            "darkcyan" => gem::named::DARK_CYAN,
52            "darkgoldenrod" => gem::named::DARK_GOLDENROD,
53            "darkgray" | "darkgrey" => gem::named::DARK_GRAY,
54            "darkgreen" => gem::named::DARK_GREEN,
55            "darkkhaki" => gem::named::DARK_KHAKI,
56            "darkmagenta" => gem::named::DARK_MAGENTA,
57            "darkolivegreen" => gem::named::DARK_OLIVE_GREEN,
58            "darkorange" => gem::named::DARK_ORANGE,
59            "darkorchid" => gem::named::DARK_ORCHID,
60            "darkred" => gem::named::DARK_RED,
61            "darksalmon" => gem::named::DARK_SALMON,
62            "darkseagreen" => gem::named::DARK_SEA_GREEN,
63            "darkslateblue" => gem::named::DARK_SLATE_BLUE,
64            "darkslategray" | "darkslategrey" => gem::named::DARK_SLATE_GRAY,
65            "darkturquoise" => gem::named::DARK_TURQUOISE,
66            "darkviolet" => gem::named::DARK_VIOLET,
67            "deeppink" => gem::named::DEEP_PINK,
68            "deepskyblue" => gem::named::DEEP_SKY_BLUE,
69            "dimgray" | "dimgrey" => gem::named::DIM_GRAY,
70            "dodgerblue" => gem::named::DODGER_BLUE,
71            "firebrick" => gem::named::FIREBRICK,
72            "floralwhite" => gem::named::FLORAL_WHITE,
73            "forestgreen" => gem::named::FOREST_GREEN,
74            "fuchsia" => gem::named::FUCHSIA,
75            "gainsboro" => gem::named::GAINSBORO,
76            "ghostwhite" => gem::named::GHOST_WHITE,
77            "gold" => gem::named::GOLD,
78            "goldenrod" => gem::named::GOLDENROD,
79            "gray" | "grey" => gem::named::GRAY,
80            "green" => gem::named::GREEN,
81            "greenyellow" => gem::named::GREEN_YELLOW,
82            "honeydew" => gem::named::HONEYDEW,
83            "hotpink" => gem::named::HOT_PINK,
84            "indianred" => gem::named::INDIAN_RED,
85            "indigo" => gem::named::INDIGO,
86            "ivory" => gem::named::IVORY,
87            "khaki" => gem::named::KHAKI,
88            "lavender" => gem::named::LAVENDER,
89            "lavenderblush" => gem::named::LAVENDER_BLUSH,
90            "lawngreen" => gem::named::LAWN_GREEN,
91            "lemonchiffon" => gem::named::LEMON_CHIFFON,
92            "lightblue" => gem::named::LIGHT_BLUE,
93            "lightcoral" => gem::named::LIGHT_CORAL,
94            "lightcyan" => gem::named::LIGHT_CYAN,
95            "lightgoldenrodyellow" => gem::named::LIGHT_GOLDENROD_YELLOW,
96            "lightgray" | "lightgrey" => gem::named::LIGHT_GRAY,
97            "lightgreen" => gem::named::LIGHT_GREEN,
98            "lightpink" => gem::named::LIGHT_PINK,
99            "lightsalmon" => gem::named::LIGHT_SALMON,
100            "lightseagreen" => gem::named::LIGHT_SEA_GREEN,
101            "lightskyblue" => gem::named::LIGHT_SKY_BLUE,
102            "lightslategray" | "lightslategrey" => gem::named::LIGHT_SLATE_GRAY,
103            "lightsteelblue" => gem::named::LIGHT_STEEL_BLUE,
104            "lightyellow" => gem::named::LIGHT_YELLOW,
105            "lime" => gem::named::LIME,
106            "limegreen" => gem::named::LIME_GREEN,
107            "linen" => gem::named::LINEN,
108            "magenta" => gem::named::MAGENTA,
109            "maroon" => gem::named::MAROON,
110            "mediumaquamarine" => gem::named::MEDIUM_AQUAMARINE,
111            "mediumblue" => gem::named::MEDIUM_BLUE,
112            "mediumorchid" => gem::named::MEDIUM_ORCHID,
113            "mediumpurple" => gem::named::MEDIUM_PURPLE,
114            "mediumseagreen" => gem::named::MEDIUM_SEA_GREEN,
115            "mediumslateblue" => gem::named::MEDIUM_SLATE_BLUE,
116            "mediumspringgreen" => gem::named::MEDIUM_SPRING_GREEN,
117            "mediumturquoise" => gem::named::MEDIUM_TURQUOISE,
118            "mediumvioletred" => gem::named::MEDIUM_VIOLET_RED,
119            "midnightblue" => gem::named::MIDNIGHT_BLUE,
120            "mintcream" => gem::named::MINT_CREAM,
121            "mistyrose" => gem::named::MISTY_ROSE,
122            "moccasin" => gem::named::MOCCASIN,
123            "navajowhite" => gem::named::NAVAJO_WHITE,
124            "navy" => gem::named::NAVY,
125            "oldlace" => gem::named::OLD_LACE,
126            "olive" => gem::named::OLIVE,
127            "olivedrab" => gem::named::OLIVE_DRAB,
128            "orange" => gem::named::ORANGE,
129            "orangered" => gem::named::ORANGE_RED,
130            "orchid" => gem::named::ORCHID,
131            "palegoldenrod" => gem::named::PALE_GOLDENROD,
132            "palegreen" => gem::named::PALE_GREEN,
133            "paleturquoise" => gem::named::PALE_TURQUOISE,
134            "palevioletred" => gem::named::PALE_VIOLET_RED,
135            "papayawhip" => gem::named::PAPAYA_WHIP,
136            "peachpuff" => gem::named::PEACH_PUFF,
137            "peru" => gem::named::PERU,
138            "pink" => gem::named::PINK,
139            "plum" => gem::named::PLUM,
140            "powderblue" => gem::named::POWDER_BLUE,
141            "purple" => gem::named::PURPLE,
142            "rebeccapurple" => gem::named::REBECCA_PURPLE,
143            "red" => gem::named::RED,
144            "rosybrown" => gem::named::ROSY_BROWN,
145            "royalblue" => gem::named::ROYAL_BLUE,
146            "saddlebrown" => gem::named::SADDLE_BROWN,
147            "salmon" => gem::named::SALMON,
148            "sandybrown" => gem::named::SANDY_BROWN,
149            "seagreen" => gem::named::SEA_GREEN,
150            "seashell" => gem::named::SEASHELL,
151            "sienna" => gem::named::SIENNA,
152            "silver" => gem::named::SILVER,
153            "skyblue" => gem::named::SKY_BLUE,
154            "slateblue" => gem::named::SLATE_BLUE,
155            "slategray" | "slategrey" => gem::named::SLATE_GRAY,
156            "snow" => gem::named::SNOW,
157            "springgreen" => gem::named::SPRING_GREEN,
158            "steelblue" => gem::named::STEEL_BLUE,
159            "tan" => gem::named::TAN,
160            "teal" => gem::named::TEAL,
161            "thistle" => gem::named::THISTLE,
162            "tomato" => gem::named::TOMATO,
163            "turquoise" => gem::named::TURQUOISE,
164            "violet" => gem::named::VIOLET,
165            "wheat" => gem::named::WHEAT,
166            "white" => gem::named::WHITE,
167            "whitesmoke" => gem::named::WHITE_SMOKE,
168            "yellow" => gem::named::YELLOW,
169            "yellowgreen" => gem::named::YELLOW_GREEN,
170            _ => return None,
171        };
172        Some(Self::from_srgb(srgb))
173    }
174
175    /// Parses a CSS hex color string into an `Rgb` variant.
176    ///
177    /// Accepts `#rgb` and `#rrggbb` formats (case-insensitive).
178    /// Returns `None` for invalid input.
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// # fn main() {
184    /// # fn run() -> Option<()> {
185    /// use retroglyph_core::color::Color;
186    ///
187    /// let c = Color::from_hex("#ff8000")?;
188    /// assert_eq!(c, Color::Rgb { r: 255, g: 128, b: 0 });
189    ///
190    /// assert_eq!(Color::from_hex("not-color"), None);
191    /// # Some(())
192    /// # }
193    /// # run().unwrap();
194    /// # }
195    /// ```
196    #[must_use]
197    pub fn from_hex(hex: &str) -> Option<Self> {
198        Srgb::from_hex(hex).map(Self::from_srgb)
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_from_named_color() {
208        let gold = Color::from_named("gold").expect("gold is a named color");
209        assert_eq!(
210            gold,
211            Color::Rgb {
212                r: 255,
213                g: 215,
214                b: 0
215            }
216        );
217    }
218
219    #[test]
220    fn test_from_named_color_case_insensitive() {
221        let red = Color::from_named("RED").expect("should match uppercase");
222        assert_eq!(red, Color::Rgb { r: 255, g: 0, b: 0 });
223    }
224
225    #[test]
226    fn test_from_named_color_invalid() {
227        assert_eq!(Color::from_named("not-a-color"), None);
228    }
229
230    #[test]
231    fn test_from_hex_full() {
232        let c = Color::from_hex("#ff8000").expect("valid hex");
233        assert_eq!(
234            c,
235            Color::Rgb {
236                r: 255,
237                g: 128,
238                b: 0
239            }
240        );
241    }
242
243    #[test]
244    fn test_from_hex_short() {
245        let c = Color::from_hex("#f80").expect("valid short hex");
246        assert_eq!(
247            c,
248            Color::Rgb {
249                r: 255,
250                g: 136,
251                b: 0
252            }
253        );
254    }
255
256    #[test]
257    fn test_from_hex_invalid() {
258        assert_eq!(Color::from_hex("xyz"), None);
259        assert_eq!(Color::from_hex("#xyz"), None);
260    }
261
262    #[test]
263    fn test_gray_grey_synonyms() {
264        assert_eq!(Color::from_named("gray"), Color::from_named("grey"));
265        assert_eq!(Color::from_named("darkgray"), Color::from_named("darkgrey"));
266        assert_eq!(
267            Color::from_named("slategray"),
268            Color::from_named("slategrey")
269        );
270    }
271}