use core::fmt;
use std::{collections::HashMap, str::FromStr, usize};
use ordermap::OrderMap;
use crate::{chars::Char, comments::Comments, error::Error, Cell};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Color4 {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Color {
None,
Color4(Color4, bool),
Color256(u8),
RGB(u8, u8, u8),
}
impl Color {
pub fn from_char_builtin(c: Char) -> Self {
match c.char {
'0' => Self::Color4(Color4::Black, false),
'1' => Self::Color4(Color4::Red, false),
'2' => Self::Color4(Color4::Green, false),
'3' => Self::Color4(Color4::Yellow, false),
'4' => Self::Color4(Color4::Blue, false),
'5' => Self::Color4(Color4::Magenta, false),
'6' => Self::Color4(Color4::Cyan, false),
'7' => Self::Color4(Color4::White, false),
'8' => Self::Color4(Color4::Black, true),
'9' => Self::Color4(Color4::Red, true),
'a' => Self::Color4(Color4::Green, true),
'b' => Self::Color4(Color4::Yellow, true),
'c' => Self::Color4(Color4::Blue, true),
'd' => Self::Color4(Color4::Magenta, true),
'e' => Self::Color4(Color4::Cyan, true),
'f' => Self::Color4(Color4::White, true),
_ => Self::None,
}
}
pub fn to_xterm256(self) -> Self {
match self {
Self::None => Self::None,
Self::Color4(c, b) => Self::Color4(c, b),
Self::Color256(c) => Self::Color256(c),
Self::RGB(r, g, b) => Self::Color256(rgb_to_xterm256(r, g, b)),
}
}
pub fn to_durdraw_color(&self) -> usize {
match self.to_xterm256() {
Color::None => 0,
Color::Color4(c, b) => match (c, b) {
(Color4::Black, true) => 241,
(Color4::Black, false) => 232,
(Color4::Red, true) => 196,
(Color4::Red, false) => 88,
(Color4::Green, true) => 46,
(Color4::Green, false) => 34,
(Color4::Yellow, true) => 220,
(Color4::Yellow, false) => 214,
(Color4::Blue, true) => 63,
(Color4::Blue, false) => 62,
(Color4::Magenta, true) => 201,
(Color4::Magenta, false) => 92,
(Color4::Cyan, true) => 51,
(Color4::Cyan, false) => 195,
(Color4::White, true) => 244,
(Color4::White, false) => 238,
},
Color::Color256(c) => match c {
0 => 16, 1 => 88, 2 => 34, 3 => 214, 4 => 62, 5 => 92, 6 => 195, 7 => 238, 8 => 241, 9 => 196, 10 => 46, 11 => 220, 12 => 63, 13 => 201, 14 => 51, 15 => 235, c => c as usize,
},
Color::RGB(_, _, _) => unreachable!(),
}
}
}
impl Default for Color {
fn default() -> Self {
Self::None
}
}
impl FromStr for Color {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim().to_lowercase();
match s.as_str() {
"black" => Ok(Self::Color4(Color4::Black, false)),
"red" => Ok(Self::Color4(Color4::Red, false)),
"green" => Ok(Self::Color4(Color4::Green, false)),
"yellow" => Ok(Self::Color4(Color4::Yellow, false)),
"blue" => Ok(Self::Color4(Color4::Blue, false)),
"magenta" => Ok(Self::Color4(Color4::Magenta, false)),
"cyan" => Ok(Self::Color4(Color4::Cyan, false)),
"white" => Ok(Self::Color4(Color4::White, false)),
"bright-black" => Ok(Self::Color4(Color4::Black, true)),
"gray" => Ok(Self::Color4(Color4::Black, true)),
"grey" => Ok(Self::Color4(Color4::Black, true)),
"bright-red" => Ok(Self::Color4(Color4::Red, true)),
"bright-green" => Ok(Self::Color4(Color4::Green, true)),
"bright-yellow" => Ok(Self::Color4(Color4::Yellow, true)),
"bright-blue" => Ok(Self::Color4(Color4::Blue, true)),
"bright-magenta" => Ok(Self::Color4(Color4::Magenta, true)),
"bright-cyan" => Ok(Self::Color4(Color4::Cyan, true)),
"bright-white" => Ok(Self::Color4(Color4::White, true)),
s => match s.parse::<u8>() {
Ok(c) => Ok(Self::Color256(c)),
Err(_) => {
let err = Error::ColorParsing(String::from(s));
if s.len() != 6 {
return Err(err);
}
let r = u8::from_str_radix(&s[0..2], 16).map_err(|_| err.clone())?;
let g = u8::from_str_radix(&s[2..4], 16).map_err(|_| err.clone())?;
let b = u8::from_str_radix(&s[4..6], 16).map_err(|_| err.clone())?;
Ok(Self::RGB(r, g, b))
}
},
}
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Color::None => write!(f, ""),
Color::Color4(c, b) => {
let mut prefix = "";
if *b {
prefix = "bright-";
};
write!(
f,
"{}{}",
prefix,
match c {
Color4::Black => "black",
Color4::Red => "red",
Color4::Green => "green",
Color4::Yellow => "yellow",
Color4::Blue => "blue",
Color4::Magenta => "magenta",
Color4::Cyan => "cyan",
Color4::White => "white",
}
)
}
Color::Color256(c) => write!(f, "{}", c),
Color::RGB(r, g, b) => write!(f, "{:02x}{:02x}{:02x}", r, g, b),
}
}
}
impl Color {
pub fn to_ansi(&self, is_fg: bool) -> String {
match self {
Color::None => {
let code = if is_fg { 39 } else { 49 };
format!("\x1b[{}m", code)
}
Color::Color4(col, bright) => {
let idx = match col {
Color4::Black => 0,
Color4::Red => 1,
Color4::Green => 2,
Color4::Yellow => 3,
Color4::Blue => 4,
Color4::Magenta => 5,
Color4::Cyan => 6,
Color4::White => 7,
};
if *bright {
let code = if is_fg { 90 + idx } else { 100 + idx };
format!("\x1b[{}m", code)
} else {
let code = if is_fg { 30 + idx } else { 40 + idx };
format!("\x1b[{}m", code)
}
}
Color::Color256(n) => {
let prefix = if is_fg { "38" } else { "48" };
format!("\x1b[{};5;{}m", prefix, n)
}
Color::RGB(r, g, b) => {
let prefix = if is_fg { "38" } else { "48" };
format!("\x1b[{};2;{};{};{}m", prefix, r, g, b)
}
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ColorPair {
pub fg: Color,
pub bg: Color,
}
impl ColorPair {
pub fn invert(&self) -> Self {
Self {
fg: self.bg,
bg: self.fg,
}
}
pub fn to_ansi(&self) -> String {
return self.fg.to_ansi(true) + self.bg.to_ansi(false).as_str();
}
pub fn to_ansi_rel(&self, prev: &Option<Self>) -> String {
if Some(*self) != *prev {
self.to_ansi()
} else {
"".into()
}
}
pub fn from_char_builtin(c: Char) -> Self {
Self {
fg: Color::from_char_builtin(c),
bg: Color::None,
}
}
}
impl fmt::Display for ColorPair {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.fg == Color::None && self.bg == Color::None {
return Ok(());
}
if self.fg == Color::None {
return write!(f, "bg:{}", self.bg);
}
if self.bg == Color::None {
return write!(f, "fg:{}", self.fg);
}
write!(f, "fg:{} bg:{}", self.fg, self.bg)
}
}
impl FromStr for ColorPair {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut pair = Self::default();
let mut fg_oc = false;
let mut bg_oc = false;
for ss in s.split(" ") {
let ss = ss.trim();
if ss.is_empty() {
continue;
}
if let Some(fgs) = ss.strip_prefix("fg:") {
if fg_oc {
return Err(Error::ColorDuplicate(String::from("fg"), String::from(s)));
}
pair.fg = fgs.parse::<Color>()?;
fg_oc = true;
continue;
}
if let Some(bgs) = ss.strip_prefix("bg:") {
if bg_oc {
return Err(Error::ColorDuplicate(String::from("bg"), String::from(s)));
}
pair.bg = bgs.parse::<Color>()?;
bg_oc = true;
continue;
}
return Err(Error::ColorParsing(String::from(s)));
}
Ok(pair)
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Palette {
pub palette: OrderMap<Char, (ColorPair, Comments)>,
}
impl Palette {
pub fn strip_comments(&mut self) {
let keys: Vec<Char> = self.palette.keys().map(|k| k.clone()).collect();
for key in keys {
if let Some((pair, _)) = self.palette.get(&key) {
self.palette.insert(key, (*pair, Vec::new()));
}
}
}
pub fn len(&self) -> usize {
self.palette.len()
}
pub fn search_color(&self, col: ColorPair) -> Option<Char> {
for (k, v) in &self.palette {
if v.0 == col {
return Some(*k);
}
}
for k in "_0123456789abcdef".chars() {
let c = Char::new_must(k);
if ColorPair::from_char_builtin(c) == col {
return Some(c);
}
}
None
}
pub fn contains_color(&self, name: Char) -> bool {
self.palette.contains_key(&name)
}
pub fn get_color(&self, name: Char) -> ColorPair {
if let Some((pair, _)) = self.palette.get(&name) {
*pair
} else {
ColorPair::from_char_builtin(name)
}
}
pub fn set_color(&mut self, name: Char, col: ColorPair) {
if ColorPair::from_char_builtin(name) == col {
self.palette.remove(&name);
} else {
self.palette.insert(name, (col, Vec::new()));
}
}
pub fn remove_color(&mut self, name: Char) {
self.palette.remove(&name);
}
pub(crate) fn add_parsing_color(
&mut self,
name: Char,
pair: ColorPair,
comments: Vec<String>,
) -> Result<(), Error> {
if self.palette.contains_key(&name) {
return Err(Error::ColorMapDup(name.into()));
}
self.palette.insert(name, (pair, comments));
Ok(())
}
}
impl fmt::Display for Palette {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (name, mapping) in &self.palette {
for c in &mapping.1 {
writeln!(f, ";; {}", c)?;
}
writeln!(f, "col {} {}", name, mapping.0)?;
}
Ok(())
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct CSSColorMap {
pub map: HashMap<(Color, bool), String>,
}
impl CSSColorMap {
pub fn map_opt(&self, color: Option<Color>, foreground: bool) -> String {
let color = if let Some(color) = color {
color
} else {
Color::None
};
self.map(color, foreground)
}
pub fn map(&self, color: Color, foreground: bool) -> String {
if let Some(s) = self.map.get(&(color, foreground)) {
s.chars()
.filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_' || *c == '#')
.collect()
} else {
match (color, foreground) {
(Color::None, true) => "#ffffff".into(),
(Color::None, false) => "#000000".into(),
(Color::Color4(Color4::Black, false), _) => "#000000".into(),
(Color::Color4(Color4::Black, true), _) => "#4e4e4e".into(),
(Color::Color4(Color4::Red, false), _) => "#800000".into(),
(Color::Color4(Color4::Red, true), _) => "#ff0000".into(),
(Color::Color4(Color4::Green, false), _) => "#008000".into(),
(Color::Color4(Color4::Green, true), _) => "#00ff00".into(),
(Color::Color4(Color4::Yellow, false), _) => "#808000".into(),
(Color::Color4(Color4::Yellow, true), _) => "#ffff00".into(),
(Color::Color4(Color4::Blue, false), _) => "#000080".into(),
(Color::Color4(Color4::Blue, true), _) => "#0000ff".into(),
(Color::Color4(Color4::Magenta, false), _) => "#800080".into(),
(Color::Color4(Color4::Magenta, true), _) => "#ff00ff".into(),
(Color::Color4(Color4::Cyan, false), _) => "#008080".into(),
(Color::Color4(Color4::Cyan, true), _) => "#00ffff".into(),
(Color::Color4(Color4::White, false), _) => "#c0c0c0".into(),
(Color::Color4(Color4::White, true), _) => "#ffffff".into(),
(Color::Color256(c), _) => {
let c = c as usize;
let table16 = [
"#000000", "#800000", "#008000", "#808000", "#000080", "#800080",
"#008080", "#c0c0c0", "#4e4e4e", "#ff0000", "#00ff00", "#ffff00",
"#0000ff", "#ff00ff", "#00ffff", "#ffffff",
];
if c < 16 {
table16[c].to_string()
} else if c < 232 {
let idx = c - 16;
let r = idx / 36;
let g = (idx % 36) / 6;
let b = idx % 6;
let levels: [u8; 6] = [0, 95, 135, 175, 215, 255];
format!("#{:02x}{:02x}{:02x}", levels[r], levels[g], levels[b])
} else {
let gray = 8 + (c - 232) * 10;
format!("#{:02x}{:02x}{:02x}", gray, gray, gray)
}
}
(Color::RGB(r, g, b), _) => format!("#{:02x}{:02x}{:02x}", r, g, b),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color4_basic_fg_bg() {
assert_eq!(
Color::Color4(Color4::Black, false).to_ansi(true),
"\x1b[30m"
);
assert_eq!(
Color::Color4(Color4::White, false).to_ansi(false),
"\x1b[47m"
);
assert_eq!(Color::Color4(Color4::Red, true).to_ansi(true), "\x1b[91m");
assert_eq!(
Color::Color4(Color4::Blue, true).to_ansi(false),
"\x1b[104m"
);
}
#[test]
fn test_color256_sequences() {
assert_eq!(Color::Color256(0).to_ansi(true), "\x1b[38;5;0m");
assert_eq!(Color::Color256(199).to_ansi(true), "\x1b[38;5;199m");
assert_eq!(Color::Color256(255).to_ansi(false), "\x1b[48;5;255m");
}
#[test]
fn test_rgb_sequences() {
assert_eq!(Color::RGB(10, 20, 30).to_ansi(true), "\x1b[38;2;10;20;30m");
assert_eq!(
Color::RGB(255, 128, 0).to_ansi(false),
"\x1b[48;2;255;128;0m"
);
}
#[test]
fn test_all_color4_indices() {
let expected = [
"\x1b[30m", "\x1b[31m", "\x1b[32m", "\x1b[33m", "\x1b[34m", "\x1b[35m", "\x1b[36m", "\x1b[37m", ];
let colors = [
Color4::Black,
Color4::Red,
Color4::Green,
Color4::Yellow,
Color4::Blue,
Color4::Magenta,
Color4::Cyan,
Color4::White,
];
for (c, &exp) in colors.iter().zip(expected.iter()) {
assert_eq!(Color::Color4(c.clone(), false).to_ansi(true), exp);
}
}
#[test]
fn test_none_color_resets() {
assert_eq!(Color::None.to_ansi(true), "\x1b[39m");
assert_eq!(Color::None.to_ansi(false), "\x1b[49m");
}
}
pub(crate) fn trans_color(leacy: char) -> char {
match leacy {
'0' => '0',
'1' => '4',
'2' => '2',
'3' => '6',
'4' => '1',
'5' => '5',
'6' => '3',
'7' => '7',
'8' => '8',
'9' => 'c',
'a' => 'a',
'b' => 'e',
'c' => '9',
'd' => 'd',
'e' => 'b',
'f' => 'f',
_ => '_',
}
}
pub fn rgb_to_xterm256(r: u8, g: u8, b: u8) -> u8 {
const CUBE: [u8; 6] = [0, 95, 135, 175, 215, 255];
let r6 = ((r as u32 * 5 + 127) / 255) as usize;
let g6 = ((g as u32 * 5 + 127) / 255) as usize;
let b6 = ((b as u32 * 5 + 127) / 255) as usize;
let cube_index = 16 + (36 * r6 + 6 * g6 + b6) as u8;
let cr = CUBE[r6] as i32;
let cg = CUBE[g6] as i32;
let cb = CUBE[b6] as i32;
let dr = r as i32 - cr;
let dg = g as i32 - cg;
let db = b as i32 - cb;
let dist_cube = dr * dr + dg * dg + db * db;
let lum = (299 * r as u32 + 587 * g as u32 + 114 * b as u32) / 1000;
let gray_level = ((lum * 23 + 127) / 255) as usize;
let gv = (8 + (gray_level as i32) * 10) as i32; let drg = r as i32 - gv;
let dgg = g as i32 - gv;
let dbg = b as i32 - gv;
let dist_gray = drg * drg + dgg * dgg + dbg * dbg;
if dist_gray < dist_cube {
(232 + gray_level as u8) as u8
} else {
cube_index
}
}
pub(crate) fn num_to_color4(i: usize) -> Option<Color4> {
match i {
0 => Some(Color4::Black),
1 => Some(Color4::Red),
2 => Some(Color4::Green),
3 => Some(Color4::Yellow),
4 => Some(Color4::Blue),
5 => Some(Color4::Magenta),
6 => Some(Color4::Cyan),
7 => Some(Color4::White),
_ => None,
}
}
pub(crate) fn apply_sgr(params: &[i32], fg: &mut Color, bg: &mut Color) {
if params.is_empty() {
*fg = Color::None;
*bg = Color::None;
return;
}
let mut i = 0;
while i < params.len() {
match params[i] {
0 => {
*fg = Color::None;
*bg = Color::None;
i += 1;
}
39 => {
*fg = Color::None;
i += 1;
}
49 => {
*bg = Color::None;
i += 1;
}
30..=37 => {
if let Some(c) = num_to_color4((params[i] - 30) as usize) {
*fg = Color::Color4(c, false);
}
i += 1;
}
90..=97 => {
if let Some(c) = num_to_color4((params[i] - 90) as usize) {
*fg = Color::Color4(c, true);
}
i += 1;
}
40..=47 => {
if let Some(c) = num_to_color4((params[i] - 40) as usize) {
*bg = Color::Color4(c, false);
}
i += 1;
}
100..=107 => {
if let Some(c) = num_to_color4((params[i] - 100) as usize) {
*bg = Color::Color4(c, true);
}
i += 1;
}
38 | 48 => {
let target_fg = params[i] == 38;
if i + 1 >= params.len() {
break;
}
match params[i + 1] {
5 => {
if i + 2 < params.len() && (0..=255).contains(¶ms[i + 2]) {
let color = Color::Color256(params[i + 2] as u8);
if target_fg {
*fg = color;
} else {
*bg = color;
}
i += 3;
} else {
i += 2;
}
}
2 => {
if i + 4 < params.len()
&& (0..=255).contains(¶ms[i + 2])
&& (0..=255).contains(¶ms[i + 3])
&& (0..=255).contains(¶ms[i + 4])
{
let color = Color::RGB(
params[i + 2] as u8,
params[i + 3] as u8,
params[i + 4] as u8,
);
if target_fg {
*fg = color;
} else {
*bg = color;
}
i += 5;
} else {
i += 2;
}
}
_ => {
i += 2;
}
}
}
_ => {
i += 1;
}
}
}
}