1#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ParseColorError {
9 input: alloc::string::String,
10}
11
12impl core::fmt::Display for ParseColorError {
13 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
14 write!(f, "invalid color string: '{}'", self.input)
15 }
16}
17
18#[cfg(feature = "std")]
19impl std::error::Error for ParseColorError {}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24pub enum Color {
25 Reset,
27 Black,
29 Red,
31 Green,
33 Yellow,
35 Blue,
37 Magenta,
39 Cyan,
41 White,
43 Gray,
45 LightRed,
47 LightGreen,
49 LightYellow,
51 LightBlue,
53 LightMagenta,
55 LightCyan,
57 LightGray,
59 Indexed(u8),
61 Rgb(u8, u8, u8),
63}
64
65impl Color {
66 #[inline]
76 #[must_use]
77 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
78 Self::Rgb(r, g, b)
79 }
80
81 #[inline]
83 #[must_use]
84 pub const fn indexed(index: u8) -> Self {
85 Self::Indexed(index)
86 }
87
88 pub fn parse(s: &str) -> Result<Self, ParseColorError> {
106 let s = s.trim().to_lowercase();
107
108 match s.as_str() {
110 "reset" => return Ok(Self::Reset),
111 "black" => return Ok(Self::Black),
112 "red" => return Ok(Self::Red),
113 "green" => return Ok(Self::Green),
114 "yellow" => return Ok(Self::Yellow),
115 "blue" => return Ok(Self::Blue),
116 "magenta" => return Ok(Self::Magenta),
117 "cyan" => return Ok(Self::Cyan),
118 "white" => return Ok(Self::White),
119 "gray" | "grey" => return Ok(Self::Gray),
120 "lightred" | "light_red" => return Ok(Self::LightRed),
121 "lightgreen" | "light_green" => return Ok(Self::LightGreen),
122 "lightyellow" | "light_yellow" => return Ok(Self::LightYellow),
123 "lightblue" | "light_blue" => return Ok(Self::LightBlue),
124 "lightmagenta" | "light_magenta" => return Ok(Self::LightMagenta),
125 "lightcyan" | "light_cyan" => return Ok(Self::LightCyan),
126 "lightgray" | "light_gray" | "lightgrey" | "light_grey" => return Ok(Self::LightGray),
127 _ => {}
128 }
129
130 if let Some(hex) = s.strip_prefix('#') {
132 return Self::parse_hex(hex).ok_or(ParseColorError { input: s });
133 }
134
135 if let Some(rgb) = s.strip_prefix("rgb(") {
137 if let Some(rgb) = rgb.strip_suffix(')') {
138 return Self::parse_rgb(rgb).ok_or(ParseColorError { input: s });
139 }
140 }
141
142 if let Ok(index) = s.parse::<u8>() {
144 return Ok(Self::Indexed(index));
145 }
146
147 Err(ParseColorError { input: s })
148 }
149
150 fn parse_hex(hex: &str) -> Option<Self> {
151 match hex.len() {
152 3 => {
153 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok()?;
155 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok()?;
156 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok()?;
157 Some(Self::Rgb(r, g, b))
158 }
159 6 => {
160 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
161 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
162 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
163 Some(Self::Rgb(r, g, b))
164 }
165 _ => None,
166 }
167 }
168
169 fn parse_rgb(rgb: &str) -> Option<Self> {
170 let parts: alloc::vec::Vec<&str> = rgb.split(',').map(str::trim).collect();
171 if parts.len() != 3 {
172 return None;
173 }
174 let r = parts[0].parse().ok()?;
175 let g = parts[1].parse().ok()?;
176 let b = parts[2].parse().ok()?;
177 Some(Self::Rgb(r, g, b))
178 }
179}
180
181impl Default for Color {
182 fn default() -> Self {
183 Self::Reset
184 }
185}
186
187impl core::str::FromStr for Color {
188 type Err = ParseColorError;
189
190 fn from_str(s: &str) -> Result<Self, Self::Err> {
191 Self::parse(s)
192 }
193}
194
195bitflags::bitflags! {
196 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
209 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
210 pub struct Modifier: u16 {
211 const BOLD = 0b0000_0000_0001;
213 const DIM = 0b0000_0000_0010;
215 const ITALIC = 0b0000_0000_0100;
217 const UNDERLINED = 0b0000_0000_1000;
219 const SLOW_BLINK = 0b0000_0001_0000;
221 const RAPID_BLINK = 0b0000_0010_0000;
223 const REVERSED = 0b0000_0100_0000;
225 const HIDDEN = 0b0000_1000_0000;
227 const CROSSED_OUT = 0b0001_0000_0000;
229 }
230}
231
232impl Default for Modifier {
233 fn default() -> Self {
234 Self::empty()
235 }
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
253#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
254pub struct Style {
255 pub fg: Option<Color>,
257 pub bg: Option<Color>,
259 #[cfg(feature = "underline-color")]
261 pub underline_color: Option<Color>,
262 pub add_modifier: Modifier,
264 pub sub_modifier: Modifier,
266}
267
268impl Style {
269 #[inline]
271 #[must_use]
272 pub const fn new() -> Self {
273 Self {
274 fg: None,
275 bg: None,
276 #[cfg(feature = "underline-color")]
277 underline_color: None,
278 add_modifier: Modifier::empty(),
279 sub_modifier: Modifier::empty(),
280 }
281 }
282
283 #[inline]
285 #[must_use]
286 pub const fn fg(mut self, color: Color) -> Self {
287 self.fg = Some(color);
288 self
289 }
290
291 #[inline]
293 #[must_use]
294 pub const fn bg(mut self, color: Color) -> Self {
295 self.bg = Some(color);
296 self
297 }
298
299 #[cfg(feature = "underline-color")]
301 #[inline]
302 #[must_use]
303 pub const fn underline_color(mut self, color: Color) -> Self {
304 self.underline_color = Some(color);
305 self
306 }
307
308 #[inline]
310 #[must_use]
311 pub const fn add_modifier(mut self, modifier: Modifier) -> Self {
312 self.add_modifier = self.add_modifier.union(modifier);
313 self
314 }
315
316 #[inline]
318 #[must_use]
319 pub const fn remove_modifier(mut self, modifier: Modifier) -> Self {
320 self.sub_modifier = self.sub_modifier.union(modifier);
321 self
322 }
323
324 #[inline]
326 #[must_use]
327 pub const fn reset() -> Self {
328 Self::new()
329 }
330
331 #[must_use]
346 pub const fn patch(mut self, other: Self) -> Self {
347 if other.fg.is_some() {
348 self.fg = other.fg;
349 }
350 if other.bg.is_some() {
351 self.bg = other.bg;
352 }
353 #[cfg(feature = "underline-color")]
354 if other.underline_color.is_some() {
355 self.underline_color = other.underline_color;
356 }
357 self.add_modifier = self.add_modifier.union(other.add_modifier);
358 self.sub_modifier = self.sub_modifier.union(other.sub_modifier);
359 self
360 }
361}
362
363pub trait Stylize: Sized {
375 fn style(self, style: Style) -> Self;
377
378 #[inline]
380 fn fg(self, color: Color) -> Self {
381 self.style(Style::default().fg(color))
382 }
383
384 #[inline]
386 fn bg(self, color: Color) -> Self {
387 self.style(Style::default().bg(color))
388 }
389
390 #[inline]
392 fn black(self) -> Self {
393 self.fg(Color::Black)
394 }
395
396 #[inline]
398 fn red(self) -> Self {
399 self.fg(Color::Red)
400 }
401
402 #[inline]
404 fn green(self) -> Self {
405 self.fg(Color::Green)
406 }
407
408 #[inline]
410 fn yellow(self) -> Self {
411 self.fg(Color::Yellow)
412 }
413
414 #[inline]
416 fn blue(self) -> Self {
417 self.fg(Color::Blue)
418 }
419
420 #[inline]
422 fn magenta(self) -> Self {
423 self.fg(Color::Magenta)
424 }
425
426 #[inline]
428 fn cyan(self) -> Self {
429 self.fg(Color::Cyan)
430 }
431
432 #[inline]
434 fn white(self) -> Self {
435 self.fg(Color::White)
436 }
437
438 #[inline]
440 fn gray(self) -> Self {
441 self.fg(Color::Gray)
442 }
443
444 #[inline]
446 fn bold(self) -> Self {
447 self.style(Style::default().add_modifier(Modifier::BOLD))
448 }
449
450 #[inline]
452 fn dim(self) -> Self {
453 self.style(Style::default().add_modifier(Modifier::DIM))
454 }
455
456 #[inline]
458 fn italic(self) -> Self {
459 self.style(Style::default().add_modifier(Modifier::ITALIC))
460 }
461
462 #[inline]
464 fn underlined(self) -> Self {
465 self.style(Style::default().add_modifier(Modifier::UNDERLINED))
466 }
467
468 #[inline]
470 fn slow_blink(self) -> Self {
471 self.style(Style::default().add_modifier(Modifier::SLOW_BLINK))
472 }
473
474 #[inline]
476 fn rapid_blink(self) -> Self {
477 self.style(Style::default().add_modifier(Modifier::RAPID_BLINK))
478 }
479
480 #[inline]
482 fn reversed(self) -> Self {
483 self.style(Style::default().add_modifier(Modifier::REVERSED))
484 }
485
486 #[inline]
488 fn hidden(self) -> Self {
489 self.style(Style::default().add_modifier(Modifier::HIDDEN))
490 }
491
492 #[inline]
494 fn crossed_out(self) -> Self {
495 self.style(Style::default().add_modifier(Modifier::CROSSED_OUT))
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn test_color_rgb() {
505 let color = Color::rgb(255, 128, 64);
506 assert_eq!(color, Color::Rgb(255, 128, 64));
507 }
508
509 #[test]
510 fn test_modifier_bitflags() {
511 let mods = Modifier::BOLD | Modifier::ITALIC;
512 assert!(mods.contains(Modifier::BOLD));
513 assert!(mods.contains(Modifier::ITALIC));
514 assert!(!mods.contains(Modifier::UNDERLINED));
515 }
516
517 #[test]
518 fn test_style_patch() {
519 let base = Style::default().fg(Color::Red).add_modifier(Modifier::BOLD);
520 let patch = Style::default()
521 .bg(Color::Blue)
522 .add_modifier(Modifier::ITALIC);
523 let merged = base.patch(patch);
524
525 assert_eq!(merged.fg, Some(Color::Red));
526 assert_eq!(merged.bg, Some(Color::Blue));
527 assert!(merged.add_modifier.contains(Modifier::BOLD));
528 assert!(merged.add_modifier.contains(Modifier::ITALIC));
529 }
530}