use super::draw::Cell;
use super::palette::TvColor;
use std::fs::File;
use std::io::{self, Write};
fn write_style_transition<W: Write>(
writer: &mut W,
from: crate::core::palette::Style,
to: crate::core::palette::Style,
) -> io::Result<()> {
use crate::core::palette::Style;
const FLAGS: &[(Style, u8, u8)] = &[
(Style::BOLD, 1, 22),
(Style::DIM, 2, 22),
(Style::ITALIC, 3, 23),
(Style::UNDERLINE, 4, 24),
(Style::REVERSE, 7, 27),
(Style::STRIKETHROUGH, 9, 29),
];
for &(flag, on, off) in FLAGS {
let was = from.contains(flag);
let now = to.contains(flag);
if now && !was {
write!(writer, "\x1b[{}m", on)?;
} else if was && !now {
write!(writer, "\x1b[{}m", off)?;
}
}
Ok(())
}
fn color_to_rgb(color: TvColor) -> (u8, u8, u8) {
match color {
TvColor::Black => (0, 0, 0),
TvColor::Blue => (0, 0, 170),
TvColor::Green => (0, 170, 0),
TvColor::Cyan => (0, 170, 170),
TvColor::Red => (170, 0, 0),
TvColor::Magenta => (170, 0, 170),
TvColor::Brown => (170, 85, 0),
TvColor::LightGray => (170, 170, 170),
TvColor::DarkGray => (85, 85, 85),
TvColor::LightBlue => (85, 85, 255),
TvColor::LightGreen => (85, 255, 85),
TvColor::LightCyan => (85, 255, 255),
TvColor::LightRed => (255, 85, 85),
TvColor::LightMagenta => (255, 85, 255),
TvColor::Yellow => (255, 255, 85),
TvColor::White => (255, 255, 255),
TvColor::Rgb { r, g, b } => (r, g, b),
}
}
pub fn dump_buffer_to_file(
buffer: &[Vec<Cell>],
width: usize,
height: usize,
path: &str,
) -> io::Result<()> {
let mut file = File::create(path)?;
dump_buffer(&mut file, buffer, width, height)?;
Ok(())
}
pub fn dump_buffer<W: Write>(
writer: &mut W,
buffer: &[Vec<Cell>],
width: usize,
height: usize,
) -> io::Result<()> {
for row in buffer.iter().take(height.min(buffer.len())) {
let mut last_fg = None;
let mut last_bg = None;
let mut last_style = crate::core::palette::Style::empty();
for x in 0..width.min(row.len()) {
let cell = row[x];
let need_fg_change = Some(cell.attr.fg) != last_fg;
let need_bg_change = Some(cell.attr.bg) != last_bg;
if need_fg_change || need_bg_change {
if need_fg_change && need_bg_change {
let (fg_r, fg_g, fg_b) = color_to_rgb(cell.attr.fg);
let (bg_r, bg_g, bg_b) = color_to_rgb(cell.attr.bg);
write!(
writer,
"\x1b[38;2;{};{};{};48;2;{};{};{}m",
fg_r, fg_g, fg_b, bg_r, bg_g, bg_b
)?;
} else if need_fg_change {
let (fg_r, fg_g, fg_b) = color_to_rgb(cell.attr.fg);
write!(writer, "\x1b[38;2;{};{};{}m", fg_r, fg_g, fg_b)?;
} else {
let (bg_r, bg_g, bg_b) = color_to_rgb(cell.attr.bg);
write!(writer, "\x1b[48;2;{};{};{}m", bg_r, bg_g, bg_b)?;
}
last_fg = Some(cell.attr.fg);
last_bg = Some(cell.attr.bg);
}
if cell.attr.style != last_style {
write_style_transition(writer, last_style, cell.attr.style)?;
last_style = cell.attr.style;
}
write!(writer, "{}", cell.ch)?;
}
writeln!(writer, "\x1b[0m")?;
}
Ok(())
}
pub fn dump_buffer_region<W: Write>(
writer: &mut W,
buffer: &[Vec<Cell>],
x: usize,
y: usize,
width: usize,
height: usize,
) -> io::Result<()> {
for row in buffer.iter().take((y + height).min(buffer.len())).skip(y) {
let mut last_fg = None;
let mut last_bg = None;
let mut last_style = crate::core::palette::Style::empty();
for col in x..(x + width).min(row.len()) {
let cell = row[col];
let need_fg_change = Some(cell.attr.fg) != last_fg;
let need_bg_change = Some(cell.attr.bg) != last_bg;
if need_fg_change || need_bg_change {
if need_fg_change && need_bg_change {
let (fg_r, fg_g, fg_b) = color_to_rgb(cell.attr.fg);
let (bg_r, bg_g, bg_b) = color_to_rgb(cell.attr.bg);
write!(
writer,
"\x1b[38;2;{};{};{};48;2;{};{};{}m",
fg_r, fg_g, fg_b, bg_r, bg_g, bg_b
)?;
} else if need_fg_change {
let (fg_r, fg_g, fg_b) = color_to_rgb(cell.attr.fg);
write!(writer, "\x1b[38;2;{};{};{}m", fg_r, fg_g, fg_b)?;
} else {
let (bg_r, bg_g, bg_b) = color_to_rgb(cell.attr.bg);
write!(writer, "\x1b[48;2;{};{};{}m", bg_r, bg_g, bg_b)?;
}
last_fg = Some(cell.attr.fg);
last_bg = Some(cell.attr.bg);
}
if cell.attr.style != last_style {
write_style_transition(writer, last_style, cell.attr.style)?;
last_style = cell.attr.style;
}
write!(writer, "{}", cell.ch)?;
}
writeln!(writer, "\x1b[0m")?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::palette::Attr;
#[test]
fn test_dump_simple_buffer() {
let cells = vec![
Cell::new('H', Attr::new(TvColor::White, TvColor::Blue)),
Cell::new('i', Attr::new(TvColor::White, TvColor::Blue)),
];
let buffer = vec![cells];
let mut output = Vec::new();
dump_buffer(&mut output, &buffer, 2, 1).unwrap();
let result = String::from_utf8(output).unwrap();
assert!(result.contains("Hi"));
assert!(result.contains("\x1b[")); }
#[test]
fn test_dump_unstyled_unchanged() {
use crate::core::palette::Attr;
let attr = Attr::new(TvColor::White, TvColor::Blue);
let row = vec![Cell::new('H', attr), Cell::new('i', attr)];
let buffer = vec![row];
let mut out = Vec::new();
dump_buffer(&mut out, &buffer, 2, 1).unwrap();
let s = String::from_utf8(out).unwrap();
assert!(!s.contains(";1m") && !s.contains("[1m"));
assert!(!s.contains("[3m") && !s.contains("[4m"));
}
#[test]
fn test_dump_emits_bold_then_clears() {
use crate::core::palette::Attr;
let bold = Attr::new(TvColor::White, TvColor::Blue).bold();
let plain = Attr::new(TvColor::White, TvColor::Blue);
let row = vec![Cell::new('B', bold), Cell::new('n', plain)];
let buffer = vec![row];
let mut out = Vec::new();
dump_buffer(&mut out, &buffer, 2, 1).unwrap();
let s = String::from_utf8(out).unwrap();
assert!(s.contains("\x1b[1m"), "bold on-code emitted");
assert!(s.contains("\x1b[22m"), "bold off-code emitted before plain char");
}
}