debug_et_diagnostics/
color.rs

1use ansi_colours::{ansi256_from_rgb, rgb_from_ansi256};
2use std::fmt::{Debug, Display, LowerHex};
3use std::iter::{IntoIterator, Iterator};
4
5#[cfg(feature = "colorsys")]
6use colorsys::{Ansi256, ColorAlpha, ColorTransform, Hsl, Rgb};
7
8const DEFAULT_COLUMNS: usize = 130;
9
10/// reset the ANSI colors of the given test
11pub fn reset<T: Display>(text: T) -> String {
12    format!("{}\x1b[0m", text)
13}
14
15/// colorize the foreground of the given text without resetting (ANSI 256 colors)
16pub fn fg<T: Display>(text: T, fg: usize) -> String {
17    format!("\x1b[1;38;5;{}m{}", wrap(fg), text)
18}
19/// colorize the background of the given text without resetting (ANSI 256 colors)
20pub fn bg<T: Display>(text: T, bg: usize) -> String {
21    format!("\x1b[1;48;5;{}m{}", wrap(bg), text)
22}
23/// colorize the foreground and backrground of the given text without resetting
24///
25/// > shorthand combination of [bg] and [fg]
26pub fn bgfg<T: Display>(text: T, fore: usize, back: usize) -> String {
27    bg(fg(text, wrap(fore) as usize), wrap(back) as usize)
28}
29/// colorize the foreground and backrground of the given text and reset the colors afterwards
30pub fn ansi<T: Display>(text: T, fore: usize, back: usize) -> String {
31    reset(bgfg(text, fore as usize, back as usize))
32}
33/// pad text by the number of columns determined by [term_cols]
34pub fn pad_columns<T: Display>(text: T) -> String {
35    let text = text.to_string();
36    let cols = term_cols();
37    pad(text, cols)
38}
39/// pad text
40pub fn pad<T: Display>(text: T, length: usize) -> String {
41    let text = text.to_string();
42    let len = text
43        .as_bytes()
44        .iter()
45        .map(|c| char::from(*c))
46        .map(|c| {
47            u32::from(c)
48                .to_ne_bytes()
49                .iter()
50                .map(Clone::clone)
51                .filter(|c| *c > 0)
52                .collect::<Vec<u8>>()
53        })
54        .flatten()
55        .count();
56
57    format!(
58        "{}{}",
59        text,
60        " ".repeat(if length > len {
61            length - len
62        } else if len < length {
63            0
64        } else {
65            0
66        })
67    )
68}
69/// clear the screen
70pub fn ansi_clear() -> String {
71    "\x1b[2J\x1b[3J\x1b[H".to_string()
72}
73/// colorize the foreground of the given text and reset afterwards
74pub fn fore<T: Display>(text: T, fore: usize) -> String {
75    let (fore, back) = couple(fore);
76    ansi(text, fore as usize, back as usize)
77}
78/// colorize the backrground of the given text and reset afterwards
79pub fn back<T: Display>(text: T, back: usize) -> String {
80    let (back, fore) = couple(back);
81    ansi(text, fore as usize, back as usize)
82}
83/// auto-colorize the given text with the color determined by [from_display]
84pub fn auto<T: Display>(word: T) -> String {
85    fore(
86        word.to_string(),
87        u8::from_str_radix(&word.to_string(), 10)
88            .unwrap_or_else(|_| from_display(word.to_string()))
89            .into(),
90    )
91}
92/// brighter version of [auto]
93pub fn auto_bright<T: Display>(word: T) -> String {
94    fore(
95        word.to_string(),
96        bright(
97            u8::from_str_radix(&word.to_string(), 10)
98                .unwrap_or_else(|_| from_display(word.to_string()))
99                .into(),
100        )
101        .into(),
102    )
103}
104/// darker version of [auto]
105pub fn auto_dark<T: Display>(word: T) -> String {
106    fore(
107        word.to_string(),
108        dark(
109            u8::from_str_radix(&word.to_string(), 10)
110                .unwrap_or_else(|_| from_display(word.to_string()))
111                .into(),
112        )
113        .into(),
114    )
115}
116/// auto-colorize the underlying bytes of given text with the color determined by [from_bytes]
117pub fn from_display<T: Display>(word: T) -> u8 {
118    let string = format!("{word}");
119    from_bytes(
120        &u8::from_str_radix(&string, 10)
121            .ok()
122            .or_else(|| u8::from_str_radix(&string, 16).ok())
123            .map(|byte| vec![byte])
124            .or_else(|| {
125                if string.to_lowercase().starts_with("0x") {
126                    u8::from_str_radix(string.to_lowercase().replacen("0x", "", 1).as_str(), 16)
127                        .map(|byte| vec![byte])
128                        .ok()
129                } else {
130                    None
131                }
132            })
133            .map(|byte| vec![byte].into_iter().flatten().collect::<Vec<u8>>())
134            .or_else(|| {
135                u16::from_str_radix(&string, 16)
136                    .map(|u| u.to_ne_bytes().to_vec())
137                    .ok()
138            })
139            .or_else(|| {
140                if string.to_lowercase().starts_with("0x") {
141                    u16::from_str_radix(string.to_lowercase().replacen("0x", "", 1).as_str(), 16)
142                        .map(|u| u.to_ne_bytes().to_vec())
143                        .ok()
144                } else {
145                    None
146                }
147            })
148            .or_else(|| {
149                u32::from_str_radix(&string, 16)
150                    .ok()
151                    .map(|u| u.to_ne_bytes().to_vec())
152            })
153            .or_else(|| {
154                if string.to_lowercase().starts_with("0x") {
155                    u32::from_str_radix(string.to_lowercase().replacen("0x", "", 1).as_str(), 16)
156                        .map(|u| u.to_ne_bytes().to_vec())
157                        .ok()
158                } else {
159                    None
160                }
161            })
162            .or_else(|| {
163                u64::from_str_radix(&string, 16)
164                    .ok()
165                    .map(|u| u.to_ne_bytes().to_vec())
166            })
167            .or_else(|| {
168                if string.to_lowercase().starts_with("0x") {
169                    u64::from_str_radix(string.to_lowercase().replacen("0x", "", 1).as_str(), 16)
170                        .map(|u| u.to_ne_bytes().to_vec())
171                        .ok()
172                } else {
173                    None
174                }
175            })
176            .unwrap_or_else(|| string.as_bytes().to_vec()),
177    )
178}
179/// auto-colorize the underlying bytes of given text with the color determined by [from_bytes]
180pub fn from_debug<T: Debug>(word: T) -> u8 {
181    from_bytes(format!("{word:#?}").as_bytes())
182}
183/// determine a triple of RGB colors of a string determined by [rgb_from_bytes]
184pub fn rgb_from_display<T: Display>(word: T) -> [u8; 3] {
185    rgb_from_bytes(word.to_string().as_bytes())
186}
187
188/// determine an ANSI-256 color determined by [rgb_from_bytes]
189pub fn from_bytes(bytes: &[u8]) -> u8 {
190    let mut color: u8 = 0;
191    for rgb in rgb_from_bytes(bytes) {
192        color ^= rgb
193    }
194    color
195}
196/// simple and naive algorithm to determine a triple of RGB colors
197/// based on XOR'ing the given slice of bytes;
198pub fn rgb_from_bytes(bytes: &[u8]) -> [u8; 3] {
199    merge_rgb(bytes.into_iter().map(|byte| rgb_from_byte(*byte)), false)
200}
201/// returns a `[red, green, blue]` slice `[u8; 3]` from a single byte
202pub fn rgb_from_byte(byte: u8) -> [u8; 3] {
203    let tuple = rgb_from_ansi256(byte);
204    [tuple.0, tuple.1, tuple.2]
205}
206
207/// returns a `[red, green, blue]` slice `[u8; 3]` from a single byte
208pub fn rgb_to_byte(rgb: [u8; 3]) -> u8 {
209    ansi256_from_rgb(rgb)
210}
211/// merges a sequence of slice `[u8; 3]` into a single slice `[u8; 3]`
212pub fn merge_rgb<I: IntoIterator<Item = [u8; 3]> + Clone>(rgbs: I, extra: bool) -> [u8; 3] {
213    let mut result = [0u8; 3];
214    for rgb in rgbs.clone().into_iter() {
215        result[0] ^= rgb[0];
216        result[1] ^= rgb[1];
217        result[2] ^= rgb[2];
218    }
219    if extra {
220        for triple in rgbs.clone().into_iter() {
221            for byte in triple.into_iter() {
222                let rgb = rgb_from_byte(byte);
223                result[0] ^= rgb[0];
224                result[1] ^= rgb[1];
225                result[2] ^= rgb[2];
226            }
227        }
228    }
229    result
230}
231
232/// returns a tuple of (foreground, backrground) color by taking one
233/// unsigned integer, wrapping that around [u8::MAX] to determine the
234/// foreground color then uses [invert_bw] to
235/// determine the background color.
236pub fn couple(color: usize) -> (u8, u8) {
237    let fore = wrap(color);
238    let back = invert_bw(fore as usize);
239    (fore, back)
240}
241
242/// converts the given color to rgb triple then inverts the rgb and converts back to ansi256
243#[cfg(feature = "colorsys")]
244pub fn invert_ansi(color: usize) -> u8 {
245    let color = Ansi256::new(wrap(color));
246    let mut hsl = Hsl::from(&Rgb::from(color));
247    hsl.set_lightness(100.0 - hsl.lightness());
248    let mut rgb = Rgb::from(&hsl);
249    rgb.invert();
250    let color = Ansi256::from(&rgb);
251    color.code()
252}
253/// converts the given color to rgb triple then inverts the rgb and converts back to ansi256
254#[cfg(not(feature = "colorsys"))]
255pub fn invert_ansi(color: usize) -> u8 {
256    if is_dark_rgb_band(color) {
257        bright(rgb_to_byte(invert_rgb(rgb_from_byte(wrap(color)))) as usize)
258    } else {
259        dark(rgb_to_byte(invert_rgb(rgb_from_byte(wrap(color)))) as usize)
260    }
261}
262
263/// converts the given color to rgb triple then inverts the rgb and converts back to ansi256
264#[cfg(feature = "colorsys")]
265pub fn invert_rgb(color: [u8; 3]) -> [u8; 3] {
266    let mut rgb = Rgb::from(color);
267    rgb.invert();
268    rgb.into()
269}
270
271/// converts the given color to rgb triple then inverts the rgb and converts back to ansi256
272#[cfg(not(feature = "colorsys"))]
273pub fn invert_rgb(color: [u8; 3]) -> [u8; 3] {
274    [255u8 - color[0], 255u8 - color[1], 255u8 - color[2]]
275}
276
277/// naive heuristic to return the brightest opposite of the given color.
278#[cfg(not(feature = "colorsys"))]
279pub fn invert_bw(color: usize) -> u8 {
280    match color {
281        0 | 8 | 16..21 | 52..61 | 88..93 | 232..239 => 231,
282        _ => 16,
283    }
284}
285/// brings the color to grayscale then inverts it
286#[cfg(feature = "colorsys")]
287pub fn invert_bw(color: usize) -> u8 {
288    let color = Ansi256::new(wrap(color));
289    let mut rgb = Rgb::from(color);
290    rgb.grayscale_simple();
291    rgb.invert();
292    Ansi256::from(&rgb).code()
293}
294
295/// return true if the given rgb band is bright
296pub fn is_bright_rgb_band(color: usize) -> bool {
297    let color = wrap(color);
298    color >= 0x75
299}
300/// return a brighter color from the given rgb band
301pub fn bright_rgb_band(color: usize) -> u8 {
302    if !is_bright_rgb_band(color) {
303        0xff - wrap(color)
304    } else {
305        wrap(color)
306    }
307}
308/// return a brighter color near the given one via [bright_rgb_band].
309#[cfg(feature = "colorsys")]
310pub fn bright(color: usize) -> u8 {
311    let color = wrap(color);
312    let mut rgb = Rgb::from(Ansi256::new(color));
313    rgb.lighten(50.0);
314    Ansi256::from(rgb).code()
315}
316/// return a brighter color near the given one via [bright_rgb_band].
317#[cfg(not(feature = "colorsys"))]
318pub fn bright(color: usize) -> u8 {
319    let color = wrap(color);
320    let [r, g, b] = rgb_from_byte(color);
321    rgb_to_byte([
322        bright_rgb_band(r as usize),
323        bright_rgb_band(g as usize),
324        bright_rgb_band(b as usize),
325    ])
326}
327/// return true if the given rgb band is dark
328pub fn is_dark_rgb_band(color: usize) -> bool {
329    let color = wrap(color);
330    color <= 0x75
331}
332/// return a darker color from the given rgb band
333pub fn dark_rgb_band(color: usize) -> u8 {
334    let color = wrap(color);
335    if !is_dark_rgb_band(color as usize) {
336        color - ((color / 4) * 3)
337    } else {
338        color
339    }
340}
341/// return a darker color near the given one via [dark_rgb_band].
342#[cfg(feature = "colorsys")]
343pub fn dark(color: usize) -> u8 {
344    let color = wrap(color);
345    let mut rgb = Rgb::from(Ansi256::new(color));
346    rgb.lighten(-50.0);
347    Ansi256::from(rgb).code()
348}
349#[cfg(not(feature = "colorsys"))]
350pub fn dark(color: usize) -> u8 {
351    let color = wrap(color);
352    let [r, g, b] = rgb_from_byte(color);
353    rgb_to_byte([
354        dark_rgb_band(r as usize),
355        dark_rgb_band(g as usize),
356        dark_rgb_band(b as usize),
357    ])
358}
359
360/// wraps the given usize via remainder
361pub fn wrap(color: usize) -> u8 {
362    if color <= u8::MAX.into() {
363        color as u8
364    } else {
365        from_bytes(&color.to_ne_bytes())
366    }
367}
368
369pub fn non_zero_be_bytes(color: usize) -> Vec<u8> {
370    let mut bytes = color.to_be_bytes().to_vec();
371    while bytes.len() > 1 && bytes[0] == 0 {
372        bytes.remove(0);
373    }
374    bytes
375}
376
377/// tries to obtain the number of columns of the [DEFAULT_COLUMNS] in case of error.
378pub fn term_cols() -> usize {
379    match ioctl_term_light::cols() {
380        0 => DEFAULT_COLUMNS,
381        cols => cols as usize,
382    }
383}
384
385/// determine an ANSI-256 color determined by [`from_bytes(&[byte])`]
386pub fn from_byte(byte: u8) -> u8 {
387    byte
388}
389
390/// auto-colorize the given byte with the color determined by [from_byte]
391pub fn byte(byte: u8) -> String {
392    let (fg, bg) = couple(from_byte(byte).into());
393    ansi(byte, fg as usize, bg as usize)
394}
395
396/// auto-colorize the given byte in hex format with the color determined by [from_byte]
397pub fn byte_hex(byte: u8) -> String {
398    let (fg, bg) = couple(from_byte(byte).into());
399    ansi(format!("0x{byte:02x}"), fg as usize, bg as usize)
400}
401
402/// auto-colorize the given byte in bin format with the color determined by [from_byte]
403pub fn byte_bin(byte: u8) -> String {
404    let (fg, bg) = couple(from_byte(byte).into());
405    ansi(format!("0b{byte:08b}"), fg as usize, bg as usize)
406}
407
408pub const STD_COLORS: [u8; 48] = [
409    0x00u8, 0x00u8, 0x00u8, //  0x00u8 black
410    0x80u8, 0x00u8, 0x00u8, //  1 red
411    0x00u8, 0x80u8, 0x00u8, //  2 green
412    0x80u8, 0x80u8, 0x00u8, //  3 yellow
413    0x00u8, 0x00u8, 0x80u8, //  4 blue
414    0x80u8, 0x00u8, 0x80u8, //  5 magenta
415    0x00u8, 0x80u8, 0x80u8, //  6 cyan
416    0xc0u8, 0xc0u8, 0xc0u8, //  7 white (light grey)
417    0x80u8, 0x80u8, 0x80u8, //  8 grey
418    0xffu8, 0x00u8, 0x00u8, //  9 bright red
419    0xffu8, 0xffu8, 0x00u8, // 10 bright green
420    0x00u8, 0xffu8, 0x00u8, // 11 bright yellow
421    0x00u8, 0x00u8, 0xffu8, // 12 bright blue
422    0xffu8, 0x00u8, 0xffu8, // 13 bright magenta
423    0x00u8, 0xffu8, 0xffu8, // 14 bright cyan
424    0xffu8, 0xffu8, 0xffu8, // 15 bright white
425];
426pub fn cube_ansi_256(color: usize, op: usize) -> u8 {
427    let color = wrap(color) as usize;
428    let cube = ((color - 16) / op) % 6;
429    if cube == 0 {
430        0u8
431    } else {
432        wrap((14135 + 10280 * cube) / 256)
433    }
434}
435pub fn get_ansi_rgb(color: usize) -> [u8; 3] {
436    let tuple = rgb_from_ansi256(wrap(color));
437    [tuple.0, tuple.1, tuple.2]
438}
439
440pub fn format_slice_hex<I: IntoIterator<Item: LowerHex>>(items: I, color: bool) -> String {
441    format!(
442        "[{}]",
443        items
444            .into_iter()
445            .map(|el| {
446                let byte = format!("0x{el:02x}");
447                if color {
448                    fore(
449                        byte,
450                        from_byte(
451                            u8::from_str_radix(&format!("{el:02x}"), 16).unwrap_or_default(),
452                            // .unwrap_or_else(|_| from_display(format!("{el:x}"))),
453                        )
454                        .into(),
455                    )
456                } else {
457                    byte
458                }
459            })
460            .collect::<Vec<String>>()
461            .join(", ")
462    )
463}
464
465pub fn format_slice_display<I: IntoIterator<Item: Display>>(items: I, color: bool) -> String {
466    format!(
467        "[{}]",
468        items
469            .into_iter()
470            .map(|el| {
471                let byte = format!("{el}");
472                if color {
473                    fore(byte, from_display(el).into())
474                } else {
475                    byte
476                }
477            })
478            .collect::<Vec<String>>()
479            .join(", ")
480    )
481}
482pub fn format_slice_debug<I: IntoIterator<Item: Debug>>(items: I, color: bool) -> String {
483    format!(
484        "[{}]",
485        items
486            .into_iter()
487            .map(|el| {
488                let byte = format!("{el:#?}");
489                if color {
490                    fore(byte, from_debug(el).into())
491                } else {
492                    byte
493                }
494            })
495            .collect::<Vec<String>>()
496            .join(", ")
497    )
498}
499
500// SEEALSO:
501// https://gist.github.com/XVilka/8346728
502// https://github.com/termstandard/colors?tab=readme-ov-file
503// https://gist.github.com/lilydjwg/fdeaf79e921c2f413f44b6f613f6ad53
504// https://github.com/robertknight/konsole/blob/master/tests/color-spaces.pl
505// https://github.com/JohnMorales/dotfiles/blob/master/colors/24-bit-color.sh
506// https://gitlab.gnome.org/GNOME/vte/-/blob/master/perf/img.sh