use crate::graphics::{CharFlags, Color, Point, Surface};
use crate::utils::IsWideChar;
use std::io::Write;
use EnumBitFlags::EnumBitFlags;
#[EnumBitFlags]
pub(crate) enum AnsiFlags {
Use16ColorSchema = 1,
}
pub(crate) struct AnsiFormatter {
text: String,
flags: AnsiFlags,
}
impl AnsiFormatter {
pub(crate) fn new(capacity: usize, flags: AnsiFlags) -> Self {
Self {
text: String::with_capacity(capacity),
flags,
}
}
#[inline(always)]
pub(crate) fn text(&self) -> &str {
&self.text
}
pub(crate) fn clear(&mut self) {
self.text.clear();
}
#[allow(dead_code)]
pub(crate) fn move_to_next_line(&mut self) {
self.text.push('\n');
}
pub(crate) fn write_char(&mut self, ch: char) {
if ch < ' ' {
self.text.push(' ');
} else {
self.text.push(ch);
}
}
pub(crate) fn write_string(&mut self, s: &str) {
self.text.push_str(s);
}
#[cfg_attr(windows, allow(dead_code))]
pub(crate) fn enable_mouse_events(&mut self) {
self.text.push_str("\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h");
}
#[cfg_attr(windows, allow(dead_code))]
pub(crate) fn disable_mouse_events(&mut self) {
self.text.push_str("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l");
}
#[inline(always)]
pub(crate) fn reset_screen(&mut self) {
self.text.push_str("\x1b[0m\x1b[2J\x1b[3J\x1b[H");
}
#[inline(always)]
pub(crate) fn set_foreground_color(&mut self, color: Color) {
if self.flags.contains_one(AnsiFlags::Use16ColorSchema) {
self.write_forenground_color_from_scheme(color);
} else {
self.text.push_str("\x1b[38;2;");
self.write_color_as_rgb(color);
self.text.push('m');
}
}
#[inline(always)]
pub(crate) fn set_background_color(&mut self, color: Color) {
if self.flags.contains_one(AnsiFlags::Use16ColorSchema) {
self.write_background_color_from_scheme(color);
} else {
self.text.push_str("\x1b[48;2;");
self.write_color_as_rgb(color);
self.text.push('m');
}
}
#[inline(always)]
pub(crate) fn set_color(&mut self, foreground: Color, background: Color) {
self.set_foreground_color(foreground);
self.set_background_color(background);
}
pub(crate) fn reset_color(&mut self) {
self.text.push_str("\x1b[0m");
}
pub(crate) fn set_cursor_position(&mut self, x: i32, y: i32) {
self.text.push_str("\x1b[");
self.write_number(y + 1);
self.text.push(';');
self.write_number(x + 1);
self.text.push('H');
}
pub(crate) fn update_char_flags(&mut self, flags: CharFlags, old_flags: CharFlags) {
macro_rules! update_ansi_flag {
($flag:ident, $set_value:expr, $reset_value:expr) => {
if flags.contains_one(CharFlags::$flag) {
if !old_flags.contains_one(CharFlags::$flag) {
self.text.push_str($set_value);
}
} else {
if old_flags.contains_one(CharFlags::$flag) {
self.text.push_str($reset_value);
}
}
};
}
update_ansi_flag!(Bold, "\x1b[1m", "\x1b[22m");
update_ansi_flag!(Italic, "\x1b[3m", "\x1b[23m");
update_ansi_flag!(Underline, "\x1b[4m", "\x1b[24m");
update_ansi_flag!(StrikeThrough, "\x1b[9m", "\x1b[29m");
update_ansi_flag!(DoubleUnderline, "\x1b[21m", "\x1b[24m");
update_ansi_flag!(DottedUnderline, "\x1b[4:4m", "\x1b[24m");
update_ansi_flag!(CurlyUnderline, "\x1b[4:3m", "\x1b[24m");
}
pub(crate) fn set_char_flags(&mut self, flags: CharFlags) {
macro_rules! set_ansi_flag {
($flag:ident, $set_value:expr, $reset_value:expr) => {
if flags.contains_one(CharFlags::$flag) {
self.text.push_str($set_value);
} else {
self.text.push_str($reset_value);
}
};
}
set_ansi_flag!(Bold, "\x1b[1m", "\x1b[22m");
set_ansi_flag!(Italic, "\x1b[3m", "\x1b[23m");
set_ansi_flag!(Underline, "\x1b[4m", "\x1b[24m");
set_ansi_flag!(StrikeThrough, "\x1b[9m", "\x1b[29m");
set_ansi_flag!(DoubleUnderline, "\x1b[21m", "\x1b[24m");
set_ansi_flag!(DottedUnderline, "\x1b[4:4m", "\x1b[24m");
set_ansi_flag!(CurlyUnderline, "\x1b[4:3m", "\x1b[24m");
}
pub(crate) fn hide_cursor(&mut self) {
self.text.push_str("\x1b[?25l");
}
pub(crate) fn show_cursor(&mut self) {
self.text.push_str("\x1b[?25h");
}
pub(crate) fn render(&mut self, surface: &Surface, offset: Point) {
self.clear();
self.reset_color();
self.hide_cursor();
let mut x = 0;
let mut y = 0;
let w = surface.size.width;
let h = surface.size.height;
let start_y = offset.y;
let mut f = None;
let mut b = None;
let mut c_flags = CharFlags::None;
let chars = &surface.chars;
while y < h {
self.set_cursor_position(0, y as i32 + start_y);
let ofs = y * w;
while x < w {
let ch = &chars[(ofs + x) as usize];
if Some(ch.foreground) != f {
self.set_foreground_color(ch.foreground);
f = Some(ch.foreground);
}
if Some(ch.background) != b {
self.set_background_color(ch.background);
b = Some(ch.background);
}
if ch.flags != c_flags {
self.update_char_flags(ch.flags, c_flags);
c_flags = ch.flags;
}
if ch.code.is_wide_char() {
self.write_string(" ");
self.set_cursor_position(x as i32, y as i32 + start_y);
self.write_char(ch.code);
x += 2;
self.set_cursor_position(x as i32, y as i32 + start_y);
} else {
self.write_char(ch.code);
x += 1;
}
}
y += 1;
x = 0;
}
if surface.cursor.is_visible() {
self.set_cursor_position(surface.cursor.x as i32 + offset.x, surface.cursor.y as i32 + offset.y);
self.show_cursor();
} else {
self.hide_cursor();
}
}
#[inline(always)]
fn write_forenground_color_from_scheme(&mut self, color: Color) {
match color {
Color::Black => self.text.push_str("\x1b[30m"),
Color::DarkBlue => self.text.push_str("\x1b[34m"),
Color::DarkGreen => self.text.push_str("\x1b[32m"),
Color::Teal => self.text.push_str("\x1b[36m"),
Color::DarkRed => self.text.push_str("\x1b[31m"),
Color::Magenta => self.text.push_str("\x1b[35m"),
Color::Olive => self.text.push_str("\x1b[33m"),
Color::Silver => self.text.push_str("\x1b[37m"),
Color::Gray => self.text.push_str("\x1b[90m"),
Color::Blue => self.text.push_str("\x1b[94m"),
Color::Green => self.text.push_str("\x1b[92m"),
Color::Aqua => self.text.push_str("\x1b[96m"),
Color::Red => self.text.push_str("\x1b[91m"),
Color::Pink => self.text.push_str("\x1b[95m"),
Color::Yellow => self.text.push_str("\x1b[93m"),
Color::White => self.text.push_str("\x1b[97m"),
Color::Transparent => {}
#[cfg(feature = "TRUE_COLORS")]
Color::RGB(r, g, b) => {
self.text.push_str("\x1b[38;2;");
self.write_number(r as i32);
self.text.push(';');
self.write_number(g as i32);
self.text.push(';');
self.write_number(b as i32);
self.text.push('m');
}
}
}
#[inline(always)]
fn write_background_color_from_scheme(&mut self, color: Color) {
match color {
Color::Black => self.text.push_str("\x1b[40m"),
Color::DarkBlue => self.text.push_str("\x1b[44m"),
Color::DarkGreen => self.text.push_str("\x1b[42m"),
Color::Teal => self.text.push_str("\x1b[46m"),
Color::DarkRed => self.text.push_str("\x1b[41m"),
Color::Magenta => self.text.push_str("\x1b[45m"),
Color::Olive => self.text.push_str("\x1b[43m"),
Color::Silver => self.text.push_str("\x1b[47m"),
Color::Gray => self.text.push_str("\x1b[100m"),
Color::Blue => self.text.push_str("\x1b[104m"),
Color::Green => self.text.push_str("\x1b[102m"),
Color::Aqua => self.text.push_str("\x1b[106m"),
Color::Red => self.text.push_str("\x1b[101m"),
Color::Pink => self.text.push_str("\x1b[105m"),
Color::Yellow => self.text.push_str("\x1b[103m"),
Color::White => self.text.push_str("\x1b[107m"),
Color::Transparent => {}
#[cfg(feature = "TRUE_COLORS")]
Color::RGB(r, g, b) => {
self.text.push_str("\x1b[48;2;");
self.write_number(r as i32);
self.text.push(';');
self.write_number(g as i32);
self.text.push(';');
self.write_number(b as i32);
self.text.push('m');
}
}
}
#[inline(always)]
fn write_color_as_rgb(&mut self, color: Color) {
match color {
Color::Black => self.text.push_str("0;0;0"),
Color::DarkRed => self.text.push_str("128;0;0"),
Color::DarkGreen => self.text.push_str("0;128;0"),
Color::Olive => self.text.push_str("128;128;0"),
Color::DarkBlue => self.text.push_str("0;0;128"),
Color::Magenta => self.text.push_str("128;0;128"),
Color::Teal => self.text.push_str("0;128;128"),
Color::Silver => self.text.push_str("196;196;196"),
Color::Gray => self.text.push_str("128;128;128"),
Color::Red => self.text.push_str("255;0;0"),
Color::Green => self.text.push_str("0;255;0"),
Color::Yellow => self.text.push_str("255;255;0"),
Color::Blue => self.text.push_str("0;0;255"),
Color::Pink => self.text.push_str("255;0;255"),
Color::Aqua => self.text.push_str("0;255;255"),
Color::White => self.text.push_str("255;255;255"),
Color::Transparent => self.text.push_str("0;0;0"),
#[cfg(feature = "TRUE_COLORS")]
Color::RGB(r, g, b) => {
self.write_number(r as i32);
self.text.push(';');
self.write_number(g as i32);
self.text.push(';');
self.write_number(b as i32);
}
}
}
fn write_number(&mut self, n: i32) {
let mut n = n;
if n < 0 {
self.text.push('-');
n = -n;
}
let mut buffer = [0u8; 16];
let mut i = 15;
loop {
buffer[i] = (n % 10) as u8 + b'0';
n /= 10;
if n == 0 {
break;
}
i -= 1;
}
let txt = unsafe { std::str::from_utf8_unchecked(&buffer[i..]) };
self.text.push_str(txt);
}
pub(crate) fn execute(&mut self) {
let _ = std::io::stdout().write_all(self.text.as_bytes());
let _ = std::io::stdout().flush();
self.text.clear();
}
}