1use crate::errors::{Result, RichError};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum ColorSystem {
19 Standard,
21 EightBit,
23 Truecolor,
25 Windows,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum ColorType {
32 Default,
34 Standard,
36 EightBit,
38 Truecolor,
40 Windows,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub struct ColorTriplet {
47 pub red: u8,
48 pub green: u8,
49 pub blue: u8,
50}
51
52impl ColorTriplet {
53 pub const fn new(red: u8, green: u8, blue: u8) -> Self {
54 Self { red, green, blue }
55 }
56
57 pub fn hex(&self) -> String {
59 format!("#{:02x}{:02x}{:02x}", self.red, self.green, self.blue)
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Hash)]
65pub struct Color {
66 pub name: String,
68 pub kind: ColorType,
69 pub number: Option<u8>,
71 pub triplet: Option<ColorTriplet>,
73}
74
75impl Color {
76 pub fn is_default(&self) -> bool {
78 self.kind == ColorType::Default
79 }
80
81 pub fn default_color() -> Self {
83 Color {
84 name: "default".to_string(),
85 kind: ColorType::Default,
86 number: None,
87 triplet: None,
88 }
89 }
90
91 fn named(name: &str, number: u8) -> Self {
94 let kind = if number < 16 {
95 ColorType::Standard
96 } else {
97 ColorType::EightBit
98 };
99 Color {
100 name: name.to_string(),
101 kind,
102 number: Some(number),
103 triplet: None,
104 }
105 }
106
107 pub fn from_ansi(number: u8) -> Self {
110 Color {
111 name: format!("color({number})"),
112 kind: if number < 16 {
113 ColorType::Standard
114 } else {
115 ColorType::EightBit
116 },
117 number: Some(number),
118 triplet: None,
119 }
120 }
121
122 pub fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
125 let triplet = ColorTriplet::new(red, green, blue);
126 Color {
127 name: triplet.hex(),
128 kind: ColorType::Truecolor,
129 number: None,
130 triplet: Some(triplet),
131 }
132 }
133
134 pub fn parse(color: &str) -> Result<Self> {
139 let original = color.trim();
140 let lower = original.to_ascii_lowercase();
141
142 if lower == "default" {
143 return Ok(Color::default_color());
144 }
145 if let Some(number) = crate::color_names::ansi_color_number(&lower) {
146 return Ok(Color::named(&lower, number));
147 }
148 if let Some(hex) = lower.strip_prefix('#') {
149 let triplet = parse_hex(hex)
150 .ok_or_else(|| RichError::ColorParse(format!("invalid hex color {original:?}")))?;
151 return Ok(Color {
152 name: lower.clone(),
158 kind: ColorType::Truecolor,
159 number: None,
160 triplet: Some(triplet),
161 });
162 }
163 if let Some(inner) = lower.strip_prefix("rgb(").and_then(|s| s.strip_suffix(')')) {
164 let triplet = parse_rgb(inner)
165 .ok_or_else(|| RichError::ColorParse(format!("invalid rgb color {original:?}")))?;
166 return Ok(Color {
167 name: lower.clone(),
173 kind: ColorType::Truecolor,
174 number: None,
175 triplet: Some(triplet),
176 });
177 }
178 if let Some(inner) = lower
179 .strip_prefix("color(")
180 .and_then(|s| s.strip_suffix(')'))
181 {
182 let n: u16 = inner
183 .trim()
184 .parse()
185 .map_err(|_| RichError::ColorParse(format!("invalid color number {original:?}")))?;
186 if n > 255 {
187 return Err(RichError::ColorParse(format!(
188 "color number must be <= 255, not {n}"
189 )));
190 }
191 return Ok(Color {
194 name: lower.clone(),
200 kind: if n < 16 {
201 ColorType::Standard
202 } else {
203 ColorType::EightBit
204 },
205 number: Some(n as u8),
206 triplet: None,
207 });
208 }
209 Err(RichError::ColorParse(format!(
210 "{original:?} is not a valid color"
211 )))
212 }
213
214 pub fn get_truecolor(&self) -> Option<ColorTriplet> {
218 match self.kind {
219 ColorType::Default => None,
220 ColorType::Truecolor => self.triplet,
221 ColorType::Standard | ColorType::Windows => {
222 self.number.map(|n| ANSI_BASE_PALETTE[n as usize])
223 }
224 ColorType::EightBit => self.number.map(eight_bit_triplet),
225 }
226 }
227
228 pub fn ansi_codes(&self, foreground: bool) -> Vec<String> {
232 match self.kind {
233 ColorType::Default => vec![if foreground { "39" } else { "49" }.to_string()],
234 ColorType::Windows | ColorType::Standard => {
235 let number = self.number.unwrap_or(0);
236 let (fore, back) = if number < 8 { (30, 40) } else { (82, 92) };
237 vec![(if foreground { fore } else { back } + number as u32).to_string()]
238 }
239 ColorType::EightBit => {
240 let number = self.number.unwrap_or(0);
241 vec![
242 if foreground { "38" } else { "48" }.to_string(),
243 "5".to_string(),
244 number.to_string(),
245 ]
246 }
247 ColorType::Truecolor => {
248 let t = self.triplet.unwrap_or(ColorTriplet::new(0, 0, 0));
249 vec![
250 if foreground { "38" } else { "48" }.to_string(),
251 "2".to_string(),
252 t.red.to_string(),
253 t.green.to_string(),
254 t.blue.to_string(),
255 ]
256 }
257 }
258 }
259
260 pub fn downgrade(&self, system: ColorSystem) -> Color {
265 if self.kind == ColorType::Default {
266 return self.clone();
267 }
268 let target_rank = match system {
269 ColorSystem::Standard | ColorSystem::Windows => 0,
270 ColorSystem::EightBit => 1,
271 ColorSystem::Truecolor => 2,
272 };
273 let self_rank = match self.kind {
274 ColorType::Standard | ColorType::Windows => 0,
275 ColorType::EightBit => 1,
276 ColorType::Truecolor => 2,
277 ColorType::Default => return self.clone(),
278 };
279 if self_rank <= target_rank {
280 return self.clone();
283 }
284 let triplet = match self.get_truecolor() {
285 Some(t) => t,
286 None => return self.clone(),
287 };
288 match target_rank {
289 1 => Color {
290 name: self.name.clone(),
291 kind: ColorType::EightBit,
292 number: Some(truecolor_to_eight_bit(triplet)),
293 triplet: None,
294 },
295 _ => {
296 let number = match_color(&STANDARD_PALETTE, triplet);
297 Color {
298 name: self.name.clone(),
299 kind: ColorType::Standard,
300 number: Some(number),
301 triplet: None,
302 }
303 }
304 }
305 }
306}
307
308fn parse_hex(hex: &str) -> Option<ColorTriplet> {
309 if hex.len() != 6 || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
310 return None;
311 }
312 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
313 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
314 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
315 Some(ColorTriplet::new(r, g, b))
316}
317
318fn parse_rgb(inner: &str) -> Option<ColorTriplet> {
319 let parts: Vec<&str> = inner.split(',').map(str::trim).collect();
320 if parts.len() != 3 {
321 return None;
322 }
323 let r = parts[0].parse().ok()?;
324 let g = parts[1].parse().ok()?;
325 let b = parts[2].parse().ok()?;
326 Some(ColorTriplet::new(r, g, b))
327}
328
329pub const ANSI_BASE_PALETTE: [ColorTriplet; 16] = [
338 ColorTriplet::new(0, 0, 0),
339 ColorTriplet::new(128, 0, 0),
340 ColorTriplet::new(0, 128, 0),
341 ColorTriplet::new(128, 128, 0),
342 ColorTriplet::new(0, 0, 128),
343 ColorTriplet::new(128, 0, 128),
344 ColorTriplet::new(0, 128, 128),
345 ColorTriplet::new(192, 192, 192),
346 ColorTriplet::new(128, 128, 128),
347 ColorTriplet::new(255, 0, 0),
348 ColorTriplet::new(0, 255, 0),
349 ColorTriplet::new(255, 255, 0),
350 ColorTriplet::new(0, 0, 255),
351 ColorTriplet::new(255, 0, 255),
352 ColorTriplet::new(0, 255, 255),
353 ColorTriplet::new(255, 255, 255),
354];
355
356pub const STANDARD_PALETTE: [ColorTriplet; 16] = [
363 ColorTriplet::new(0, 0, 0),
364 ColorTriplet::new(170, 0, 0),
365 ColorTriplet::new(0, 170, 0),
366 ColorTriplet::new(170, 85, 0),
367 ColorTriplet::new(0, 0, 170),
368 ColorTriplet::new(170, 0, 170),
369 ColorTriplet::new(0, 170, 170),
370 ColorTriplet::new(170, 170, 170),
371 ColorTriplet::new(85, 85, 85),
372 ColorTriplet::new(255, 85, 85),
373 ColorTriplet::new(85, 255, 85),
374 ColorTriplet::new(255, 255, 85),
375 ColorTriplet::new(85, 85, 255),
376 ColorTriplet::new(255, 85, 255),
377 ColorTriplet::new(85, 255, 255),
378 ColorTriplet::new(255, 255, 255),
379];
380
381static EIGHT_BIT_PALETTE: [ColorTriplet; 256] = build_eight_bit_palette();
384
385const fn build_eight_bit_palette() -> [ColorTriplet; 256] {
386 let mut palette = [ColorTriplet::new(0, 0, 0); 256];
387 let mut i = 0;
389 while i < 16 {
390 palette[i] = ANSI_BASE_PALETTE[i];
391 i += 1;
392 }
393 let levels = [0u8, 95, 135, 175, 215, 255];
395 let mut r = 0;
396 while r < 6 {
397 let mut g = 0;
398 while g < 6 {
399 let mut b = 0;
400 while b < 6 {
401 let index = 16 + 36 * r + 6 * g + b;
402 palette[index] = ColorTriplet::new(levels[r], levels[g], levels[b]);
403 b += 1;
404 }
405 g += 1;
406 }
407 r += 1;
408 }
409 let mut n = 0;
411 while n < 24 {
412 let value = 8 + 10 * n as u8;
413 palette[232 + n] = ColorTriplet::new(value, value, value);
414 n += 1;
415 }
416 palette
417}
418
419fn eight_bit_triplet(number: u8) -> ColorTriplet {
420 EIGHT_BIT_PALETTE[number as usize]
421}
422
423fn rgb_to_ls(red: f64, green: f64, blue: f64) -> (f64, f64) {
429 let max = red.max(green).max(blue);
430 let min = red.min(green).min(blue);
431 let lightness = (max + min) / 2.0;
432 if max == min {
433 return (lightness, 0.0);
434 }
435 let saturation = if lightness <= 0.5 {
436 (max - min) / (max + min)
437 } else {
438 (max - min) / (2.0 - max - min)
439 };
440 (lightness, saturation)
441}
442
443fn truecolor_to_eight_bit(color: ColorTriplet) -> u8 {
452 let (red, green, blue) = (
453 color.red as f64 / 255.0,
454 color.green as f64 / 255.0,
455 color.blue as f64 / 255.0,
456 );
457 let (lightness, saturation) = rgb_to_ls(red, green, blue);
458
459 if saturation < 0.15 {
461 let gray = (lightness * 25.0).round_ties_even() as i64;
464 return match gray {
465 0 => 16,
466 25 => 231,
467 other => (231 + other) as u8,
468 };
469 }
470
471 let axis = |component: u8| -> f64 {
473 let value = component as f64;
474 if value < 95.0 {
475 value / 95.0
476 } else {
477 1.0 + (value - 95.0) / 40.0
478 }
479 };
480 let six_red = axis(color.red).round_ties_even();
481 let six_green = axis(color.green).round_ties_even();
482 let six_blue = axis(color.blue).round_ties_even();
483 (16.0 + 36.0 * six_red + 6.0 * six_green + six_blue) as u8
484}
485
486fn match_color(palette: &[ColorTriplet], color: ColorTriplet) -> u8 {
487 let (red1, green1, blue1) = (color.red as i64, color.green as i64, color.blue as i64);
488 let mut best_index = 0usize;
489 let mut best_distance = i64::MAX;
490 for (index, candidate) in palette.iter().enumerate() {
491 let (red2, green2, blue2) = (
492 candidate.red as i64,
493 candidate.green as i64,
494 candidate.blue as i64,
495 );
496 let red_mean = (red1 + red2) / 2;
497 let red = red1 - red2;
498 let green = green1 - green2;
499 let blue = blue1 - blue2;
500 let distance = (((512 + red_mean) * red * red) >> 8)
503 + 4 * green * green
504 + (((767 - red_mean) * blue * blue) >> 8);
505 if distance < best_distance {
506 best_distance = distance;
507 best_index = index;
508 }
509 }
510 best_index as u8
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn parses_standard_name() {
519 let c = Color::parse("red").unwrap();
520 assert_eq!(c.kind, ColorType::Standard);
521 assert_eq!(c.number, Some(1));
522 assert_eq!(c.ansi_codes(true), vec!["31"]);
523 assert_eq!(c.ansi_codes(false), vec!["41"]);
524 }
525
526 #[test]
527 fn extended_name_is_eight_bit() {
528 let c = Color::parse("orange1").unwrap();
530 assert_eq!(c.kind, ColorType::EightBit);
531 assert_eq!(c.number, Some(214));
532 assert_eq!(c.ansi_codes(true), vec!["38", "5", "214"]);
533 }
534
535 #[test]
536 fn bright_color_uses_high_intensity_sgr() {
537 let c = Color::parse("bright_red").unwrap();
538 assert_eq!(c.number, Some(9));
539 assert_eq!(c.ansi_codes(true), vec!["91"]);
541 }
542
543 #[test]
544 fn parses_hex_truecolor() {
545 let c = Color::parse("#ff8800").unwrap();
546 assert_eq!(c.kind, ColorType::Truecolor);
547 assert_eq!(c.triplet, Some(ColorTriplet::new(0xff, 0x88, 0x00)));
548 assert_eq!(c.ansi_codes(true), vec!["38", "2", "255", "136", "0"]);
549 }
550
551 #[test]
558 fn downgrade_to_standard_matches_upstream() {
559 let standard = |hex: &str| {
560 let down = Color::parse(hex).unwrap().downgrade(ColorSystem::Standard);
561 assert_eq!(down.kind, ColorType::Standard);
562 down.number
563 };
564 assert_eq!(standard("#ff0000"), Some(1));
565 assert_eq!(standard("#00ff00"), Some(2));
566 assert_eq!(standard("#0000ff"), Some(4));
567 assert_eq!(standard("#ffffff"), Some(15));
568 assert_eq!(standard("#808080"), Some(7));
569 assert_eq!(standard("#ff8800"), Some(9));
570 }
571
572 #[test]
576 fn downgrade_to_eight_bit_matches_upstream() {
577 let eight_bit = |hex: &str| {
578 let down = Color::parse(hex).unwrap().downgrade(ColorSystem::EightBit);
579 assert_eq!(down.kind, ColorType::EightBit);
580 down.number
581 };
582 assert_eq!(eight_bit("#ff0000"), Some(196));
583 assert_eq!(eight_bit("#00ff00"), Some(46));
584 assert_eq!(eight_bit("#0000ff"), Some(21));
585 assert_eq!(eight_bit("#ff8800"), Some(208));
586 assert_eq!(eight_bit("#ffffff"), Some(231));
588 assert_eq!(eight_bit("#808080"), Some(244));
589 }
590
591 #[test]
592 fn eight_bit_palette_cube_is_correct() {
593 assert_eq!(eight_bit_triplet(196), ColorTriplet::new(255, 0, 0));
595 }
596}