use ansi_colours::{ansi256_from_rgb, rgb_from_ansi256};
use std::fmt::{Debug, Display, LowerHex};
use std::iter::{IntoIterator, Iterator};
#[cfg(feature = "colorsys")]
use colorsys::{Ansi256, ColorAlpha, ColorTransform, Hsl, Rgb};
const DEFAULT_COLUMNS: usize = 130;
pub fn reset<T: Display>(text: T) -> String {
format!("{}\x1b[0m", text)
}
pub fn fg<T: Display>(text: T, fg: usize) -> String {
format!("\x1b[1;38;5;{}m{}", wrap(fg), text)
}
pub fn bg<T: Display>(text: T, bg: usize) -> String {
format!("\x1b[1;48;5;{}m{}", wrap(bg), text)
}
pub fn bgfg<T: Display>(text: T, fore: usize, back: usize) -> String {
bg(fg(text, wrap(fore) as usize), wrap(back) as usize)
}
pub fn ansi<T: Display>(text: T, fore: usize, back: usize) -> String {
reset(bgfg(text, fore as usize, back as usize))
}
pub fn pad_columns<T: Display>(text: T) -> String {
let text = text.to_string();
let cols = term_cols();
pad(text, cols)
}
pub fn pad<T: Display>(text: T, length: usize) -> String {
let text = text.to_string();
let len = text
.as_bytes()
.iter()
.map(|c| char::from(*c))
.map(|c| {
u32::from(c)
.to_ne_bytes()
.iter()
.map(Clone::clone)
.filter(|c| *c > 0)
.collect::<Vec<u8>>()
})
.flatten()
.count();
format!(
"{}{}",
text,
" ".repeat(if length > len {
length - len
} else if len < length {
0
} else {
0
})
)
}
pub fn ansi_clear() -> String {
"\x1b[2J\x1b[3J\x1b[H".to_string()
}
pub fn fore<T: Display>(text: T, fore: usize) -> String {
let (fore, back) = couple(fore);
ansi(text, fore as usize, back as usize)
}
pub fn back<T: Display>(text: T, back: usize) -> String {
let (back, fore) = couple(back);
ansi(text, fore as usize, back as usize)
}
pub fn auto<T: Display>(word: T) -> String {
fore(
word.to_string(),
u8::from_str_radix(&word.to_string(), 10)
.unwrap_or_else(|_| from_display(word.to_string()))
.into(),
)
}
pub fn auto_bright<T: Display>(word: T) -> String {
fore(
word.to_string(),
bright(
u8::from_str_radix(&word.to_string(), 10)
.unwrap_or_else(|_| from_display(word.to_string()))
.into(),
)
.into(),
)
}
pub fn auto_dark<T: Display>(word: T) -> String {
fore(
word.to_string(),
dark(
u8::from_str_radix(&word.to_string(), 10)
.unwrap_or_else(|_| from_display(word.to_string()))
.into(),
)
.into(),
)
}
pub fn from_display<T: Display>(word: T) -> u8 {
let string = format!("{word}");
from_bytes(
&u8::from_str_radix(&string, 10)
.ok()
.or_else(|| u8::from_str_radix(&string, 16).ok())
.map(|byte| vec![byte])
.or_else(|| {
if string.to_lowercase().starts_with("0x") {
u8::from_str_radix(string.to_lowercase().replacen("0x", "", 1).as_str(), 16)
.map(|byte| vec![byte])
.ok()
} else {
None
}
})
.map(|byte| vec![byte].into_iter().flatten().collect::<Vec<u8>>())
.or_else(|| {
u16::from_str_radix(&string, 16)
.map(|u| u.to_ne_bytes().to_vec())
.ok()
})
.or_else(|| {
if string.to_lowercase().starts_with("0x") {
u16::from_str_radix(string.to_lowercase().replacen("0x", "", 1).as_str(), 16)
.map(|u| u.to_ne_bytes().to_vec())
.ok()
} else {
None
}
})
.or_else(|| {
u32::from_str_radix(&string, 16)
.ok()
.map(|u| u.to_ne_bytes().to_vec())
})
.or_else(|| {
if string.to_lowercase().starts_with("0x") {
u32::from_str_radix(string.to_lowercase().replacen("0x", "", 1).as_str(), 16)
.map(|u| u.to_ne_bytes().to_vec())
.ok()
} else {
None
}
})
.or_else(|| {
u64::from_str_radix(&string, 16)
.ok()
.map(|u| u.to_ne_bytes().to_vec())
})
.or_else(|| {
if string.to_lowercase().starts_with("0x") {
u64::from_str_radix(string.to_lowercase().replacen("0x", "", 1).as_str(), 16)
.map(|u| u.to_ne_bytes().to_vec())
.ok()
} else {
None
}
})
.unwrap_or_else(|| string.as_bytes().to_vec()),
)
}
pub fn from_debug<T: Debug>(word: T) -> u8 {
from_bytes(format!("{word:#?}").as_bytes())
}
pub fn rgb_from_display<T: Display>(word: T) -> [u8; 3] {
rgb_from_bytes(word.to_string().as_bytes())
}
pub fn from_bytes(bytes: &[u8]) -> u8 {
let mut color: u8 = 0;
for rgb in rgb_from_bytes(bytes) {
color ^= rgb
}
color
}
pub fn rgb_from_bytes(bytes: &[u8]) -> [u8; 3] {
merge_rgb(bytes.into_iter().map(|byte| rgb_from_byte(*byte)), false)
}
pub fn rgb_from_byte(byte: u8) -> [u8; 3] {
let tuple = rgb_from_ansi256(byte);
[tuple.0, tuple.1, tuple.2]
}
pub fn rgb_to_byte(rgb: [u8; 3]) -> u8 {
ansi256_from_rgb(rgb)
}
pub fn merge_rgb<I: IntoIterator<Item = [u8; 3]> + Clone>(rgbs: I, extra: bool) -> [u8; 3] {
let mut result = [0u8; 3];
for rgb in rgbs.clone().into_iter() {
result[0] ^= rgb[0];
result[1] ^= rgb[1];
result[2] ^= rgb[2];
}
if extra {
for triple in rgbs.clone().into_iter() {
for byte in triple.into_iter() {
let rgb = rgb_from_byte(byte);
result[0] ^= rgb[0];
result[1] ^= rgb[1];
result[2] ^= rgb[2];
}
}
}
result
}
pub fn couple(color: usize) -> (u8, u8) {
let fore = wrap(color);
let back = invert_bw(fore as usize);
(fore, back)
}
#[cfg(feature = "colorsys")]
pub fn invert_ansi(color: usize) -> u8 {
let color = Ansi256::new(wrap(color));
let mut hsl = Hsl::from(&Rgb::from(color));
hsl.set_lightness(100.0 - hsl.lightness());
let mut rgb = Rgb::from(&hsl);
rgb.invert();
let color = Ansi256::from(&rgb);
color.code()
}
#[cfg(not(feature = "colorsys"))]
pub fn invert_ansi(color: usize) -> u8 {
if is_dark_rgb_band(color) {
bright(rgb_to_byte(invert_rgb(rgb_from_byte(wrap(color)))) as usize)
} else {
dark(rgb_to_byte(invert_rgb(rgb_from_byte(wrap(color)))) as usize)
}
}
#[cfg(feature = "colorsys")]
pub fn invert_rgb(color: [u8; 3]) -> [u8; 3] {
let mut rgb = Rgb::from(color);
rgb.invert();
rgb.into()
}
#[cfg(not(feature = "colorsys"))]
pub fn invert_rgb(color: [u8; 3]) -> [u8; 3] {
[255u8 - color[0], 255u8 - color[1], 255u8 - color[2]]
}
#[cfg(not(feature = "colorsys"))]
pub fn invert_bw(color: usize) -> u8 {
match color {
0 | 8 | 16..21 | 52..61 | 88..93 | 232..239 => 231,
_ => 16,
}
}
#[cfg(feature = "colorsys")]
pub fn invert_bw(color: usize) -> u8 {
let color = Ansi256::new(wrap(color));
let mut rgb = Rgb::from(color);
rgb.grayscale_simple();
rgb.invert();
Ansi256::from(&rgb).code()
}
pub fn is_bright_rgb_band(color: usize) -> bool {
let color = wrap(color);
color >= 0x75
}
pub fn bright_rgb_band(color: usize) -> u8 {
if !is_bright_rgb_band(color) {
0xff - wrap(color)
} else {
wrap(color)
}
}
#[cfg(feature = "colorsys")]
pub fn bright(color: usize) -> u8 {
let color = wrap(color);
let mut rgb = Rgb::from(Ansi256::new(color));
rgb.lighten(50.0);
Ansi256::from(rgb).code()
}
#[cfg(not(feature = "colorsys"))]
pub fn bright(color: usize) -> u8 {
let color = wrap(color);
let [r, g, b] = rgb_from_byte(color);
rgb_to_byte([
bright_rgb_band(r as usize),
bright_rgb_band(g as usize),
bright_rgb_band(b as usize),
])
}
pub fn is_dark_rgb_band(color: usize) -> bool {
let color = wrap(color);
color <= 0x75
}
pub fn dark_rgb_band(color: usize) -> u8 {
let color = wrap(color);
if !is_dark_rgb_band(color as usize) {
color - ((color / 4) * 3)
} else {
color
}
}
#[cfg(feature = "colorsys")]
pub fn dark(color: usize) -> u8 {
let color = wrap(color);
let mut rgb = Rgb::from(Ansi256::new(color));
rgb.lighten(-50.0);
Ansi256::from(rgb).code()
}
#[cfg(not(feature = "colorsys"))]
pub fn dark(color: usize) -> u8 {
let color = wrap(color);
let [r, g, b] = rgb_from_byte(color);
rgb_to_byte([
dark_rgb_band(r as usize),
dark_rgb_band(g as usize),
dark_rgb_band(b as usize),
])
}
pub fn wrap(color: usize) -> u8 {
if color <= u8::MAX.into() {
color as u8
} else {
from_bytes(&color.to_ne_bytes())
}
}
pub fn non_zero_be_bytes(color: usize) -> Vec<u8> {
let mut bytes = color.to_be_bytes().to_vec();
while bytes.len() > 1 && bytes[0] == 0 {
bytes.remove(0);
}
bytes
}
pub fn term_cols() -> usize {
match ioctl_term_light::cols() {
0 => DEFAULT_COLUMNS,
cols => cols as usize,
}
}
pub fn from_byte(byte: u8) -> u8 {
byte
}
pub fn byte(byte: u8) -> String {
let (fg, bg) = couple(from_byte(byte).into());
ansi(byte, fg as usize, bg as usize)
}
pub fn byte_hex(byte: u8) -> String {
let (fg, bg) = couple(from_byte(byte).into());
ansi(format!("0x{byte:02x}"), fg as usize, bg as usize)
}
pub fn byte_bin(byte: u8) -> String {
let (fg, bg) = couple(from_byte(byte).into());
ansi(format!("0b{byte:08b}"), fg as usize, bg as usize)
}
pub const STD_COLORS: [u8; 48] = [
0x00u8, 0x00u8, 0x00u8, 0x80u8, 0x00u8, 0x00u8, 0x00u8, 0x80u8, 0x00u8, 0x80u8, 0x80u8, 0x00u8, 0x00u8, 0x00u8, 0x80u8, 0x80u8, 0x00u8, 0x80u8, 0x00u8, 0x80u8, 0x80u8, 0xc0u8, 0xc0u8, 0xc0u8, 0x80u8, 0x80u8, 0x80u8, 0xffu8, 0x00u8, 0x00u8, 0xffu8, 0xffu8, 0x00u8, 0x00u8, 0xffu8, 0x00u8, 0x00u8, 0x00u8, 0xffu8, 0xffu8, 0x00u8, 0xffu8, 0x00u8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, ];
pub fn cube_ansi_256(color: usize, op: usize) -> u8 {
let color = wrap(color) as usize;
let cube = ((color - 16) / op) % 6;
if cube == 0 {
0u8
} else {
wrap((14135 + 10280 * cube) / 256)
}
}
pub fn get_ansi_rgb(color: usize) -> [u8; 3] {
let tuple = rgb_from_ansi256(wrap(color));
[tuple.0, tuple.1, tuple.2]
}
pub fn format_slice_hex<I: IntoIterator<Item: LowerHex>>(items: I, color: bool) -> String {
format!(
"[{}]",
items
.into_iter()
.map(|el| {
let byte = format!("0x{el:02x}");
if color {
fore(
byte,
from_byte(
u8::from_str_radix(&format!("{el:02x}"), 16).unwrap_or_default(),
)
.into(),
)
} else {
byte
}
})
.collect::<Vec<String>>()
.join(", ")
)
}
pub fn format_slice_display<I: IntoIterator<Item: Display>>(items: I, color: bool) -> String {
format!(
"[{}]",
items
.into_iter()
.map(|el| {
let byte = format!("{el}");
if color {
fore(byte, from_display(el).into())
} else {
byte
}
})
.collect::<Vec<String>>()
.join(", ")
)
}
pub fn format_slice_debug<I: IntoIterator<Item: Debug>>(items: I, color: bool) -> String {
format!(
"[{}]",
items
.into_iter()
.map(|el| {
let byte = format!("{el:#?}");
if color {
fore(byte, from_debug(el).into())
} else {
byte
}
})
.collect::<Vec<String>>()
.join(", ")
)
}