1use std::{fmt, str::FromStr};
5
6use rgb::Rgb;
7use strum::EnumCount;
8use tinyrand::{RandRange, StdRand};
9
10use crate::Prefix;
11
12use super::ExtendedColour;
13
14#[allow(missing_docs)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumCount)]
17pub enum Red {
18 Maroon,
19 #[allow(clippy::enum_variant_names)]
20 DarkRed,
21 Brown,
22 Firebrick,
23 Crimson,
24 #[allow(clippy::enum_variant_names)]
25 Red,
26 Tomato,
27 Coral,
28 #[allow(clippy::enum_variant_names)]
29 IndianRed,
30 LightCoral,
31 DarkSalmon,
32 Salmon,
33 LightSalmon,
34 #[allow(clippy::enum_variant_names)]
35 OrangeRed,
36 DarkOrange,
37 Orange,
38}
39
40impl fmt::Display for Red {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::Maroon => write!(f, "#800000"),
44 Self::DarkRed => write!(f, "#8B0000"),
45 Self::Brown => write!(f, "#A52A2A"),
46 Self::Firebrick => write!(f, "#B22222"),
47 Self::Crimson => write!(f, "#DC143C"),
48 Self::Red => write!(f, "#FF0000"),
49 Self::Tomato => write!(f, "#FF6347"),
50 Self::Coral => write!(f, "#FF7F50"),
51 Self::IndianRed => write!(f, "#CD5C5C"),
52 Self::LightCoral => write!(f, "#F08080"),
53 Self::DarkSalmon => write!(f, "#E9967A"),
54 Self::Salmon => write!(f, "#FA8072"),
55 Self::LightSalmon => write!(f, "#FFA07A"),
56 Self::OrangeRed => write!(f, "#FF4500"),
57 Self::DarkOrange => write!(f, "#FF8C00"),
58 Self::Orange => write!(f, "#FFA500"),
59 }
60 }
61}
62
63impl Red {
64 pub fn to_rgb(&self) -> Rgb<u8> {
78 let colour = self.to_string();
79
80 let r: u8 = u8::from_str_radix(&colour[1..3], 16).unwrap();
81 let g: u8 = u8::from_str_radix(&colour[3..5], 16).unwrap();
82 let b: u8 = u8::from_str_radix(&colour[5..7], 16).unwrap();
83
84 Rgb::new(r, g, b)
85 }
86
87 pub fn to_hex_triplet(&self, prefix: Prefix) -> String {
100 let rgb = self.to_rgb();
101
102 let prefix = match prefix {
103 Prefix::Hash => "#",
104 Prefix::None => "",
105 };
106
107 format!("{}{:02X}{:02X}{:02X}", prefix, rgb.r, rgb.g, rgb.b)
108 }
109
110 pub fn parse(name: &str) -> Option<Self> {
125 match name.to_lowercase().as_str() {
126 "#800000" | "800000" | "maroon" => Some(Self::Maroon),
127 "#8b0000" | "8b0000" | "darkred" => Some(Self::DarkRed),
128 "#a52a2a" | "a52a2a" | "brown" => Some(Self::Brown),
129 "#b22222" | "b22222" | "firebrick" => Some(Self::Firebrick),
130 "#dc143c" | "dc143c" | "crimson" => Some(Self::Crimson),
131 "#ff0000" | "ff0000" | "red" => Some(Self::Red),
132 "#ff6347" | "ff6347" | "tomato" => Some(Self::Tomato),
133 "#ff7f50" | "ff7f50" | "coral" => Some(Self::Coral),
134 "#cd5c5c" | "cd5c5c" | "indianred" => Some(Self::IndianRed),
135 "#f08080" | "f08080" | "lightcoral" => Some(Self::LightCoral),
136 "#e9967a" | "e9967a" | "darksalmon" => Some(Self::DarkSalmon),
137 "#fa8072" | "fa8072" | "salmon" => Some(Self::Salmon),
138 "#ffa07a" | "ffa07a" | "lightsalmon" => Some(Self::LightSalmon),
139 "#ff4500" | "ff4500" | "orangered" => Some(Self::OrangeRed),
140 "#ff8c00" | "ff8c00" | "darkorange" => Some(Self::DarkOrange),
141 "#ffa500" | "ffa500" | "orange" => Some(Self::Orange),
142 _ => None,
143 }
144 }
145
146 pub fn random() -> Self {
158 let mut rand = StdRand::default();
159
160 match rand.next_range(0..Self::COUNT) {
161 0 => Self::Maroon,
162 1 => Self::DarkRed,
163 2 => Self::Brown,
164 3 => Self::Firebrick,
165 4 => Self::Crimson,
166 5 => Self::Red,
167 6 => Self::Tomato,
168 7 => Self::Coral,
169 8 => Self::IndianRed,
170 9 => Self::LightCoral,
171 10 => Self::DarkSalmon,
172 11 => Self::Salmon,
173 12 => Self::LightSalmon,
174 13 => Self::OrangeRed,
175 14 => Self::DarkOrange,
176 15 => Self::Orange,
177 _ => Self::Red,
178 }
179 }
180}
181
182impl FromStr for Red {
183 type Err = String;
184 fn from_str(s: &str) -> Result<Self, Self::Err> {
185 match Self::parse(s) {
186 Some(colour) => Ok(colour),
187 None => Err(format!("Invalid Colour: {s}")),
188 }
189 }
190}
191
192impl ExtendedColour for Red {}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use rstest::rstest;
198
199 #[rstest]
200 #[case(Red::Maroon, "rgb(128,0,0)")]
201 #[case(Red::DarkRed, "rgb(139,0,0)")]
202 #[case(Red::Brown, "rgb(165,42,42)")]
203 #[case(Red::Firebrick, "rgb(178,34,34)")]
204 #[case(Red::Crimson, "rgb(220,20,60)")]
205 #[case(Red::Red, "rgb(255,0,0)")]
206 #[case(Red::Tomato, "rgb(255,99,71)")]
207 #[case(Red::Coral, "rgb(255,127,80)")]
208 #[case(Red::IndianRed, "rgb(205,92,92)")]
209 #[case(Red::LightCoral, "rgb(240,128,128)")]
210 #[case(Red::DarkSalmon, "rgb(233,150,122)")]
211 #[case(Red::Salmon, "rgb(250,128,114)")]
212 #[case(Red::LightSalmon, "rgb(255,160,122)")]
213 #[case(Red::OrangeRed, "rgb(255,69,0)")]
214 #[case(Red::DarkOrange, "rgb(255,140,0)")]
215 #[case(Red::Orange, "rgb(255,165,0)")]
216 fn test_rgb_string(#[case] colour: Red, #[case] expected: String) {
217 let rgb_colour = colour.to_rgb();
218 let string = rgb_colour.to_string();
219
220 assert_eq!(expected, string);
221 }
222
223 #[rstest]
224 #[case(Red::Maroon, "800000")]
225 #[case(Red::DarkRed, "8B0000")]
226 #[case(Red::Brown, "A52A2A")]
227 #[case(Red::Firebrick, "B22222")]
228 #[case(Red::Crimson, "DC143C")]
229 #[case(Red::Red, "FF0000")]
230 #[case(Red::Tomato, "FF6347")]
231 #[case(Red::Coral, "FF7F50")]
232 #[case(Red::IndianRed, "CD5C5C")]
233 #[case(Red::LightCoral, "F08080")]
234 #[case(Red::DarkSalmon, "E9967A")]
235 #[case(Red::Salmon, "FA8072")]
236 #[case(Red::LightSalmon, "FFA07A")]
237 #[case(Red::OrangeRed, "FF4500")]
238 #[case(Red::DarkOrange, "FF8C00")]
239 #[case(Red::Orange, "FFA500")]
240 fn test_hex_triplet_string(
241 #[case] colour: Red,
242 #[values(Prefix::None, Prefix::Hash)] prefix: Prefix,
243 #[case] expected: String,
244 ) {
245 let prefix_string = match prefix {
246 Prefix::None => "".to_string(),
247 Prefix::Hash => "#".to_string(),
248 };
249
250 let expected = format!("{prefix_string}{expected}");
251
252 let hex_colour = colour.to_hex_triplet(prefix);
253
254 assert_eq!(expected, hex_colour);
255 }
256
257 #[rstest]
258 #[case("#800000", Red::Maroon)]
259 #[case("800000", Red::Maroon)]
260 #[case("maroon", Red::Maroon)]
261 #[case("#8b0000", Red::DarkRed)]
262 #[case("8b0000", Red::DarkRed)]
263 #[case("darkred", Red::DarkRed)]
264 #[case("#a52a2a", Red::Brown)]
265 #[case("a52a2a", Red::Brown)]
266 #[case("brown", Red::Brown)]
267 #[case("#b22222", Red::Firebrick)]
268 #[case("b22222", Red::Firebrick)]
269 #[case("firebrick", Red::Firebrick)]
270 #[case("#dc143c", Red::Crimson)]
271 #[case("dc143c", Red::Crimson)]
272 #[case("crimson", Red::Crimson)]
273 #[case("#ff0000", Red::Red)]
274 #[case("ff0000", Red::Red)]
275 #[case("red", Red::Red)]
276 #[case("#ff6347", Red::Tomato)]
277 #[case("ff6347", Red::Tomato)]
278 #[case("tomato", Red::Tomato)]
279 #[case("#ff7f50", Red::Coral)]
280 #[case("ff7f50", Red::Coral)]
281 #[case("coral", Red::Coral)]
282 #[case("#cd5c5c", Red::IndianRed)]
283 #[case("cd5c5c", Red::IndianRed)]
284 #[case("indianred", Red::IndianRed)]
285 #[case("#f08080", Red::LightCoral)]
286 #[case("f08080", Red::LightCoral)]
287 #[case("lightcoral", Red::LightCoral)]
288 #[case("#e9967a", Red::DarkSalmon)]
289 #[case("e9967a", Red::DarkSalmon)]
290 #[case("darksalmon", Red::DarkSalmon)]
291 #[case("#fa8072", Red::Salmon)]
292 #[case("fa8072", Red::Salmon)]
293 #[case("salmon", Red::Salmon)]
294 #[case("#ffa07a", Red::LightSalmon)]
295 #[case("ffa07a", Red::LightSalmon)]
296 #[case("lightsalmon", Red::LightSalmon)]
297 #[case("#ff4500", Red::OrangeRed)]
298 #[case("ff4500", Red::OrangeRed)]
299 #[case("orangered", Red::OrangeRed)]
300 #[case("#ff8c00", Red::DarkOrange)]
301 #[case("ff8c00", Red::DarkOrange)]
302 #[case("darkorange", Red::DarkOrange)]
303 #[case("#ffa500", Red::Orange)]
304 #[case("ffa500", Red::Orange)]
305 #[case("orange", Red::Orange)]
306 fn test_from_str(#[case] input: &str, #[case] expected: Red) {
307 assert_eq!(expected, Red::from_str(input).unwrap())
308 }
309
310 #[rstest]
311 #[case("#800000", Some(Red::Maroon))]
312 #[case("800000", Some(Red::Maroon))]
313 #[case("maroon", Some(Red::Maroon))]
314 #[case("#8b0000", Some(Red::DarkRed))]
315 #[case("8b0000", Some(Red::DarkRed))]
316 #[case("darkred", Some(Red::DarkRed))]
317 #[case("#a52a2a", Some(Red::Brown))]
318 #[case("a52a2a", Some(Red::Brown))]
319 #[case("brown", Some(Red::Brown))]
320 #[case("#b22222", Some(Red::Firebrick))]
321 #[case("b22222", Some(Red::Firebrick))]
322 #[case("firebrick", Some(Red::Firebrick))]
323 #[case("#dc143c", Some(Red::Crimson))]
324 #[case("dc143c", Some(Red::Crimson))]
325 #[case("crimson", Some(Red::Crimson))]
326 #[case("#ff0000", Some(Red::Red))]
327 #[case("ff0000", Some(Red::Red))]
328 #[case("red", Some(Red::Red))]
329 #[case("#ff6347", Some(Red::Tomato))]
330 #[case("ff6347", Some(Red::Tomato))]
331 #[case("tomato", Some(Red::Tomato))]
332 #[case("#ff7f50", Some(Red::Coral))]
333 #[case("ff7f50", Some(Red::Coral))]
334 #[case("coral", Some(Red::Coral))]
335 #[case("#cd5c5c", Some(Red::IndianRed))]
336 #[case("cd5c5c", Some(Red::IndianRed))]
337 #[case("indianred", Some(Red::IndianRed))]
338 #[case("#f08080", Some(Red::LightCoral))]
339 #[case("f08080", Some(Red::LightCoral))]
340 #[case("lightcoral", Some(Red::LightCoral))]
341 #[case("#e9967a", Some(Red::DarkSalmon))]
342 #[case("e9967a", Some(Red::DarkSalmon))]
343 #[case("darksalmon", Some(Red::DarkSalmon))]
344 #[case("#fa8072", Some(Red::Salmon))]
345 #[case("fa8072", Some(Red::Salmon))]
346 #[case("salmon", Some(Red::Salmon))]
347 #[case("#ffa07a", Some(Red::LightSalmon))]
348 #[case("ffa07a", Some(Red::LightSalmon))]
349 #[case("lightsalmon", Some(Red::LightSalmon))]
350 #[case("#ff4500", Some(Red::OrangeRed))]
351 #[case("ff4500", Some(Red::OrangeRed))]
352 #[case("orangered", Some(Red::OrangeRed))]
353 #[case("#ff8c00", Some(Red::DarkOrange))]
354 #[case("ff8c00", Some(Red::DarkOrange))]
355 #[case("darkorange", Some(Red::DarkOrange))]
356 #[case("#ffa500", Some(Red::Orange))]
357 #[case("ffa500", Some(Red::Orange))]
358 #[case("orange", Some(Red::Orange))]
359 #[case("012345", None)]
360 fn test_name_colour(#[case] input: &str, #[case] expected: Option<Red>) {
361 assert_eq!(expected, Red::name_colour(input))
362 }
363}