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