1use std::fmt;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
40pub struct ColorTriplet {
41 pub red: u8,
42 pub green: u8,
43 pub blue: u8,
44}
45
46impl ColorTriplet {
47 pub const fn new(red: u8, green: u8, blue: u8) -> Self {
49 Self { red, green, blue }
50 }
51}
52
53impl fmt::Display for ColorTriplet {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write!(f, "#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
56 }
57}
58use std::hash::Hash;
59
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
67pub enum ColorSystem {
68 Standard = 1,
70 EightBit = 2,
72 TrueColor = 3,
74}
75
76impl fmt::Display for ColorSystem {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match self {
79 Self::Standard => write!(f, "standard"),
80 Self::EightBit => write!(f, "256"),
81 Self::TrueColor => write!(f, "truecolor"),
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92pub enum ColorType {
93 Default,
95 Standard,
97 EightBit,
99 TrueColor,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub struct Color {
119 pub(crate) color_type: ColorType,
120 pub(crate) number: Option<u8>,
123 pub(crate) triplet: Option<(u8, u8, u8)>,
125 pub(crate) name: Option<&'static str>,
127}
128
129impl Color {
130 pub const fn default() -> Self {
134 Self {
135 color_type: ColorType::Default,
136 number: None,
137 triplet: None,
138 name: None,
139 }
140 }
141
142 pub fn from_ansi_name(name: &str) -> Option<Self> {
144 let n = ANSI_NAME_MAP.get(name).copied()?;
145 Some(Self {
146 color_type: if n < 16 {
147 ColorType::Standard
148 } else {
149 ColorType::EightBit
150 },
151 number: Some(n),
152 triplet: None,
153 name: None,
154 })
155 }
156
157 pub fn from_8bit(n: u8) -> Self {
159 Self {
160 color_type: ColorType::EightBit,
161 number: Some(n),
162 triplet: None,
163 name: None,
164 }
165 }
166
167 pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
169 Self {
170 color_type: ColorType::TrueColor,
171 number: None,
172 triplet: Some((r, g, b)),
173 name: None,
174 }
175 }
176
177 pub fn from_hex(hex: &str) -> Result<Self, ColorParseError> {
179 let hex = hex.trim_start_matches('#');
180 if hex.len() != 6 {
181 return Err(ColorParseError::InvalidHex(hex.to_string()));
182 }
183 let r = u8::from_str_radix(&hex[0..2], 16)
184 .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
185 let g = u8::from_str_radix(&hex[2..4], 16)
186 .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
187 let b = u8::from_str_radix(&hex[4..6], 16)
188 .map_err(|_| ColorParseError::InvalidHex(hex.to_string()))?;
189 Ok(Self::from_rgb(r, g, b))
190 }
191
192 pub fn parse(s: &str) -> Result<Self, ColorParseError> {
194 let lower = s.to_lowercase();
195 if lower == "default" || lower.is_empty() {
196 return Ok(Self::default());
197 }
198 if let Some(c) = Self::from_ansi_name(&lower) {
199 return Ok(c);
200 }
201 if lower.starts_with('#') || lower.len() == 6 {
202 return Self::from_hex(&lower);
203 }
204 if lower.starts_with("color") {
206 if let Ok(n) = lower[5..].parse::<u8>() {
207 return Ok(Self::from_8bit(n));
208 }
209 }
210 Err(ColorParseError::UnknownName(lower))
211 }
212
213 pub fn is_default(&self) -> bool {
217 matches!(self.color_type, ColorType::Default)
218 }
219
220 pub fn from_triplet(triplet: &ColorTriplet) -> Self {
232 Self::from_rgb(triplet.red, triplet.green, triplet.blue)
233 }
234
235 pub fn is_system_defined(&self) -> bool {
240 matches!(self.color_type, ColorType::Standard | ColorType::EightBit)
241 }
242
243 pub fn get_ansi_codes(&self, background: bool) -> (Option<String>, Option<String>) {
258 if self.is_default() {
259 return (None, None);
260 }
261 let code = match self.color_type {
262 ColorType::Standard => {
263 let base: u8 = if background { 40 } else { 30 };
264 if let Some(n) = self.number {
265 let bright_offset: u8 = if n >= 8 { 60 } else { 0 };
266 Some((base + n + bright_offset).to_string())
267 } else {
268 None
269 }
270 }
271 ColorType::EightBit => {
272 let prefix = if background { "48;5;" } else { "38;5;" };
273 self.number.map(|n| format!("{prefix}{n}"))
274 }
275 ColorType::TrueColor => {
276 let prefix = if background { "48;2;" } else { "38;2;" };
277 self.triplet.map(|(r, g, b)| format!("{prefix}{r};{g};{b}"))
278 }
279 ColorType::Default => None,
280 };
281 if background {
282 (None, code)
283 } else {
284 (code, None)
285 }
286 }
287
288 pub fn name(&self) -> Option<&'static str> {
294 self.name
295 }
296
297 pub fn number(&self) -> Option<u8> {
302 self.number
303 }
304
305 pub fn triplet(&self) -> Option<(u8, u8, u8)> {
311 self.triplet
312 }
313
314 pub fn get_truecolor(&self, theme: &TerminalTheme) -> (u8, u8, u8) {
317 match self.color_type {
318 ColorType::TrueColor => self.triplet.unwrap(),
319 ColorType::Default => theme.foreground_color,
320 _ => {
321 if let Some(n) = self.number {
322 if let Some(&[r, g, b]) = EIGHT_BIT_PALETTE.get(n as usize) {
323 return (r, g, b);
324 }
325 }
326 theme.foreground_color
327 }
328 }
329 }
330
331 pub fn downgrade(&self, system: ColorSystem) -> Self {
333 match system {
334 ColorSystem::TrueColor => *self,
335 ColorSystem::EightBit => {
336 if matches!(self.color_type, ColorType::TrueColor) {
337 let (r, g, b) = self.triplet.unwrap();
338 let idx = rgb_to_8bit(r, g, b);
339 Self::from_8bit(idx)
340 } else {
341 *self
342 }
343 }
344 ColorSystem::Standard => {
345 if matches!(self.color_type, ColorType::TrueColor) {
346 let (r, g, b) = self.triplet.unwrap();
347 let idx = rgb_to_standard(r, g, b);
348 Self {
349 color_type: ColorType::Standard,
350 number: Some(idx),
351 triplet: None,
352 name: None,
353 }
354 } else if let Some(n) = self.number {
355 if n >= 16 {
356 let idx = n % 16;
357 Self {
358 color_type: ColorType::Standard,
359 number: Some(idx),
360 triplet: None,
361 name: None,
362 }
363 } else {
364 *self
365 }
366 } else {
367 *self
368 }
369 }
370 }
371 }
372}
373
374impl fmt::Display for Color {
375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 match self.color_type {
377 ColorType::Default => write!(f, "default"),
378 ColorType::Standard => write!(f, "{}", STANDARD_COLOR_NAMES[self.number.unwrap() as usize]),
379 ColorType::EightBit => write!(f, "color({})", self.number.unwrap()),
380 ColorType::TrueColor => {
381 let (r, g, b) = self.triplet.unwrap();
382 write!(f, "#{:02x}{:02x}{:02x}", r, g, b)
383 }
384 }
385 }
386}
387
388#[derive(Debug, Clone)]
394pub enum ColorParseError {
395 UnknownName(String),
397 InvalidHex(String),
399}
400
401impl fmt::Display for ColorParseError {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 match self {
404 Self::UnknownName(n) => write!(f, "unknown color name: {n}"),
405 Self::InvalidHex(h) => write!(f, "invalid hex color: {h}"),
406 }
407 }
408}
409
410impl std::error::Error for ColorParseError {}
411
412#[derive(Debug, Clone, Copy)]
418pub struct TerminalTheme {
419 pub foreground_color: (u8, u8, u8),
420 pub background_color: (u8, u8, u8),
421}
422
423impl Default for TerminalTheme {
424 fn default() -> Self {
425 Self {
426 foreground_color: (255, 255, 255),
427 background_color: (0, 0, 0),
428 }
429 }
430}
431
432pub static STANDARD_PALETTE: &[(u8, u8, u8)] = &[
438 (0, 0, 0), (128, 0, 0), (0, 128, 0), (128, 128, 0), (0, 0, 128), (128, 0, 128), (0, 128, 128), (192, 192, 192), (128, 128, 128), (255, 0, 0), (0, 255, 0), (255, 255, 0), (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255), ];
455
456pub static STANDARD_COLOR_NAMES: &[&str] = &[
458 "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white",
459 "bright_black", "bright_red", "bright_green", "bright_yellow",
460 "bright_blue", "bright_magenta", "bright_cyan", "bright_white",
461];
462
463pub static EIGHT_BIT_PALETTE: Lazy<[[u8; 3]; 256]> = Lazy::new(|| {
465 let mut table = [[0u8; 3]; 256];
466 let std: [[u8; 3]; 16] = [
467 [0, 0, 0],
468 [128, 0, 0],
469 [0, 128, 0],
470 [128, 128, 0],
471 [0, 0, 128],
472 [128, 0, 128],
473 [0, 128, 128],
474 [192, 192, 192],
475 [128, 128, 128],
476 [255, 0, 0],
477 [0, 255, 0],
478 [255, 255, 0],
479 [0, 0, 255],
480 [255, 0, 255],
481 [0, 255, 255],
482 [255, 255, 255],
483 ];
484 for i in 0..16 {
485 table[i] = std[i];
486 }
487 let levels = [0u8, 95, 135, 175, 215, 255];
488 for r in 0..6 {
489 for g in 0..6 {
490 for b in 0..6 {
491 let idx = 16 + 36 * r + 6 * g + b;
492 table[idx] = [levels[r], levels[g], levels[b]];
493 }
494 }
495 }
496 for gr in 0..24 {
497 let val = (gr * 10 + 8) as u8;
498 table[232 + gr] = [val, val, val];
499 }
500 table
501});
502
503pub fn rgb_to_8bit(r: u8, g: u8, b: u8) -> u8 {
509 let grey = ((r as u32 + g as u32 + b as u32) / 3) as u8;
511 if r == g && g == b {
512 if grey < 8 {
513 return 16; }
515 if grey > 248 {
516 return 231; }
518 return 232 + ((grey - 8) / 10) as u8;
519 }
520
521 let r6 = ((r as f64 / 255.0) * 5.0).round() as u8;
523 let g6 = ((g as f64 / 255.0) * 5.0).round() as u8;
524 let b6 = ((b as f64 / 255.0) * 5.0).round() as u8;
525 16 + 36 * r6 + 6 * g6 + b6
526}
527
528pub fn rgb_to_standard(_r: u8, _g: u8, _b: u8) -> u8 {
530 let mut best_idx = 0u8;
532 let mut best_dist = u32::MAX;
533 for (i, &(pr, pg, pb)) in STANDARD_PALETTE.iter().enumerate() {
534 let dr = _r as i32 - pr as i32;
535 let dg = _g as i32 - pg as i32;
536 let db = _b as i32 - pb as i32;
537 let dist = (dr * dr + dg * dg + db * db) as u32;
538 if dist < best_dist {
539 best_dist = dist;
540 best_idx = i as u8;
541 }
542 }
543 best_idx
544}
545
546pub fn blend_rgb(
548 color1: (u8, u8, u8),
549 color2: (u8, u8, u8),
550 cross_fade: f64,
551) -> (u8, u8, u8) {
552 let r = (color1.0 as f64 + (color2.0 as f64 - color1.0 as f64) * cross_fade) as u8;
553 let g = (color1.1 as f64 + (color2.1 as f64 - color1.1 as f64) * cross_fade) as u8;
554 let b = (color1.2 as f64 + (color2.2 as f64 - color1.2 as f64) * cross_fade) as u8;
555 (r, g, b)
556}
557
558pub fn blend_colors(
560 color1: &Color,
561 color2: &Color,
562 cross_fade: f64,
563 theme: &TerminalTheme,
564) -> Color {
565 let rgb1 = color1.get_truecolor(theme);
566 let rgb2 = color2.get_truecolor(theme);
567 let blended = blend_rgb(rgb1, rgb2, cross_fade);
568 Color::from_rgb(blended.0, blended.1, blended.2)
569}
570
571use once_cell::sync::Lazy;
579use std::collections::HashMap;
580
581static ANSI_NAME_MAP: Lazy<HashMap<&'static str, u8>> = Lazy::new(|| {
582 let mut m = HashMap::new();
583 m.insert("black", 0u8);
585 m.insert("red", 1u8);
586 m.insert("green", 2u8);
587 m.insert("yellow", 3u8);
588 m.insert("blue", 4u8);
589 m.insert("magenta", 5u8);
590 m.insert("cyan", 6u8);
591 m.insert("white", 7u8);
592 m.insert("bright_black", 8u8);
593 m.insert("grey", 8u8);
594 m.insert("gray", 8u8);
595 m.insert("bright_red", 9u8);
596 m.insert("bright_green", 10u8);
597 m.insert("bright_yellow", 11u8);
598 m.insert("bright_blue", 12u8);
599 m.insert("bright_magenta", 13u8);
600 m.insert("bright_cyan", 14u8);
601 m.insert("bright_white", 15u8);
602 m.insert("grey0", 16u8);
604 m.insert("gray0", 16u8);
605 m.insert("navy_blue", 17u8);
606 m.insert("dark_blue", 18u8);
607 m.insert("blue3", 20u8);
608 m.insert("blue1", 21u8);
609 m.insert("dark_green", 22u8);
610 m.insert("deep_sky_blue4", 25u8);
611 m.insert("dodger_blue3", 26u8);
612 m.insert("dodger_blue2", 27u8);
613 m.insert("green4", 28u8);
614 m.insert("spring_green4", 29u8);
615 m.insert("turquoise4", 30u8);
616 m.insert("deep_sky_blue3", 32u8);
617 m.insert("dodger_blue1", 33u8);
618 m.insert("dark_cyan", 36u8);
619 m.insert("light_sea_green", 37u8);
620 m.insert("deep_sky_blue2", 38u8);
621 m.insert("deep_sky_blue1", 39u8);
622 m.insert("green3", 40u8);
623 m.insert("spring_green3", 41u8);
624 m.insert("cyan3", 43u8);
625 m.insert("dark_turquoise", 44u8);
626 m.insert("turquoise2", 45u8);
627 m.insert("green1", 46u8);
628 m.insert("spring_green2", 47u8);
629 m.insert("spring_green1", 48u8);
630 m.insert("medium_spring_green", 49u8);
631 m.insert("cyan2", 50u8);
632 m.insert("cyan1", 51u8);
633 m.insert("purple4", 55u8);
634 m.insert("purple3", 56u8);
635 m.insert("blue_violet", 57u8);
636 m.insert("grey37", 59u8);
637 m.insert("gray37", 59u8);
638 m.insert("medium_purple4", 60u8);
639 m.insert("slate_blue3", 62u8);
640 m.insert("royal_blue1", 63u8);
641 m.insert("chartreuse4", 64u8);
642 m.insert("pale_turquoise4", 66u8);
643 m.insert("steel_blue", 67u8);
644 m.insert("steel_blue3", 68u8);
645 m.insert("cornflower_blue", 69u8);
646 m.insert("dark_sea_green4", 71u8);
647 m.insert("dark_sea_green", 71u8);
648 m.insert("cadet_blue", 73u8);
649 m.insert("sky_blue3", 74u8);
650 m.insert("chartreuse3", 76u8);
651 m.insert("sea_green3", 78u8);
652 m.insert("aquamarine3", 79u8);
653 m.insert("medium_turquoise", 80u8);
654 m.insert("steel_blue1", 81u8);
655 m.insert("sea_green2", 83u8);
656 m.insert("sea_green1", 85u8);
657 m.insert("dark_slate_gray2", 87u8);
658 m.insert("dark_red", 88u8);
659 m.insert("dark_magenta", 91u8);
660 m.insert("orange4", 94u8);
661 m.insert("light_pink4", 95u8);
662 m.insert("plum4", 96u8);
663 m.insert("medium_purple3", 98u8);
664 m.insert("slate_blue1", 99u8);
665 m.insert("wheat4", 101u8);
666 m.insert("grey53", 102u8);
667 m.insert("gray53", 102u8);
668 m.insert("light_slate_grey", 103u8);
669 m.insert("light_slate_gray", 103u8);
670 m.insert("medium_purple", 104u8);
671 m.insert("light_slate_blue", 105u8);
672 m.insert("yellow4", 106u8);
673 m.insert("dark_olive_green3", 110u8); m.insert("light_sky_blue3", 110u8);
675 m.insert("sky_blue2", 111u8);
676 m.insert("chartreuse2", 112u8);
677 m.insert("pale_green3", 114u8);
678 m.insert("dark_slate_gray3", 116u8);
679 m.insert("sky_blue1", 117u8);
680 m.insert("chartreuse1", 118u8);
681 m.insert("light_green", 120u8);
682 m.insert("aquamarine1", 122u8);
683 m.insert("dark_slate_gray1", 123u8);
684 m.insert("deep_pink4", 125u8);
685 m.insert("medium_violet_red", 126u8);
686 m.insert("dark_violet", 128u8);
687 m.insert("purple", 129u8);
688 m.insert("medium_orchid3", 133u8);
689 m.insert("medium_orchid", 134u8);
690 m.insert("dark_goldenrod", 136u8);
691 m.insert("rosy_brown", 138u8);
692 m.insert("grey63", 139u8);
693 m.insert("gray63", 139u8);
694 m.insert("medium_purple2", 140u8);
695 m.insert("medium_purple1", 141u8);
696 m.insert("dark_khaki", 143u8);
697 m.insert("navajo_white3", 144u8);
698 m.insert("grey69", 145u8);
699 m.insert("gray69", 145u8);
700 m.insert("light_steel_blue3", 146u8);
701 m.insert("light_steel_blue", 147u8);
702 m.insert("dark_olive_green2", 155u8);
703 m.insert("pale_green1", 156u8);
704 m.insert("dark_sea_green2", 157u8);
705 m.insert("pale_turquoise1", 159u8);
706 m.insert("red3", 160u8);
707 m.insert("deep_pink3", 162u8);
708 m.insert("magenta3", 164u8);
709 m.insert("dark_orange3", 166u8);
710 m.insert("indian_red", 167u8);
711 m.insert("hot_pink3", 168u8);
712 m.insert("hot_pink2", 169u8);
713 m.insert("orchid", 170u8);
714 m.insert("orange3", 172u8);
715 m.insert("light_salmon3", 173u8);
716 m.insert("light_pink3", 174u8);
717 m.insert("pink3", 175u8);
718 m.insert("plum3", 176u8);
719 m.insert("violet", 177u8);
720 m.insert("gold3", 178u8);
721 m.insert("light_goldenrod3", 179u8);
722 m.insert("tan", 180u8);
723 m.insert("misty_rose3", 181u8);
724 m.insert("thistle3", 182u8);
725 m.insert("plum2", 183u8);
726 m.insert("yellow3", 184u8);
727 m.insert("khaki3", 185u8);
728 m.insert("light_yellow3", 187u8);
729 m.insert("grey84", 188u8);
730 m.insert("gray84", 188u8);
731 m.insert("light_steel_blue1", 189u8);
732 m.insert("yellow2", 190u8);
733 m.insert("dark_olive_green1", 192u8);
734 m.insert("dark_sea_green1", 193u8);
735 m.insert("honeydew2", 194u8);
736 m.insert("light_cyan1", 195u8);
737 m.insert("red1", 196u8);
738 m.insert("deep_pink2", 197u8);
739 m.insert("deep_pink1", 199u8);
740 m.insert("magenta2", 200u8);
741 m.insert("magenta1", 201u8);
742 m.insert("orange_red1", 202u8);
743 m.insert("indian_red1", 204u8);
744 m.insert("hot_pink", 206u8);
745 m.insert("medium_orchid1", 207u8);
746 m.insert("dark_orange", 208u8);
747 m.insert("salmon1", 209u8);
748 m.insert("light_coral", 210u8);
749 m.insert("pale_violet_red1", 211u8);
750 m.insert("orchid2", 212u8);
751 m.insert("orchid1", 213u8);
752 m.insert("orange1", 214u8);
753 m.insert("sandy_brown", 215u8);
754 m.insert("light_salmon1", 216u8);
755 m.insert("light_pink1", 217u8);
756 m.insert("pink1", 218u8);
757 m.insert("plum1", 219u8);
758 m.insert("gold1", 220u8);
759 m.insert("light_goldenrod2", 222u8);
760 m.insert("navajo_white1", 223u8);
761 m.insert("misty_rose1", 224u8);
762 m.insert("thistle1", 225u8);
763 m.insert("yellow1", 226u8);
764 m.insert("light_goldenrod1", 227u8);
765 m.insert("khaki1", 228u8);
766 m.insert("wheat1", 229u8);
767 m.insert("cornsilk1", 230u8);
768 m.insert("grey100", 231u8);
769 m.insert("gray100", 231u8);
770 m.insert("grey3", 232u8);
772 m.insert("gray3", 232u8);
773 m.insert("grey7", 233u8);
774 m.insert("gray7", 233u8);
775 m.insert("grey11", 234u8);
776 m.insert("gray11", 234u8);
777 m.insert("grey15", 235u8);
778 m.insert("gray15", 235u8);
779 m.insert("grey19", 236u8);
780 m.insert("gray19", 236u8);
781 m.insert("grey23", 237u8);
782 m.insert("gray23", 237u8);
783 m.insert("grey27", 238u8);
784 m.insert("gray27", 238u8);
785 m.insert("grey30", 239u8);
786 m.insert("gray30", 239u8);
787 m.insert("grey35", 240u8);
788 m.insert("gray35", 240u8);
789 m.insert("grey39", 241u8);
790 m.insert("gray39", 241u8);
791 m.insert("grey42", 242u8);
792 m.insert("gray42", 242u8);
793 m.insert("grey46", 243u8);
794 m.insert("gray46", 243u8);
795 m.insert("grey50", 244u8);
796 m.insert("gray50", 244u8);
797 m.insert("grey54", 245u8);
798 m.insert("gray54", 245u8);
799 m.insert("grey58", 246u8);
800 m.insert("gray58", 246u8);
801 m.insert("grey62", 247u8);
802 m.insert("gray62", 247u8);
803 m.insert("grey66", 248u8);
804 m.insert("gray66", 248u8);
805 m.insert("dark_grey", 248u8);
806 m.insert("dark_gray", 248u8);
807 m.insert("grey70", 249u8);
808 m.insert("gray70", 249u8);
809 m.insert("grey74", 250u8);
810 m.insert("gray74", 250u8);
811 m.insert("light_grey", 250u8);
812 m.insert("light_gray", 250u8);
813 m.insert("grey78", 251u8);
814 m.insert("gray78", 251u8);
815 m.insert("grey82", 252u8);
816 m.insert("gray82", 252u8);
817 m.insert("grey85", 253u8);
818 m.insert("gray85", 253u8);
819 m.insert("grey89", 254u8);
820 m.insert("gray89", 254u8);
821 m.insert("grey93", 255u8);
822 m.insert("gray93", 255u8);
823 m
824});
825
826impl Color {
827 pub fn name_to_index(name: &str) -> Option<u8> {
829 ANSI_NAME_MAP.get(name).copied()
830 }
831}
832
833#[cfg(test)]
834mod tests {
835 use super::*;
836
837 #[test]
838 fn test_default_color() {
839 let c = Color::default();
840 assert!(c.is_default());
841 }
842
843 #[test]
844 fn test_parse_red() {
845 let c = Color::parse("red").unwrap();
846 assert_eq!(c.number, Some(1));
847 }
848
849 #[test]
850 fn test_parse_hex() {
851 let c = Color::parse("#ff0000").unwrap();
852 assert_eq!(c.triplet, Some((255, 0, 0)));
853 }
854
855 #[test]
856 fn test_rgb_to_8bit_black() {
857 assert_eq!(rgb_to_8bit(0, 0, 0), 16);
858 }
859
860 #[test]
861 fn test_from_triplet() {
862 let triplet = ColorTriplet::new(255, 128, 0);
863 let color = Color::from_triplet(&triplet);
864 assert_eq!(color.triplet(), Some((255, 128, 0)));
865 }
866
867 #[test]
868 fn test_is_system_defined() {
869 let default = Color::default();
870 assert!(!default.is_system_defined());
871
872 let red = Color::parse("red").unwrap();
873 assert!(red.is_system_defined());
874
875 let custom = Color::from_rgb(100, 200, 50);
876 assert!(!custom.is_system_defined());
877 }
878
879 #[test]
880 fn test_get_ansi_codes_standard() {
881 let red = Color::parse("red").unwrap();
882 let (fg, _bg) = red.get_ansi_codes(false);
883 assert!(fg.is_some());
884 assert!(fg.unwrap().contains("31"));
885 }
886
887 #[test]
888 fn test_get_ansi_codes_background() {
889 let blue = Color::parse("blue").unwrap();
890 let (_fg, bg) = blue.get_ansi_codes(true);
891 assert!(bg.is_some());
892 assert!(bg.unwrap().contains("44"));
893 }
894
895 #[test]
896 fn test_get_ansi_codes_truecolor() {
897 let c = Color::from_rgb(255, 128, 0);
898 let (fg, _bg) = c.get_ansi_codes(false);
899 assert!(fg.is_some());
900 assert!(fg.unwrap().contains("38;2;255;128;0"));
901 }
902
903 #[test]
904 fn test_get_ansi_codes_default() {
905 let c = Color::default();
906 let (fg, bg) = c.get_ansi_codes(false);
907 assert!(fg.is_none());
908 assert!(bg.is_none());
909 }
910
911 #[test]
912 fn test_color_name() {
913 let red = Color::parse("red").unwrap();
914 assert_eq!(red.number(), Some(1));
917 }
918
919 #[test]
920 fn test_color_number() {
921 let c = Color::from_8bit(42);
922 assert_eq!(c.number(), Some(42));
923 }
924
925 #[test]
926 fn test_color_triplet() {
927 let c = Color::from_rgb(10, 20, 30);
928 assert_eq!(c.triplet(), Some((10, 20, 30)));
929 let d = Color::default();
930 assert_eq!(d.triplet(), None);
931 }
932}