Skip to main content

rich_rs/
color.rs

1//! Color types and color manipulation.
2//!
3//! This module provides comprehensive color support including:
4//! - Named colors (~250 ANSI color names)
5//! - 8-bit (256) colors
6//! - 24-bit RGB (truecolor)
7//! - Color parsing from various formats
8//! - Color downgrading to lower color systems
9//! - ANSI escape code generation
10
11use once_cell::sync::Lazy;
12use std::collections::HashMap;
13
14use crate::error::ParseError;
15
16// ============================================================================
17// ColorTriplet
18// ============================================================================
19
20/// RGB color triplet with red, green, and blue components (0-255).
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
22pub struct ColorTriplet {
23    /// Red component (0-255).
24    pub red: u8,
25    /// Green component (0-255).
26    pub green: u8,
27    /// Blue component (0-255).
28    pub blue: u8,
29}
30
31impl ColorTriplet {
32    /// Create a new color triplet.
33    pub const fn new(red: u8, green: u8, blue: u8) -> Self {
34        Self { red, green, blue }
35    }
36
37    /// Get the color as a CSS-style hex string (e.g., "#ff0000").
38    pub fn hex(&self) -> String {
39        format!("#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
40    }
41
42    /// Get the color as an RGB string (e.g., "rgb(255,0,0)").
43    pub fn rgb(&self) -> String {
44        format!("rgb({},{},{})", self.red, self.green, self.blue)
45    }
46
47    /// Get normalized color components as floats between 0.0 and 1.0.
48    pub fn normalized(&self) -> (f64, f64, f64) {
49        (
50            self.red as f64 / 255.0,
51            self.green as f64 / 255.0,
52            self.blue as f64 / 255.0,
53        )
54    }
55}
56
57impl From<(u8, u8, u8)> for ColorTriplet {
58    fn from((r, g, b): (u8, u8, u8)) -> Self {
59        Self::new(r, g, b)
60    }
61}
62
63// ============================================================================
64// ColorSystem
65// ============================================================================
66
67/// The color system supported by a terminal.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub enum ColorSystem {
70    /// 16 standard ANSI colors (colors 0-15).
71    Standard = 1,
72    /// 256 colors (8-bit palette).
73    EightBit = 2,
74    /// 24-bit RGB (truecolor).
75    TrueColor = 3,
76    /// Windows legacy console colors.
77    Windows = 4,
78}
79
80impl ColorSystem {
81    /// Get the number of colors in this system.
82    pub fn color_count(&self) -> usize {
83        match self {
84            ColorSystem::Standard => 16,
85            ColorSystem::EightBit => 256,
86            ColorSystem::TrueColor => 16_777_216,
87            ColorSystem::Windows => 16,
88        }
89    }
90
91    /// Get the capability rank of this color system for downgrade comparison.
92    ///
93    /// Windows is treated as equivalent to Standard (both are 16-color systems).
94    /// This ensures TrueColor and EightBit colors are properly downgraded to Windows.
95    fn rank(&self) -> u8 {
96        match self {
97            ColorSystem::Standard => 1,
98            ColorSystem::Windows => 1, // treat as 16-color system
99            ColorSystem::EightBit => 2,
100            ColorSystem::TrueColor => 3,
101        }
102    }
103}
104
105// ============================================================================
106// ColorType
107// ============================================================================
108
109/// The type of color stored in a Color struct.
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
111pub enum ColorType {
112    /// Default terminal color (no specific color set).
113    #[default]
114    Default,
115    /// Standard ANSI color (index 0-15).
116    Standard,
117    /// 8-bit color (index 0-255).
118    EightBit,
119    /// 24-bit RGB (truecolor).
120    TrueColor,
121    /// Windows legacy console color.
122    Windows,
123}
124
125impl ColorType {
126    /// Get the corresponding color system for this color type.
127    pub fn system(&self) -> ColorSystem {
128        match self {
129            ColorType::Default => ColorSystem::Standard,
130            ColorType::Standard => ColorSystem::Standard,
131            ColorType::EightBit => ColorSystem::EightBit,
132            ColorType::TrueColor => ColorSystem::TrueColor,
133            ColorType::Windows => ColorSystem::Windows,
134        }
135    }
136}
137
138// ============================================================================
139// Palette
140// ============================================================================
141
142/// A palette of colors for color matching.
143#[derive(Debug, Clone)]
144pub struct Palette {
145    colors: Vec<ColorTriplet>,
146}
147
148impl Palette {
149    /// Create a new palette from a slice of RGB tuples.
150    pub fn new(colors: &[(u8, u8, u8)]) -> Self {
151        Self {
152            colors: colors.iter().map(|&c| ColorTriplet::from(c)).collect(),
153        }
154    }
155
156    /// Get a color triplet by index.
157    pub fn get(&self, index: usize) -> Option<ColorTriplet> {
158        self.colors.get(index).copied()
159    }
160
161    /// Get the number of colors in the palette.
162    pub fn len(&self) -> usize {
163        self.colors.len()
164    }
165
166    /// Check if the palette is empty.
167    pub fn is_empty(&self) -> bool {
168        self.colors.is_empty()
169    }
170
171    /// Find the closest color index in the palette to the given triplet.
172    ///
173    /// Uses weighted Euclidean distance in RGB space, with weights that
174    /// account for human perception of color differences.
175    pub fn match_color(&self, triplet: ColorTriplet) -> usize {
176        let red1 = triplet.red as i32;
177        let green1 = triplet.green as i32;
178        let blue1 = triplet.blue as i32;
179
180        let mut min_distance = f64::MAX;
181        let mut min_index = 0;
182
183        for (index, color) in self.colors.iter().enumerate() {
184            let red2 = color.red as i32;
185            let green2 = color.green as i32;
186            let blue2 = color.blue as i32;
187
188            // Weighted Euclidean distance (matches Python Rich's algorithm)
189            let red_mean = (red1 + red2) / 2;
190            let red_diff = red1 - red2;
191            let green_diff = green1 - green2;
192            let blue_diff = blue1 - blue2;
193
194            let distance = (((512 + red_mean) * red_diff * red_diff) >> 8) as f64
195                + (4 * green_diff * green_diff) as f64
196                + (((767 - red_mean) * blue_diff * blue_diff) >> 8) as f64;
197            let distance = distance.sqrt();
198
199            if distance < min_distance {
200                min_distance = distance;
201                min_index = index;
202            }
203        }
204
205        min_index
206    }
207}
208
209impl std::ops::Index<usize> for Palette {
210    type Output = ColorTriplet;
211
212    fn index(&self, index: usize) -> &Self::Output {
213        &self.colors[index]
214    }
215}
216
217// ============================================================================
218// Color Palettes (Static Data)
219// ============================================================================
220
221/// Windows 10 console color palette.
222pub static WINDOWS_PALETTE: Lazy<Palette> = Lazy::new(|| {
223    Palette::new(&[
224        (12, 12, 12),
225        (197, 15, 31),
226        (19, 161, 14),
227        (193, 156, 0),
228        (0, 55, 218),
229        (136, 23, 152),
230        (58, 150, 221),
231        (204, 204, 204),
232        (118, 118, 118),
233        (231, 72, 86),
234        (22, 198, 12),
235        (249, 241, 165),
236        (59, 120, 255),
237        (180, 0, 158),
238        (97, 214, 214),
239        (242, 242, 242),
240    ])
241});
242
243/// Standard 16 ANSI color palette.
244pub static STANDARD_PALETTE: Lazy<Palette> = Lazy::new(|| {
245    Palette::new(&[
246        (0, 0, 0),
247        (170, 0, 0),
248        (0, 170, 0),
249        (170, 85, 0),
250        (0, 0, 170),
251        (170, 0, 170),
252        (0, 170, 170),
253        (170, 170, 170),
254        (85, 85, 85),
255        (255, 85, 85),
256        (85, 255, 85),
257        (255, 255, 85),
258        (85, 85, 255),
259        (255, 85, 255),
260        (85, 255, 255),
261        (255, 255, 255),
262    ])
263});
264
265/// 256-color (8-bit) palette.
266pub static EIGHT_BIT_PALETTE: Lazy<Palette> = Lazy::new(|| {
267    Palette::new(&[
268        // Standard colors (0-15)
269        (0, 0, 0),
270        (128, 0, 0),
271        (0, 128, 0),
272        (128, 128, 0),
273        (0, 0, 128),
274        (128, 0, 128),
275        (0, 128, 128),
276        (192, 192, 192),
277        (128, 128, 128),
278        (255, 0, 0),
279        (0, 255, 0),
280        (255, 255, 0),
281        (0, 0, 255),
282        (255, 0, 255),
283        (0, 255, 255),
284        (255, 255, 255),
285        // 216 colors (6x6x6 color cube, indices 16-231)
286        (0, 0, 0),
287        (0, 0, 95),
288        (0, 0, 135),
289        (0, 0, 175),
290        (0, 0, 215),
291        (0, 0, 255),
292        (0, 95, 0),
293        (0, 95, 95),
294        (0, 95, 135),
295        (0, 95, 175),
296        (0, 95, 215),
297        (0, 95, 255),
298        (0, 135, 0),
299        (0, 135, 95),
300        (0, 135, 135),
301        (0, 135, 175),
302        (0, 135, 215),
303        (0, 135, 255),
304        (0, 175, 0),
305        (0, 175, 95),
306        (0, 175, 135),
307        (0, 175, 175),
308        (0, 175, 215),
309        (0, 175, 255),
310        (0, 215, 0),
311        (0, 215, 95),
312        (0, 215, 135),
313        (0, 215, 175),
314        (0, 215, 215),
315        (0, 215, 255),
316        (0, 255, 0),
317        (0, 255, 95),
318        (0, 255, 135),
319        (0, 255, 175),
320        (0, 255, 215),
321        (0, 255, 255),
322        (95, 0, 0),
323        (95, 0, 95),
324        (95, 0, 135),
325        (95, 0, 175),
326        (95, 0, 215),
327        (95, 0, 255),
328        (95, 95, 0),
329        (95, 95, 95),
330        (95, 95, 135),
331        (95, 95, 175),
332        (95, 95, 215),
333        (95, 95, 255),
334        (95, 135, 0),
335        (95, 135, 95),
336        (95, 135, 135),
337        (95, 135, 175),
338        (95, 135, 215),
339        (95, 135, 255),
340        (95, 175, 0),
341        (95, 175, 95),
342        (95, 175, 135),
343        (95, 175, 175),
344        (95, 175, 215),
345        (95, 175, 255),
346        (95, 215, 0),
347        (95, 215, 95),
348        (95, 215, 135),
349        (95, 215, 175),
350        (95, 215, 215),
351        (95, 215, 255),
352        (95, 255, 0),
353        (95, 255, 95),
354        (95, 255, 135),
355        (95, 255, 175),
356        (95, 255, 215),
357        (95, 255, 255),
358        (135, 0, 0),
359        (135, 0, 95),
360        (135, 0, 135),
361        (135, 0, 175),
362        (135, 0, 215),
363        (135, 0, 255),
364        (135, 95, 0),
365        (135, 95, 95),
366        (135, 95, 135),
367        (135, 95, 175),
368        (135, 95, 215),
369        (135, 95, 255),
370        (135, 135, 0),
371        (135, 135, 95),
372        (135, 135, 135),
373        (135, 135, 175),
374        (135, 135, 215),
375        (135, 135, 255),
376        (135, 175, 0),
377        (135, 175, 95),
378        (135, 175, 135),
379        (135, 175, 175),
380        (135, 175, 215),
381        (135, 175, 255),
382        (135, 215, 0),
383        (135, 215, 95),
384        (135, 215, 135),
385        (135, 215, 175),
386        (135, 215, 215),
387        (135, 215, 255),
388        (135, 255, 0),
389        (135, 255, 95),
390        (135, 255, 135),
391        (135, 255, 175),
392        (135, 255, 215),
393        (135, 255, 255),
394        (175, 0, 0),
395        (175, 0, 95),
396        (175, 0, 135),
397        (175, 0, 175),
398        (175, 0, 215),
399        (175, 0, 255),
400        (175, 95, 0),
401        (175, 95, 95),
402        (175, 95, 135),
403        (175, 95, 175),
404        (175, 95, 215),
405        (175, 95, 255),
406        (175, 135, 0),
407        (175, 135, 95),
408        (175, 135, 135),
409        (175, 135, 175),
410        (175, 135, 215),
411        (175, 135, 255),
412        (175, 175, 0),
413        (175, 175, 95),
414        (175, 175, 135),
415        (175, 175, 175),
416        (175, 175, 215),
417        (175, 175, 255),
418        (175, 215, 0),
419        (175, 215, 95),
420        (175, 215, 135),
421        (175, 215, 175),
422        (175, 215, 215),
423        (175, 215, 255),
424        (175, 255, 0),
425        (175, 255, 95),
426        (175, 255, 135),
427        (175, 255, 175),
428        (175, 255, 215),
429        (175, 255, 255),
430        (215, 0, 0),
431        (215, 0, 95),
432        (215, 0, 135),
433        (215, 0, 175),
434        (215, 0, 215),
435        (215, 0, 255),
436        (215, 95, 0),
437        (215, 95, 95),
438        (215, 95, 135),
439        (215, 95, 175),
440        (215, 95, 215),
441        (215, 95, 255),
442        (215, 135, 0),
443        (215, 135, 95),
444        (215, 135, 135),
445        (215, 135, 175),
446        (215, 135, 215),
447        (215, 135, 255),
448        (215, 175, 0),
449        (215, 175, 95),
450        (215, 175, 135),
451        (215, 175, 175),
452        (215, 175, 215),
453        (215, 175, 255),
454        (215, 215, 0),
455        (215, 215, 95),
456        (215, 215, 135),
457        (215, 215, 175),
458        (215, 215, 215),
459        (215, 215, 255),
460        (215, 255, 0),
461        (215, 255, 95),
462        (215, 255, 135),
463        (215, 255, 175),
464        (215, 255, 215),
465        (215, 255, 255),
466        (255, 0, 0),
467        (255, 0, 95),
468        (255, 0, 135),
469        (255, 0, 175),
470        (255, 0, 215),
471        (255, 0, 255),
472        (255, 95, 0),
473        (255, 95, 95),
474        (255, 95, 135),
475        (255, 95, 175),
476        (255, 95, 215),
477        (255, 95, 255),
478        (255, 135, 0),
479        (255, 135, 95),
480        (255, 135, 135),
481        (255, 135, 175),
482        (255, 135, 215),
483        (255, 135, 255),
484        (255, 175, 0),
485        (255, 175, 95),
486        (255, 175, 135),
487        (255, 175, 175),
488        (255, 175, 215),
489        (255, 175, 255),
490        (255, 215, 0),
491        (255, 215, 95),
492        (255, 215, 135),
493        (255, 215, 175),
494        (255, 215, 215),
495        (255, 215, 255),
496        (255, 255, 0),
497        (255, 255, 95),
498        (255, 255, 135),
499        (255, 255, 175),
500        (255, 255, 215),
501        (255, 255, 255),
502        // Grayscale (24 shades, indices 232-255)
503        (8, 8, 8),
504        (18, 18, 18),
505        (28, 28, 28),
506        (38, 38, 38),
507        (48, 48, 48),
508        (58, 58, 58),
509        (68, 68, 68),
510        (78, 78, 78),
511        (88, 88, 88),
512        (98, 98, 98),
513        (108, 108, 108),
514        (118, 118, 118),
515        (128, 128, 128),
516        (138, 138, 138),
517        (148, 148, 148),
518        (158, 158, 158),
519        (168, 168, 168),
520        (178, 178, 178),
521        (188, 188, 188),
522        (198, 198, 198),
523        (208, 208, 208),
524        (218, 218, 218),
525        (228, 228, 228),
526        (238, 238, 238),
527    ])
528});
529
530// ============================================================================
531// ANSI Color Names
532// ============================================================================
533
534/// Map of ANSI color names to their color numbers.
535/// Includes all ~250 named colors from Python Rich.
536pub static ANSI_COLOR_NAMES: Lazy<HashMap<&'static str, u8>> = Lazy::new(|| {
537    let mut m = HashMap::new();
538    // Standard 16 colors
539    m.insert("black", 0);
540    m.insert("red", 1);
541    m.insert("green", 2);
542    m.insert("yellow", 3);
543    m.insert("blue", 4);
544    m.insert("magenta", 5);
545    m.insert("cyan", 6);
546    m.insert("white", 7);
547    m.insert("bright_black", 8);
548    m.insert("bright_red", 9);
549    m.insert("bright_green", 10);
550    m.insert("bright_yellow", 11);
551    m.insert("bright_blue", 12);
552    m.insert("bright_magenta", 13);
553    m.insert("bright_cyan", 14);
554    m.insert("bright_white", 15);
555    // Extended colors (16-255)
556    m.insert("grey0", 16);
557    m.insert("gray0", 16);
558    m.insert("navy_blue", 17);
559    m.insert("dark_blue", 18);
560    m.insert("blue3", 20);
561    m.insert("blue1", 21);
562    m.insert("dark_green", 22);
563    m.insert("deep_sky_blue4", 25);
564    m.insert("dodger_blue3", 26);
565    m.insert("dodger_blue2", 27);
566    m.insert("green4", 28);
567    m.insert("spring_green4", 29);
568    m.insert("turquoise4", 30);
569    m.insert("deep_sky_blue3", 32);
570    m.insert("dodger_blue1", 33);
571    m.insert("green3", 40);
572    m.insert("spring_green3", 41);
573    m.insert("dark_cyan", 36);
574    m.insert("light_sea_green", 37);
575    m.insert("deep_sky_blue2", 38);
576    m.insert("deep_sky_blue1", 39);
577    m.insert("spring_green2", 47);
578    m.insert("cyan3", 43);
579    m.insert("dark_turquoise", 44);
580    m.insert("turquoise2", 45);
581    m.insert("green1", 46);
582    m.insert("spring_green1", 48);
583    m.insert("medium_spring_green", 49);
584    m.insert("cyan2", 50);
585    m.insert("cyan1", 51);
586    m.insert("dark_red", 88);
587    m.insert("deep_pink4", 125);
588    m.insert("purple4", 55);
589    m.insert("purple3", 56);
590    m.insert("blue_violet", 57);
591    m.insert("orange4", 94);
592    m.insert("grey37", 59);
593    m.insert("gray37", 59);
594    m.insert("medium_purple4", 60);
595    m.insert("slate_blue3", 62);
596    m.insert("royal_blue1", 63);
597    m.insert("chartreuse4", 64);
598    m.insert("dark_sea_green4", 71);
599    m.insert("pale_turquoise4", 66);
600    m.insert("steel_blue", 67);
601    m.insert("steel_blue3", 68);
602    m.insert("cornflower_blue", 69);
603    m.insert("chartreuse3", 76);
604    m.insert("cadet_blue", 73);
605    m.insert("sky_blue3", 74);
606    m.insert("steel_blue1", 81);
607    m.insert("pale_green3", 114);
608    m.insert("sea_green3", 78);
609    m.insert("aquamarine3", 79);
610    m.insert("medium_turquoise", 80);
611    m.insert("chartreuse2", 112);
612    m.insert("sea_green2", 83);
613    m.insert("sea_green1", 85);
614    m.insert("aquamarine1", 122);
615    m.insert("dark_slate_gray2", 87);
616    m.insert("dark_magenta", 91);
617    m.insert("dark_violet", 128);
618    m.insert("purple", 129);
619    m.insert("light_pink4", 95);
620    m.insert("plum4", 96);
621    m.insert("medium_purple3", 98);
622    m.insert("slate_blue1", 99);
623    m.insert("yellow4", 106);
624    m.insert("wheat4", 101);
625    m.insert("grey53", 102);
626    m.insert("gray53", 102);
627    m.insert("light_slate_grey", 103);
628    m.insert("light_slate_gray", 103);
629    m.insert("medium_purple", 104);
630    m.insert("light_slate_blue", 105);
631    m.insert("dark_olive_green3", 149);
632    m.insert("dark_sea_green", 108);
633    m.insert("light_sky_blue3", 110);
634    m.insert("sky_blue2", 111);
635    m.insert("dark_sea_green3", 150);
636    m.insert("dark_slate_gray3", 116);
637    m.insert("sky_blue1", 117);
638    m.insert("chartreuse1", 118);
639    m.insert("light_green", 120);
640    m.insert("pale_green1", 156);
641    m.insert("dark_slate_gray1", 123);
642    m.insert("red3", 160);
643    m.insert("medium_violet_red", 126);
644    m.insert("magenta3", 164);
645    m.insert("dark_orange3", 166);
646    m.insert("indian_red", 167);
647    m.insert("hot_pink3", 168);
648    m.insert("medium_orchid3", 133);
649    m.insert("medium_orchid", 134);
650    m.insert("medium_purple2", 140);
651    m.insert("dark_goldenrod", 136);
652    m.insert("light_salmon3", 173);
653    m.insert("rosy_brown", 138);
654    m.insert("grey63", 139);
655    m.insert("gray63", 139);
656    m.insert("medium_purple1", 141);
657    m.insert("gold3", 178);
658    m.insert("dark_khaki", 143);
659    m.insert("navajo_white3", 144);
660    m.insert("grey69", 145);
661    m.insert("gray69", 145);
662    m.insert("light_steel_blue3", 146);
663    m.insert("light_steel_blue", 147);
664    m.insert("yellow3", 184);
665    m.insert("dark_sea_green2", 157);
666    m.insert("light_cyan3", 152);
667    m.insert("light_sky_blue1", 153);
668    m.insert("green_yellow", 154);
669    m.insert("dark_olive_green2", 155);
670    m.insert("dark_sea_green1", 193);
671    m.insert("pale_turquoise1", 159);
672    m.insert("deep_pink3", 162);
673    m.insert("magenta2", 200);
674    m.insert("hot_pink2", 169);
675    m.insert("orchid", 170);
676    m.insert("medium_orchid1", 207);
677    m.insert("orange3", 172);
678    m.insert("light_pink3", 174);
679    m.insert("pink3", 175);
680    m.insert("plum3", 176);
681    m.insert("violet", 177);
682    m.insert("light_goldenrod3", 179);
683    m.insert("tan", 180);
684    m.insert("misty_rose3", 181);
685    m.insert("thistle3", 182);
686    m.insert("plum2", 183);
687    m.insert("khaki3", 185);
688    m.insert("light_goldenrod2", 222);
689    m.insert("light_yellow3", 187);
690    m.insert("grey84", 188);
691    m.insert("gray84", 188);
692    m.insert("light_steel_blue1", 189);
693    m.insert("yellow2", 190);
694    m.insert("dark_olive_green1", 192);
695    m.insert("honeydew2", 194);
696    m.insert("light_cyan1", 195);
697    m.insert("red1", 196);
698    m.insert("deep_pink2", 197);
699    m.insert("deep_pink1", 199);
700    m.insert("magenta1", 201);
701    m.insert("orange_red1", 202);
702    m.insert("indian_red1", 204);
703    m.insert("hot_pink", 206);
704    m.insert("dark_orange", 208);
705    m.insert("salmon1", 209);
706    m.insert("light_coral", 210);
707    m.insert("pale_violet_red1", 211);
708    m.insert("orchid2", 212);
709    m.insert("orchid1", 213);
710    m.insert("orange1", 214);
711    m.insert("sandy_brown", 215);
712    m.insert("light_salmon1", 216);
713    m.insert("light_pink1", 217);
714    m.insert("pink1", 218);
715    m.insert("plum1", 219);
716    m.insert("gold1", 220);
717    m.insert("navajo_white1", 223);
718    m.insert("misty_rose1", 224);
719    m.insert("thistle1", 225);
720    m.insert("yellow1", 226);
721    m.insert("light_goldenrod1", 227);
722    m.insert("khaki1", 228);
723    m.insert("wheat1", 229);
724    m.insert("cornsilk1", 230);
725    m.insert("grey100", 231);
726    m.insert("gray100", 231);
727    m.insert("grey3", 232);
728    m.insert("gray3", 232);
729    m.insert("grey7", 233);
730    m.insert("gray7", 233);
731    m.insert("grey11", 234);
732    m.insert("gray11", 234);
733    m.insert("grey15", 235);
734    m.insert("gray15", 235);
735    m.insert("grey19", 236);
736    m.insert("gray19", 236);
737    m.insert("grey23", 237);
738    m.insert("gray23", 237);
739    m.insert("grey27", 238);
740    m.insert("gray27", 238);
741    m.insert("grey30", 239);
742    m.insert("gray30", 239);
743    m.insert("grey35", 240);
744    m.insert("gray35", 240);
745    m.insert("grey39", 241);
746    m.insert("gray39", 241);
747    m.insert("grey42", 242);
748    m.insert("gray42", 242);
749    m.insert("grey46", 243);
750    m.insert("gray46", 243);
751    m.insert("grey50", 244);
752    m.insert("gray50", 244);
753    m.insert("grey54", 245);
754    m.insert("gray54", 245);
755    m.insert("grey58", 246);
756    m.insert("gray58", 246);
757    m.insert("grey62", 247);
758    m.insert("gray62", 247);
759    m.insert("grey66", 248);
760    m.insert("gray66", 248);
761    m.insert("grey70", 249);
762    m.insert("gray70", 249);
763    m.insert("grey74", 250);
764    m.insert("gray74", 250);
765    m.insert("grey78", 251);
766    m.insert("gray78", 251);
767    m.insert("grey82", 252);
768    m.insert("gray82", 252);
769    m.insert("grey85", 253);
770    m.insert("gray85", 253);
771    m.insert("grey89", 254);
772    m.insert("gray89", 254);
773    m.insert("grey93", 255);
774    m.insert("gray93", 255);
775    // Aliases
776    m.insert("grey", 8);
777    m.insert("gray", 8);
778    m
779});
780
781// ============================================================================
782// Color
783// ============================================================================
784
785/// A terminal color definition.
786///
787/// Colors can be:
788/// - Default (terminal's default color)
789/// - Standard (16 ANSI colors, 0-15)
790/// - EightBit (256 colors, 0-255)
791/// - TrueColor (24-bit RGB)
792///
793/// # Examples
794///
795/// ```
796/// use rich_rs::Color;
797///
798/// // Parse from string
799/// let red = Color::parse("red").unwrap();
800/// let hex = Color::parse("#ff0000").unwrap();
801/// let rgb = Color::parse("rgb(255,0,0)").unwrap();
802/// let indexed = Color::parse("color(196)").unwrap();
803///
804/// // Create directly
805/// let custom = Color::from_rgb(255, 128, 0);
806/// ```
807#[derive(Debug, Clone, PartialEq, Eq, Hash)]
808pub struct Color {
809    /// The original name/representation of the color.
810    pub name: String,
811    /// The type of color.
812    pub color_type: ColorType,
813    /// The color number (for Standard and EightBit types).
814    pub number: Option<u8>,
815    /// The RGB triplet (for TrueColor type).
816    pub triplet: Option<ColorTriplet>,
817}
818
819impl Default for Color {
820    fn default() -> Self {
821        Self::default_color()
822    }
823}
824
825impl Color {
826    /// Create a new Color with the given parameters.
827    pub fn new(
828        name: impl Into<String>,
829        color_type: ColorType,
830        number: Option<u8>,
831        triplet: Option<ColorTriplet>,
832    ) -> Self {
833        Self {
834            name: name.into(),
835            color_type,
836            number,
837            triplet,
838        }
839    }
840
841    /// Create the default terminal color.
842    pub fn default_color() -> Self {
843        Self::new("default", ColorType::Default, None, None)
844    }
845
846    /// Create a color from an 8-bit ANSI number (0-255).
847    pub fn from_ansi(number: u8) -> Self {
848        let color_type = if number < 16 {
849            ColorType::Standard
850        } else {
851            ColorType::EightBit
852        };
853        Self::new(format!("color({})", number), color_type, Some(number), None)
854    }
855
856    /// Create a color from an RGB triplet.
857    pub fn from_triplet(triplet: ColorTriplet) -> Self {
858        Self::new(triplet.hex(), ColorType::TrueColor, None, Some(triplet))
859    }
860
861    /// Create a color from RGB values.
862    pub fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
863        Self::from_triplet(ColorTriplet::new(red, green, blue))
864    }
865
866    /// Get the color system for this color.
867    pub fn system(&self) -> ColorSystem {
868        self.color_type.system()
869    }
870
871    /// Check if this color is system-defined (Standard or Windows).
872    pub fn is_system_defined(&self) -> bool {
873        matches!(
874            self.color_type.system(),
875            ColorSystem::Standard | ColorSystem::Windows
876        )
877    }
878
879    /// Check if this is the default color.
880    pub fn is_default(&self) -> bool {
881        self.color_type == ColorType::Default
882    }
883
884    /// Get the RGB triplet for this color.
885    ///
886    /// For Standard and EightBit colors, looks up the color in the palette.
887    /// For Default colors, returns a typical terminal foreground/background.
888    pub fn get_truecolor(&self, foreground: bool) -> ColorTriplet {
889        match self.color_type {
890            ColorType::TrueColor => self.triplet.unwrap_or_default(),
891            ColorType::EightBit => {
892                let number = self.number.unwrap_or(0) as usize;
893                EIGHT_BIT_PALETTE.get(number).unwrap_or_default()
894            }
895            ColorType::Standard => {
896                let number = self.number.unwrap_or(0) as usize;
897                STANDARD_PALETTE.get(number).unwrap_or_default()
898            }
899            ColorType::Windows => {
900                let number = self.number.unwrap_or(0) as usize;
901                WINDOWS_PALETTE.get(number).unwrap_or_default()
902            }
903            ColorType::Default => {
904                // Default terminal colors (typical values)
905                if foreground {
906                    ColorTriplet::new(255, 255, 255) // White
907                } else {
908                    ColorTriplet::new(0, 0, 0) // Black
909                }
910            }
911        }
912    }
913
914    /// Parse a color from a string.
915    ///
916    /// Supported formats:
917    /// - Named colors: "red", "bright_blue", "grey50"
918    /// - Hex: "#ff0000" or "#f00"
919    /// - RGB: "rgb(255,0,0)"
920    /// - Indexed: "color(196)"
921    /// - Default: "default"
922    ///
923    /// # Examples
924    ///
925    /// ```
926    /// use rich_rs::Color;
927    ///
928    /// assert!(Color::parse("red").is_ok());
929    /// assert!(Color::parse("#ff0000").is_ok());
930    /// assert!(Color::parse("rgb(255,0,0)").is_ok());
931    /// assert!(Color::parse("color(196)").is_ok());
932    /// assert!(Color::parse("invalid").is_err());
933    /// ```
934    pub fn parse(color: &str) -> Result<Self, ParseError> {
935        let original_color = color;
936        let color = color.to_lowercase();
937        let color = color.trim();
938
939        // Default color
940        if color == "default" {
941            return Ok(Self::default_color());
942        }
943
944        // Named color
945        if let Some(&number) = ANSI_COLOR_NAMES.get(color) {
946            let color_type = if number < 16 {
947                ColorType::Standard
948            } else {
949                ColorType::EightBit
950            };
951            return Ok(Self::new(color.to_string(), color_type, Some(number), None));
952        }
953
954        // Hex color: #rrggbb or #rgb
955        if let Some(hex) = color.strip_prefix('#') {
956            return Self::parse_hex(hex, original_color);
957        }
958
959        // RGB: rgb(r,g,b)
960        if let Some(inner) = color.strip_prefix("rgb(").and_then(|s| s.strip_suffix(')')) {
961            return Self::parse_rgb_string(inner, original_color);
962        }
963
964        // Indexed: color(n)
965        if let Some(inner) = color
966            .strip_prefix("color(")
967            .and_then(|s| s.strip_suffix(')'))
968        {
969            return Self::parse_color_number(inner, original_color);
970        }
971
972        Err(ParseError::invalid_color(format!(
973            "{:?} is not a valid color",
974            original_color
975        )))
976    }
977
978    fn parse_hex(hex: &str, original: &str) -> Result<Self, ParseError> {
979        let make_err =
980            || ParseError::invalid_color(format!("{:?} is not a valid hex color", original));
981
982        match hex.len() {
983            3 => {
984                // Short form: #rgb -> #rrggbb
985                let chars: Vec<char> = hex.chars().collect();
986                let r = u8::from_str_radix(&chars[0].to_string(), 16).map_err(|_| make_err())? * 17;
987                let g = u8::from_str_radix(&chars[1].to_string(), 16).map_err(|_| make_err())? * 17;
988                let b = u8::from_str_radix(&chars[2].to_string(), 16).map_err(|_| make_err())? * 17;
989                let triplet = ColorTriplet::new(r, g, b);
990                Ok(Self::new(
991                    triplet.hex(),
992                    ColorType::TrueColor,
993                    None,
994                    Some(triplet),
995                ))
996            }
997            6 => {
998                let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| make_err())?;
999                let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| make_err())?;
1000                let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| make_err())?;
1001                let triplet = ColorTriplet::new(r, g, b);
1002                Ok(Self::new(
1003                    triplet.hex(),
1004                    ColorType::TrueColor,
1005                    None,
1006                    Some(triplet),
1007                ))
1008            }
1009            _ => Err(make_err()),
1010        }
1011    }
1012
1013    fn parse_rgb_string(inner: &str, original: &str) -> Result<Self, ParseError> {
1014        let components: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
1015        if components.len() != 3 {
1016            return Err(ParseError::invalid_color(format!(
1017                "expected three components in {:?}",
1018                original
1019            )));
1020        }
1021
1022        let parse_component = |s: &str| -> Result<u8, ParseError> {
1023            s.parse::<u16>()
1024                .map_err(|_| {
1025                    ParseError::invalid_color(format!("invalid RGB component in {:?}", original))
1026                })
1027                .and_then(|v| {
1028                    if v > 255 {
1029                        Err(ParseError::invalid_color(format!(
1030                            "color components must be <= 255 in {:?}",
1031                            original
1032                        )))
1033                    } else {
1034                        Ok(v as u8)
1035                    }
1036                })
1037        };
1038
1039        let r = parse_component(components[0])?;
1040        let g = parse_component(components[1])?;
1041        let b = parse_component(components[2])?;
1042
1043        let triplet = ColorTriplet::new(r, g, b);
1044        Ok(Self::new(
1045            format!("rgb({},{},{})", r, g, b),
1046            ColorType::TrueColor,
1047            None,
1048            Some(triplet),
1049        ))
1050    }
1051
1052    fn parse_color_number(inner: &str, original: &str) -> Result<Self, ParseError> {
1053        let number: u16 = inner.trim().parse().map_err(|_| {
1054            ParseError::invalid_color(format!("invalid color number in {:?}", original))
1055        })?;
1056
1057        if number > 255 {
1058            return Err(ParseError::invalid_color(format!(
1059                "color number must be <= 255 in {:?}",
1060                original
1061            )));
1062        }
1063
1064        let number = number as u8;
1065        let color_type = if number < 16 {
1066            ColorType::Standard
1067        } else {
1068            ColorType::EightBit
1069        };
1070
1071        Ok(Self::new(
1072            format!("color({})", number),
1073            color_type,
1074            Some(number),
1075            None,
1076        ))
1077    }
1078
1079    /// Get the ANSI escape codes for this color.
1080    ///
1081    /// Returns a vector of strings that should be joined with ";" to form
1082    /// the SGR (Select Graphic Rendition) parameter.
1083    ///
1084    /// # Arguments
1085    ///
1086    /// * `foreground` - If true, generate foreground color codes; otherwise background.
1087    ///
1088    /// # Examples
1089    ///
1090    /// ```
1091    /// use rich_rs::Color;
1092    ///
1093    /// let red = Color::parse("red").unwrap();
1094    /// assert_eq!(red.get_ansi_codes(true), vec!["31"]);
1095    /// assert_eq!(red.get_ansi_codes(false), vec!["41"]);
1096    ///
1097    /// let bright_red = Color::parse("bright_red").unwrap();
1098    /// assert_eq!(bright_red.get_ansi_codes(true), vec!["91"]);
1099    ///
1100    /// let color256 = Color::parse("color(196)").unwrap();
1101    /// assert_eq!(color256.get_ansi_codes(true), vec!["38", "5", "196"]);
1102    ///
1103    /// let rgb = Color::parse("#ff0000").unwrap();
1104    /// assert_eq!(rgb.get_ansi_codes(true), vec!["38", "2", "255", "0", "0"]);
1105    /// ```
1106    pub fn get_ansi_codes(&self, foreground: bool) -> Vec<String> {
1107        match self.color_type {
1108            ColorType::Default => {
1109                vec![if foreground { "39" } else { "49" }.to_string()]
1110            }
1111            ColorType::Standard | ColorType::Windows => {
1112                let number = self.number.unwrap_or(0);
1113                let (fore_base, back_base) = if number < 8 { (30, 40) } else { (90, 100) };
1114                let code = if foreground {
1115                    fore_base + (number % 8) as u16
1116                } else {
1117                    back_base + (number % 8) as u16
1118                };
1119                vec![code.to_string()]
1120            }
1121            ColorType::EightBit => {
1122                let number = self.number.unwrap_or(0);
1123                vec![
1124                    if foreground { "38" } else { "48" }.to_string(),
1125                    "5".to_string(),
1126                    number.to_string(),
1127                ]
1128            }
1129            ColorType::TrueColor => {
1130                let triplet = self.triplet.unwrap_or_default();
1131                vec![
1132                    if foreground { "38" } else { "48" }.to_string(),
1133                    "2".to_string(),
1134                    triplet.red.to_string(),
1135                    triplet.green.to_string(),
1136                    triplet.blue.to_string(),
1137                ]
1138            }
1139        }
1140    }
1141
1142    /// Downgrade this color to a lower color system.
1143    ///
1144    /// # Arguments
1145    ///
1146    /// * `system` - The target color system.
1147    ///
1148    /// # Returns
1149    ///
1150    /// A new Color that can be represented in the target system.
1151    ///
1152    /// # Examples
1153    ///
1154    /// ```
1155    /// use rich_rs::{Color, ColorSystem, ColorType};
1156    ///
1157    /// let rgb = Color::from_rgb(255, 100, 50);
1158    /// let downgraded = rgb.downgrade(ColorSystem::EightBit);
1159    /// assert_eq!(downgraded.color_type, ColorType::EightBit);
1160    /// ```
1161    pub fn downgrade(&self, system: ColorSystem) -> Self {
1162        // If default or already at target system, return self
1163        if self.color_type == ColorType::Default {
1164            return self.clone();
1165        }
1166
1167        let current_system = self.color_type.system();
1168
1169        // If current system is same or lower capability, no downgrade needed
1170        // Use rank() to compare capabilities (Windows and Standard both rank as 16-color systems)
1171        if current_system.rank() <= system.rank() {
1172            return self.clone();
1173        }
1174
1175        match system {
1176            ColorSystem::TrueColor => self.clone(),
1177
1178            ColorSystem::EightBit => {
1179                // Downgrade from TrueColor to EightBit
1180                if current_system == ColorSystem::TrueColor {
1181                    let triplet = self.triplet.unwrap_or_default();
1182                    let (_, l, s) = rgb_to_hls(triplet);
1183
1184                    // If saturation is low, use grayscale
1185                    if s < 0.15 {
1186                        let gray = (l * 25.0).round() as u8;
1187                        let color_number = if gray == 0 {
1188                            16
1189                        } else if gray == 25 {
1190                            231
1191                        } else {
1192                            231 + gray
1193                        };
1194                        return Self::new(
1195                            self.name.clone(),
1196                            ColorType::EightBit,
1197                            Some(color_number),
1198                            None,
1199                        );
1200                    }
1201
1202                    // Convert to 6x6x6 color cube
1203                    let r = triplet.red as f64;
1204                    let g = triplet.green as f64;
1205                    let b = triplet.blue as f64;
1206
1207                    let six_red = if r < 95.0 {
1208                        r / 95.0
1209                    } else {
1210                        1.0 + (r - 95.0) / 40.0
1211                    };
1212                    let six_green = if g < 95.0 {
1213                        g / 95.0
1214                    } else {
1215                        1.0 + (g - 95.0) / 40.0
1216                    };
1217                    let six_blue = if b < 95.0 {
1218                        b / 95.0
1219                    } else {
1220                        1.0 + (b - 95.0) / 40.0
1221                    };
1222
1223                    let color_number = 16
1224                        + 36 * six_red.round() as u8
1225                        + 6 * six_green.round() as u8
1226                        + six_blue.round() as u8;
1227
1228                    Self::new(
1229                        self.name.clone(),
1230                        ColorType::EightBit,
1231                        Some(color_number),
1232                        None,
1233                    )
1234                } else {
1235                    self.clone()
1236                }
1237            }
1238
1239            ColorSystem::Standard => {
1240                // Get the triplet (either directly or from palette)
1241                let triplet = match self.color_type {
1242                    ColorType::TrueColor => self.triplet.unwrap_or_default(),
1243                    ColorType::EightBit => {
1244                        let number = self.number.unwrap_or(0) as usize;
1245                        EIGHT_BIT_PALETTE.get(number).unwrap_or_default()
1246                    }
1247                    _ => return self.clone(),
1248                };
1249
1250                let color_number = STANDARD_PALETTE.match_color(triplet) as u8;
1251                Self::new(
1252                    self.name.clone(),
1253                    ColorType::Standard,
1254                    Some(color_number),
1255                    None,
1256                )
1257            }
1258
1259            ColorSystem::Windows => {
1260                // Get the triplet
1261                let triplet = match self.color_type {
1262                    ColorType::TrueColor => self.triplet.unwrap_or_default(),
1263                    ColorType::EightBit => {
1264                        let number = self.number.unwrap_or(0) as usize;
1265                        if number < 16 {
1266                            return Self::new(
1267                                self.name.clone(),
1268                                ColorType::Windows,
1269                                Some(number as u8),
1270                                None,
1271                            );
1272                        }
1273                        EIGHT_BIT_PALETTE.get(number).unwrap_or_default()
1274                    }
1275                    _ => return self.clone(),
1276                };
1277
1278                let color_number = WINDOWS_PALETTE.match_color(triplet) as u8;
1279                Self::new(
1280                    self.name.clone(),
1281                    ColorType::Windows,
1282                    Some(color_number),
1283                    None,
1284                )
1285            }
1286        }
1287    }
1288}
1289
1290/// Convert RGB to HLS (Hue, Lightness, Saturation).
1291///
1292/// Returns (hue, lightness, saturation) where all values are 0.0-1.0.
1293fn rgb_to_hls(triplet: ColorTriplet) -> (f64, f64, f64) {
1294    let (r, g, b) = triplet.normalized();
1295
1296    let max_c = r.max(g).max(b);
1297    let min_c = r.min(g).min(b);
1298    let l = (max_c + min_c) / 2.0;
1299
1300    if (max_c - min_c).abs() < f64::EPSILON {
1301        return (0.0, l, 0.0);
1302    }
1303
1304    let s = if l <= 0.5 {
1305        (max_c - min_c) / (max_c + min_c)
1306    } else {
1307        (max_c - min_c) / (2.0 - max_c - min_c)
1308    };
1309
1310    let rc = (max_c - r) / (max_c - min_c);
1311    let gc = (max_c - g) / (max_c - min_c);
1312    let bc = (max_c - b) / (max_c - min_c);
1313
1314    let h = if (r - max_c).abs() < f64::EPSILON {
1315        bc - gc
1316    } else if (g - max_c).abs() < f64::EPSILON {
1317        2.0 + rc - bc
1318    } else {
1319        4.0 + gc - rc
1320    };
1321
1322    let h = (h / 6.0).rem_euclid(1.0);
1323
1324    (h, l, s)
1325}
1326
1327/// Parse a six-character hex string into a ColorTriplet.
1328pub fn parse_rgb_hex(hex_color: &str) -> Result<ColorTriplet, ParseError> {
1329    if hex_color.len() != 6 {
1330        return Err(ParseError::invalid_color("hex color must be 6 characters"));
1331    }
1332    let r = u8::from_str_radix(&hex_color[0..2], 16)
1333        .map_err(|_| ParseError::invalid_color("invalid hex color"))?;
1334    let g = u8::from_str_radix(&hex_color[2..4], 16)
1335        .map_err(|_| ParseError::invalid_color("invalid hex color"))?;
1336    let b = u8::from_str_radix(&hex_color[4..6], 16)
1337        .map_err(|_| ParseError::invalid_color("invalid hex color"))?;
1338    Ok(ColorTriplet::new(r, g, b))
1339}
1340
1341/// Blend two colors together.
1342///
1343/// # Arguments
1344///
1345/// * `color1` - The first color.
1346/// * `color2` - The second color.
1347/// * `cross_fade` - The blend factor (0.0 = color1, 1.0 = color2).
1348pub fn blend_rgb(color1: ColorTriplet, color2: ColorTriplet, cross_fade: f64) -> ColorTriplet {
1349    let r1 = color1.red as f64;
1350    let g1 = color1.green as f64;
1351    let b1 = color1.blue as f64;
1352    let r2 = color2.red as f64;
1353    let g2 = color2.green as f64;
1354    let b2 = color2.blue as f64;
1355
1356    ColorTriplet::new(
1357        (r1 + (r2 - r1) * cross_fade) as u8,
1358        (g1 + (g2 - g1) * cross_fade) as u8,
1359        (b1 + (b2 - b1) * cross_fade) as u8,
1360    )
1361}
1362
1363// ============================================================================
1364// Backward Compatibility
1365// ============================================================================
1366
1367// For backward compatibility with existing code that uses the simple Color enum,
1368// we provide a simpler interface. The style.rs module currently uses `Option<Color>`
1369// with the old enum. We need to maintain compatibility.
1370
1371/// Simple color enum for backward compatibility.
1372///
1373/// This is kept for compatibility with existing style.rs code.
1374/// For new code, use the full `Color` struct via `Color::parse()`.
1375#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1376pub enum SimpleColor {
1377    /// Default terminal color (no color set).
1378    #[default]
1379    Default,
1380    /// Standard ANSI color (0-15).
1381    Standard(u8),
1382    /// 8-bit color (0-255).
1383    EightBit(u8),
1384    /// 24-bit RGB color.
1385    Rgb { r: u8, g: u8, b: u8 },
1386}
1387
1388impl SimpleColor {
1389    /// Create a new RGB color.
1390    pub fn rgb(r: u8, g: u8, b: u8) -> Self {
1391        SimpleColor::Rgb { r, g, b }
1392    }
1393
1394    /// Parse a color from a string (simple version).
1395    ///
1396    /// This is a simpler parser for backward compatibility.
1397    pub fn parse(s: &str) -> Option<Self> {
1398        let s = s.trim().to_lowercase();
1399
1400        // Hex color
1401        if let Some(hex) = s.strip_prefix('#') {
1402            return Self::parse_hex(hex);
1403        }
1404
1405        // RGB: rgb(r,g,b)
1406        if let Some(inner) = s.strip_prefix("rgb(").and_then(|s| s.strip_suffix(')')) {
1407            let parts: Vec<&str> = inner.split(',').map(|p| p.trim()).collect();
1408            if parts.len() == 3 {
1409                let r = parts[0].parse().ok()?;
1410                let g = parts[1].parse().ok()?;
1411                let b = parts[2].parse().ok()?;
1412                return Some(SimpleColor::Rgb { r, g, b });
1413            }
1414            return None;
1415        }
1416
1417        // color(n)
1418        if let Some(inner) = s.strip_prefix("color(").and_then(|s| s.strip_suffix(')')) {
1419            let n: u8 = inner.trim().parse().ok()?;
1420            return Some(if n < 16 {
1421                SimpleColor::Standard(n)
1422            } else {
1423                SimpleColor::EightBit(n)
1424            });
1425        }
1426
1427        // Named colors
1428        if let Some(&number) = ANSI_COLOR_NAMES.get(s.as_str()) {
1429            return Some(if number < 16 {
1430                SimpleColor::Standard(number)
1431            } else {
1432                SimpleColor::EightBit(number)
1433            });
1434        }
1435
1436        if s == "default" {
1437            return Some(SimpleColor::Default);
1438        }
1439
1440        None
1441    }
1442
1443    fn parse_hex(hex: &str) -> Option<Self> {
1444        let hex = hex.trim_start_matches('#');
1445        match hex.len() {
1446            3 => {
1447                let r = u8::from_str_radix(&hex[0..1], 16).ok()? * 17;
1448                let g = u8::from_str_radix(&hex[1..2], 16).ok()? * 17;
1449                let b = u8::from_str_radix(&hex[2..3], 16).ok()? * 17;
1450                Some(SimpleColor::Rgb { r, g, b })
1451            }
1452            6 => {
1453                let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
1454                let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
1455                let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
1456                Some(SimpleColor::Rgb { r, g, b })
1457            }
1458            _ => None,
1459        }
1460    }
1461
1462    /// Convert to the full Color struct.
1463    pub fn to_color(&self) -> Color {
1464        match self {
1465            SimpleColor::Default => Color::default_color(),
1466            SimpleColor::Standard(n) => Color::from_ansi(*n),
1467            SimpleColor::EightBit(n) => Color::from_ansi(*n),
1468            SimpleColor::Rgb { r, g, b } => Color::from_rgb(*r, *g, *b),
1469        }
1470    }
1471
1472    /// Get ANSI codes for this color.
1473    pub fn get_ansi_codes(&self, foreground: bool) -> Vec<String> {
1474        self.to_color().get_ansi_codes(foreground)
1475    }
1476
1477    /// Downgrade this color to a lower color system.
1478    ///
1479    /// # Arguments
1480    ///
1481    /// * `system` - The target color system.
1482    ///
1483    /// # Returns
1484    ///
1485    /// A new SimpleColor that can be represented in the target system.
1486    pub fn downgrade(&self, system: ColorSystem) -> Self {
1487        self.to_color().downgrade(system).into()
1488    }
1489
1490    /// Get the hex color string for this color.
1491    ///
1492    /// For RGB colors, returns the hex string directly.
1493    /// For indexed colors, looks up the RGB value in the palette.
1494    pub fn get_hex(&self) -> String {
1495        match self {
1496            SimpleColor::Rgb { r, g, b } => format!("#{:02x}{:02x}{:02x}", r, g, b),
1497            SimpleColor::Default => "#ffffff".to_string(), // Default foreground
1498            SimpleColor::Standard(n) => {
1499                let triplet = STANDARD_PALETTE.get(*n as usize).unwrap_or_default();
1500                triplet.hex()
1501            }
1502            SimpleColor::EightBit(n) => {
1503                let triplet = EIGHT_BIT_PALETTE.get(*n as usize).unwrap_or_default();
1504                triplet.hex()
1505            }
1506        }
1507    }
1508}
1509
1510impl From<SimpleColor> for Color {
1511    fn from(simple: SimpleColor) -> Self {
1512        simple.to_color()
1513    }
1514}
1515
1516impl From<Color> for SimpleColor {
1517    fn from(color: Color) -> Self {
1518        match color.color_type {
1519            ColorType::Default => SimpleColor::Default,
1520            ColorType::Standard | ColorType::Windows => {
1521                SimpleColor::Standard(color.number.unwrap_or(0))
1522            }
1523            ColorType::EightBit => SimpleColor::EightBit(color.number.unwrap_or(0)),
1524            ColorType::TrueColor => {
1525                let t = color.triplet.unwrap_or_default();
1526                SimpleColor::Rgb {
1527                    r: t.red,
1528                    g: t.green,
1529                    b: t.blue,
1530                }
1531            }
1532        }
1533    }
1534}
1535
1536// ============================================================================
1537// Tests
1538// ============================================================================
1539
1540#[cfg(test)]
1541mod tests {
1542    use super::*;
1543
1544    // --- ColorTriplet Tests ---
1545
1546    #[test]
1547    fn test_color_triplet_new() {
1548        let t = ColorTriplet::new(255, 128, 64);
1549        assert_eq!(t.red, 255);
1550        assert_eq!(t.green, 128);
1551        assert_eq!(t.blue, 64);
1552    }
1553
1554    #[test]
1555    fn test_color_triplet_hex() {
1556        assert_eq!(ColorTriplet::new(255, 0, 0).hex(), "#ff0000");
1557        assert_eq!(ColorTriplet::new(0, 255, 0).hex(), "#00ff00");
1558        assert_eq!(ColorTriplet::new(0, 0, 255).hex(), "#0000ff");
1559        assert_eq!(ColorTriplet::new(18, 52, 86).hex(), "#123456");
1560    }
1561
1562    #[test]
1563    fn test_color_triplet_rgb() {
1564        assert_eq!(ColorTriplet::new(255, 0, 0).rgb(), "rgb(255,0,0)");
1565        assert_eq!(ColorTriplet::new(128, 64, 32).rgb(), "rgb(128,64,32)");
1566    }
1567
1568    #[test]
1569    fn test_color_triplet_normalized() {
1570        let (r, g, b) = ColorTriplet::new(255, 127, 0).normalized();
1571        assert!((r - 1.0).abs() < 0.001);
1572        assert!((g - 0.498).abs() < 0.01);
1573        assert!((b - 0.0).abs() < 0.001);
1574    }
1575
1576    // --- Palette Tests ---
1577
1578    #[test]
1579    fn test_palette_match() {
1580        let palette = Palette::new(&[(0, 0, 0), (255, 255, 255), (255, 0, 0)]);
1581
1582        // Exact match
1583        assert_eq!(palette.match_color(ColorTriplet::new(0, 0, 0)), 0);
1584        assert_eq!(palette.match_color(ColorTriplet::new(255, 255, 255)), 1);
1585        assert_eq!(palette.match_color(ColorTriplet::new(255, 0, 0)), 2);
1586
1587        // Close match - dark gray should match black
1588        assert_eq!(palette.match_color(ColorTriplet::new(10, 10, 10)), 0);
1589
1590        // Light gray should match white
1591        assert_eq!(palette.match_color(ColorTriplet::new(240, 240, 240)), 1);
1592    }
1593
1594    // --- Color Parsing Tests ---
1595
1596    #[test]
1597    fn test_parse_named() {
1598        let red = Color::parse("red").unwrap();
1599        assert_eq!(red.color_type, ColorType::Standard);
1600        assert_eq!(red.number, Some(1));
1601
1602        let bright_blue = Color::parse("bright_blue").unwrap();
1603        assert_eq!(bright_blue.color_type, ColorType::Standard);
1604        assert_eq!(bright_blue.number, Some(12));
1605
1606        // Case insensitive
1607        let blue = Color::parse("BLUE").unwrap();
1608        assert_eq!(blue.number, Some(4));
1609
1610        // Extended color
1611        let orchid = Color::parse("orchid").unwrap();
1612        assert_eq!(orchid.color_type, ColorType::EightBit);
1613        assert_eq!(orchid.number, Some(170));
1614    }
1615
1616    #[test]
1617    fn test_parse_hex() {
1618        let red = Color::parse("#ff0000").unwrap();
1619        assert_eq!(red.color_type, ColorType::TrueColor);
1620        assert_eq!(red.triplet, Some(ColorTriplet::new(255, 0, 0)));
1621
1622        // Short form
1623        let white = Color::parse("#fff").unwrap();
1624        assert_eq!(white.triplet, Some(ColorTriplet::new(255, 255, 255)));
1625
1626        // Mixed case
1627        let color = Color::parse("#AbCdEf").unwrap();
1628        assert_eq!(color.triplet, Some(ColorTriplet::new(171, 205, 239)));
1629    }
1630
1631    #[test]
1632    fn test_parse_rgb() {
1633        let red = Color::parse("rgb(255,0,0)").unwrap();
1634        assert_eq!(red.color_type, ColorType::TrueColor);
1635        assert_eq!(red.triplet, Some(ColorTriplet::new(255, 0, 0)));
1636
1637        // With spaces
1638        let green = Color::parse("rgb(0, 255, 0)").unwrap();
1639        assert_eq!(green.triplet, Some(ColorTriplet::new(0, 255, 0)));
1640    }
1641
1642    #[test]
1643    fn test_parse_color_number() {
1644        let standard = Color::parse("color(1)").unwrap();
1645        assert_eq!(standard.color_type, ColorType::Standard);
1646        assert_eq!(standard.number, Some(1));
1647
1648        let extended = Color::parse("color(196)").unwrap();
1649        assert_eq!(extended.color_type, ColorType::EightBit);
1650        assert_eq!(extended.number, Some(196));
1651    }
1652
1653    #[test]
1654    fn test_parse_default() {
1655        let default = Color::parse("default").unwrap();
1656        assert_eq!(default.color_type, ColorType::Default);
1657    }
1658
1659    #[test]
1660    fn test_parse_invalid() {
1661        assert!(Color::parse("not_a_color").is_err());
1662        assert!(Color::parse("#gggggg").is_err());
1663        assert!(Color::parse("rgb(256,0,0)").is_err());
1664        assert!(Color::parse("color(256)").is_err());
1665    }
1666
1667    // --- ANSI Code Tests ---
1668
1669    #[test]
1670    fn test_ansi_codes_standard() {
1671        let black = Color::parse("black").unwrap();
1672        assert_eq!(black.get_ansi_codes(true), vec!["30"]);
1673        assert_eq!(black.get_ansi_codes(false), vec!["40"]);
1674
1675        let red = Color::parse("red").unwrap();
1676        assert_eq!(red.get_ansi_codes(true), vec!["31"]);
1677        assert_eq!(red.get_ansi_codes(false), vec!["41"]);
1678
1679        let bright_red = Color::parse("bright_red").unwrap();
1680        assert_eq!(bright_red.get_ansi_codes(true), vec!["91"]);
1681        assert_eq!(bright_red.get_ansi_codes(false), vec!["101"]);
1682    }
1683
1684    #[test]
1685    fn test_ansi_codes_eight_bit() {
1686        let color = Color::parse("color(196)").unwrap();
1687        assert_eq!(color.get_ansi_codes(true), vec!["38", "5", "196"]);
1688        assert_eq!(color.get_ansi_codes(false), vec!["48", "5", "196"]);
1689    }
1690
1691    #[test]
1692    fn test_ansi_codes_truecolor() {
1693        let color = Color::parse("#ff8000").unwrap();
1694        assert_eq!(
1695            color.get_ansi_codes(true),
1696            vec!["38", "2", "255", "128", "0"]
1697        );
1698        assert_eq!(
1699            color.get_ansi_codes(false),
1700            vec!["48", "2", "255", "128", "0"]
1701        );
1702    }
1703
1704    #[test]
1705    fn test_ansi_codes_default() {
1706        let default = Color::default_color();
1707        assert_eq!(default.get_ansi_codes(true), vec!["39"]);
1708        assert_eq!(default.get_ansi_codes(false), vec!["49"]);
1709    }
1710
1711    // --- Downgrade Tests ---
1712
1713    #[test]
1714    fn test_downgrade_truecolor_to_eight_bit() {
1715        let rgb = Color::from_rgb(255, 0, 0);
1716        let downgraded = rgb.downgrade(ColorSystem::EightBit);
1717        assert_eq!(downgraded.color_type, ColorType::EightBit);
1718        // Should map to a red in the 256 palette
1719        assert!(downgraded.number.is_some());
1720    }
1721
1722    #[test]
1723    fn test_downgrade_to_standard() {
1724        let rgb = Color::from_rgb(255, 0, 0);
1725        let downgraded = rgb.downgrade(ColorSystem::Standard);
1726        assert_eq!(downgraded.color_type, ColorType::Standard);
1727        // Red should map to standard red (1 or 9)
1728        let num = downgraded.number.unwrap();
1729        assert!(num == 1 || num == 9);
1730    }
1731
1732    #[test]
1733    fn test_downgrade_grayscale() {
1734        // A gray color should use the grayscale ramp
1735        let gray = Color::from_rgb(128, 128, 128);
1736        let downgraded = gray.downgrade(ColorSystem::EightBit);
1737        assert_eq!(downgraded.color_type, ColorType::EightBit);
1738        // Should be in the grayscale range (232-255) or close
1739        let num = downgraded.number.unwrap();
1740        assert!(num >= 232 || num == 16 || num == 231);
1741    }
1742
1743    #[test]
1744    fn test_downgrade_no_change() {
1745        // Default doesn't change
1746        let default = Color::default_color();
1747        let downgraded = default.downgrade(ColorSystem::Standard);
1748        assert_eq!(downgraded.color_type, ColorType::Default);
1749
1750        // Standard to standard doesn't change
1751        let standard = Color::parse("red").unwrap();
1752        let downgraded = standard.downgrade(ColorSystem::Standard);
1753        assert_eq!(downgraded.number, Some(1));
1754    }
1755
1756    #[test]
1757    fn test_downgrade_to_windows() {
1758        // TrueColor -> Windows should work (this was the bug: Windows rank=4 > TrueColor rank=3
1759        // meant TrueColor was never downgraded to Windows)
1760        let rgb = Color::from_rgb(255, 0, 0);
1761        let downgraded = rgb.downgrade(ColorSystem::Windows);
1762        assert_eq!(downgraded.color_type, ColorType::Windows);
1763        // Red should map to a red in the Windows palette
1764        assert!(downgraded.number.is_some());
1765
1766        // EightBit -> Windows should also work
1767        let eight_bit = Color::from_ansi(196); // Red in 256 palette
1768        let downgraded = eight_bit.downgrade(ColorSystem::Windows);
1769        assert_eq!(downgraded.color_type, ColorType::Windows);
1770        assert!(downgraded.number.is_some());
1771    }
1772
1773    // --- SimpleColor Backward Compatibility Tests ---
1774
1775    #[test]
1776    fn test_simple_color_parse_named() {
1777        assert_eq!(SimpleColor::parse("red"), Some(SimpleColor::Standard(1)));
1778        assert_eq!(SimpleColor::parse("BLUE"), Some(SimpleColor::Standard(4)));
1779    }
1780
1781    #[test]
1782    fn test_simple_color_parse_hex() {
1783        assert_eq!(
1784            SimpleColor::parse("#ff0000"),
1785            Some(SimpleColor::Rgb { r: 255, g: 0, b: 0 })
1786        );
1787        assert_eq!(
1788            SimpleColor::parse("#f00"),
1789            Some(SimpleColor::Rgb { r: 255, g: 0, b: 0 })
1790        );
1791    }
1792
1793    #[test]
1794    fn test_simple_color_to_color() {
1795        let simple = SimpleColor::Standard(1);
1796        let color = simple.to_color();
1797        assert_eq!(color.color_type, ColorType::Standard);
1798        assert_eq!(color.number, Some(1));
1799    }
1800
1801    // --- RGB to HLS Tests ---
1802
1803    #[test]
1804    fn test_rgb_to_hls() {
1805        // Pure red
1806        let (h, l, s) = rgb_to_hls(ColorTriplet::new(255, 0, 0));
1807        assert!((h - 0.0).abs() < 0.01);
1808        assert!((l - 0.5).abs() < 0.01);
1809        assert!((s - 1.0).abs() < 0.01);
1810
1811        // Pure white (no saturation)
1812        let (_, _, s) = rgb_to_hls(ColorTriplet::new(255, 255, 255));
1813        assert!(s < 0.01);
1814
1815        // Pure black (no saturation)
1816        let (_, _, s) = rgb_to_hls(ColorTriplet::new(0, 0, 0));
1817        assert!(s < 0.01);
1818    }
1819
1820    // --- Blend Tests ---
1821
1822    #[test]
1823    fn test_blend_rgb() {
1824        let black = ColorTriplet::new(0, 0, 0);
1825        let white = ColorTriplet::new(255, 255, 255);
1826
1827        let mid = blend_rgb(black, white, 0.5);
1828        assert_eq!(mid.red, 127);
1829        assert_eq!(mid.green, 127);
1830        assert_eq!(mid.blue, 127);
1831
1832        let quarter = blend_rgb(black, white, 0.25);
1833        assert_eq!(quarter.red, 63);
1834    }
1835
1836    // --- Integration Tests ---
1837
1838    #[test]
1839    fn test_palette_sizes() {
1840        assert_eq!(STANDARD_PALETTE.len(), 16);
1841        assert_eq!(EIGHT_BIT_PALETTE.len(), 256);
1842        assert_eq!(WINDOWS_PALETTE.len(), 16);
1843    }
1844
1845    #[test]
1846    fn test_ansi_color_names_coverage() {
1847        // Test that common color names are present
1848        assert!(ANSI_COLOR_NAMES.contains_key("red"));
1849        assert!(ANSI_COLOR_NAMES.contains_key("green"));
1850        assert!(ANSI_COLOR_NAMES.contains_key("blue"));
1851        assert!(ANSI_COLOR_NAMES.contains_key("bright_red"));
1852        assert!(ANSI_COLOR_NAMES.contains_key("grey"));
1853        assert!(ANSI_COLOR_NAMES.contains_key("gray")); // Alias
1854        assert!(ANSI_COLOR_NAMES.contains_key("orchid"));
1855        assert!(ANSI_COLOR_NAMES.contains_key("grey93"));
1856    }
1857}