1use std::fmt;
4use std::hash::Hash;
5
6use serde::de::Visitor;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use thiserror::Error;
9
10#[derive(Default, Debug, PartialOrd, Eq, Ord, Clone, Copy)]
12pub enum Color {
13 #[default]
17 Reset,
18 Rgb(RgbColor),
20 Named(NamedColor),
22}
23
24#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
26pub struct RgbColor {
27 pub r: u8,
29 pub g: u8,
31 pub b: u8,
33}
34
35#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
37pub enum NamedColor {
38 Black = 0,
40 DarkBlue,
42 DarkGreen,
44 DarkAqua,
46 DarkRed,
48 DarkPurple,
50 Gold,
52 Gray,
54 DarkGray,
56 Blue,
58 Green,
60 Aqua,
62 Red,
64 LightPurple,
66 Yellow,
68 White,
70}
71
72#[derive(Debug, Error, PartialEq, PartialOrd, Clone, Copy, Hash, Eq, Ord)]
74#[error("invalid color name or hex code")]
75pub struct ColorError;
76
77impl Color {
78 pub const RESET: Self = Self::Reset;
79 pub const AQUA: Self = Self::Named(NamedColor::Aqua);
80 pub const BLACK: Self = Self::Named(NamedColor::Black);
81 pub const BLUE: Self = Self::Named(NamedColor::Blue);
82 pub const DARK_AQUA: Self = Self::Named(NamedColor::DarkAqua);
83 pub const DARK_BLUE: Self = Self::Named(NamedColor::DarkBlue);
84 pub const DARK_GRAY: Self = Self::Named(NamedColor::DarkGray);
85 pub const DARK_GREEN: Self = Self::Named(NamedColor::DarkGreen);
86 pub const DARK_PURPLE: Self = Self::Named(NamedColor::DarkPurple);
87 pub const DARK_RED: Self = Self::Named(NamedColor::DarkRed);
88 pub const GOLD: Self = Self::Named(NamedColor::Gold);
89 pub const GRAY: Self = Self::Named(NamedColor::Gray);
90 pub const GREEN: Self = Self::Named(NamedColor::Green);
91 pub const LIGHT_PURPLE: Self = Self::Named(NamedColor::LightPurple);
92 pub const RED: Self = Self::Named(NamedColor::Red);
93 pub const WHITE: Self = Self::Named(NamedColor::White);
94 pub const YELLOW: Self = Self::Named(NamedColor::Yellow);
95
96 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
98 Self::Rgb(RgbColor::new(r, g, b))
99 }
100}
101
102impl RgbColor {
103 pub const fn new(r: u8, g: u8, b: u8) -> Self {
105 Self { r, g, b }
106 }
107 pub fn to_named_lossy(self) -> NamedColor {
109 fn squared_distance(c1: RgbColor, c2: RgbColor) -> i32 {
111 (i32::from(c1.r) - i32::from(c2.r)).pow(2)
112 + (i32::from(c1.g) - i32::from(c2.g)).pow(2)
113 + (i32::from(c1.b) - i32::from(c2.b)).pow(2)
114 }
115
116 [
117 NamedColor::Aqua,
118 NamedColor::Black,
119 NamedColor::Blue,
120 NamedColor::DarkAqua,
121 NamedColor::DarkBlue,
122 NamedColor::DarkGray,
123 NamedColor::DarkGreen,
124 NamedColor::DarkPurple,
125 NamedColor::DarkRed,
126 NamedColor::Gold,
127 NamedColor::Gray,
128 NamedColor::Green,
129 NamedColor::LightPurple,
130 NamedColor::Red,
131 NamedColor::White,
132 NamedColor::Yellow,
133 ]
134 .into_iter()
135 .min_by_key(|&named| squared_distance(named.into(), self))
136 .unwrap()
137 }
138}
139
140impl NamedColor {
141 pub const fn hex_digit(self) -> char {
143 b"0123456789abcdef"[self as usize] as char
144 }
145 pub const fn name(self) -> &'static str {
147 [
148 "black",
149 "dark_blue",
150 "dark_green",
151 "dark_aqua",
152 "dark_red",
153 "dark_purple",
154 "gold",
155 "gray",
156 "dark_gray",
157 "blue",
158 "green",
159 "aqua",
160 "red",
161 "light_purple",
162 "yellow",
163 "white",
164 ][self as usize]
165 }
166}
167
168impl PartialEq for Color {
169 fn eq(&self, other: &Self) -> bool {
170 match (*self, *other) {
171 (Self::Reset, Self::Reset) => true,
172 (Self::Rgb(rgb1), Self::Rgb(rgb2)) => rgb1 == rgb2,
173 (Self::Named(normal1), Self::Named(normal2)) => normal1 == normal2,
174 (Self::Rgb(rgb), Self::Named(normal)) | (Self::Named(normal), Self::Rgb(rgb)) => {
175 rgb == RgbColor::from(normal)
176 }
177 (Self::Reset, _) | (_, Self::Reset) => false,
178 }
179 }
180}
181
182impl Hash for Color {
183 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
184 match self {
185 Self::Reset => state.write_u8(0),
186 Self::Rgb(rgb) => {
187 state.write_u8(1);
188 rgb.hash(state);
189 }
190 Self::Named(normal) => {
191 state.write_u8(1);
192 RgbColor::from(*normal).hash(state);
193 }
194 }
195 }
196}
197
198impl From<NamedColor> for RgbColor {
199 fn from(value: NamedColor) -> Self {
200 match value {
201 NamedColor::Aqua => Self::new(85, 255, 255),
202 NamedColor::Black => Self::new(0, 0, 0),
203 NamedColor::Blue => Self::new(85, 85, 255),
204 NamedColor::DarkAqua => Self::new(0, 170, 170),
205 NamedColor::DarkBlue => Self::new(0, 0, 170),
206 NamedColor::DarkGray => Self::new(85, 85, 85),
207 NamedColor::DarkGreen => Self::new(0, 170, 0),
208 NamedColor::DarkPurple => Self::new(170, 0, 170),
209 NamedColor::DarkRed => Self::new(170, 0, 0),
210 NamedColor::Gold => Self::new(255, 170, 0),
211 NamedColor::Gray => Self::new(170, 170, 170),
212 NamedColor::Green => Self::new(85, 255, 85),
213 NamedColor::LightPurple => Self::new(255, 85, 255),
214 NamedColor::Red => Self::new(255, 85, 85),
215 NamedColor::White => Self::new(255, 255, 255),
216 NamedColor::Yellow => Self::new(255, 255, 85),
217 }
218 }
219}
220
221impl From<RgbColor> for Color {
222 fn from(value: RgbColor) -> Self {
223 Self::Rgb(value)
224 }
225}
226
227impl From<NamedColor> for Color {
228 fn from(value: NamedColor) -> Self {
229 Self::Named(value)
230 }
231}
232
233impl TryFrom<&str> for Color {
234 type Error = ColorError;
235
236 fn try_from(value: &str) -> Result<Self, Self::Error> {
237 if value.starts_with('#') {
238 return Ok(Self::Rgb(RgbColor::try_from(value)?));
239 }
240
241 if value == "reset" {
242 return Ok(Self::Reset);
243 }
244
245 Ok(Self::Named(NamedColor::try_from(value)?))
246 }
247}
248
249impl TryFrom<&str> for NamedColor {
250 type Error = ColorError;
251
252 fn try_from(value: &str) -> Result<Self, Self::Error> {
253 match value {
254 "black" => Ok(NamedColor::Black),
255 "dark_blue" => Ok(NamedColor::DarkBlue),
256 "dark_green" => Ok(NamedColor::DarkGreen),
257 "dark_aqua" => Ok(NamedColor::DarkAqua),
258 "dark_red" => Ok(NamedColor::DarkRed),
259 "dark_purple" => Ok(NamedColor::DarkPurple),
260 "gold" => Ok(NamedColor::Gold),
261 "gray" => Ok(NamedColor::Gray),
262 "dark_gray" => Ok(NamedColor::DarkGray),
263 "blue" => Ok(NamedColor::Blue),
264 "green" => Ok(NamedColor::Green),
265 "aqua" => Ok(NamedColor::Aqua),
266 "red" => Ok(NamedColor::Red),
267 "light_purple" => Ok(NamedColor::LightPurple),
268 "yellow" => Ok(NamedColor::Yellow),
269 "white" => Ok(NamedColor::White),
270 _ => Err(ColorError),
271 }
272 }
273}
274
275impl TryFrom<&str> for RgbColor {
276 type Error = ColorError;
277
278 fn try_from(value: &str) -> Result<Self, Self::Error> {
279 let to_num = |d| match d {
280 b'0'..=b'9' => Ok(d - b'0'),
281 b'a'..=b'f' => Ok(d - b'a' + 0xa),
282 b'A'..=b'F' => Ok(d - b'A' + 0xa),
283 _ => Err(ColorError),
284 };
285
286 if let &[b'#', r0, r1, g0, g1, b0, b1] = value.as_bytes() {
287 Ok(RgbColor {
288 r: (to_num(r0)? << 4) | to_num(r1)?,
289 g: (to_num(g0)? << 4) | to_num(g1)?,
290 b: (to_num(b0)? << 4) | to_num(b1)?,
291 })
292 } else {
293 Err(ColorError)
294 }
295 }
296}
297
298impl Serialize for Color {
299 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
300 format!("{self}").serialize(serializer)
301 }
302}
303
304impl<'de> Deserialize<'de> for Color {
305 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
306 deserializer.deserialize_str(ColorVisitor)
307 }
308}
309
310struct ColorVisitor;
311
312impl Visitor<'_> for ColorVisitor {
313 type Value = Color;
314
315 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
316 write!(f, "a hex color (#rrggbb), a normal color or 'reset'")
317 }
318
319 fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
320 Color::try_from(s).map_err(|_| E::custom("invalid color"))
321 }
322}
323
324impl fmt::Display for Color {
325 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
326 match self {
327 Color::Reset => write!(f, "reset"),
328 Color::Rgb(rgb) => rgb.fmt(f),
329 Color::Named(normal) => normal.fmt(f),
330 }
331 }
332}
333
334impl fmt::Display for RgbColor {
335 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
336 write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
337 }
338}
339
340impl fmt::Display for NamedColor {
341 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
342 write!(f, "{}", self.name())
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn colors() {
352 assert_eq!(
353 Color::try_from("#aBcDeF"),
354 Ok(RgbColor::new(0xab, 0xcd, 0xef).into())
355 );
356 assert_eq!(
357 Color::try_from("#fFfFfF"),
358 Ok(RgbColor::new(255, 255, 255).into())
359 );
360 assert_eq!(Color::try_from("#000000"), Ok(NamedColor::Black.into()));
361 assert_eq!(Color::try_from("red"), Ok(NamedColor::Red.into()));
362 assert_eq!(Color::try_from("blue"), Ok(NamedColor::Blue.into()));
363 assert!(Color::try_from("#ffTf00").is_err());
364 assert!(Color::try_from("#ffš00").is_err());
365 assert!(Color::try_from("#00000000").is_err());
366 assert!(Color::try_from("#").is_err());
367 }
368}