use crate::color::Color;
use crate::frame::Cell;
#[derive(Clone, Debug)]
pub(crate) struct Glyph {
pub(crate) index: usize,
pub(crate) x: usize,
pub(crate) y: usize,
pub(crate) cell: Cell,
}
#[derive(Clone, Debug)]
pub(crate) struct TextGrid {
pub(crate) width: usize,
pub(crate) height: usize,
pub(crate) glyphs: Vec<Glyph>,
}
impl TextGrid {
pub(crate) fn parse(input: &str, tab_width: usize) -> Self {
let input = if input.is_empty() { "No Input." } else { input };
let tab_width = tab_width.max(1);
let mut glyphs = Vec::new();
let mut x = 0usize;
let mut y = 0usize;
let mut width = 0usize;
let mut style = Cell::default();
let mut chars = input.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\x1b' && chars.peek() == Some(&'[') {
chars.next();
let mut seq = String::new();
for next in chars.by_ref() {
let end = next.is_ascii_alphabetic();
seq.push(next);
if end {
break;
}
}
if seq.ends_with('m') {
apply_sgr(&seq[..seq.len().saturating_sub(1)], &mut style);
}
continue;
}
match ch {
'\n' => {
width = width.max(x);
x = 0;
y += 1;
}
'\r' => x = 0,
'\t' => {
let spaces = tab_width - (x % tab_width);
for _ in 0..spaces {
push_glyph(&mut glyphs, &style, ' ', x, y);
x += 1;
}
}
_ => {
push_glyph(&mut glyphs, &style, ch, x, y);
x += 1;
}
}
}
width = width.max(x).max(1);
let height = (y + 1).max(1);
Self {
width,
height,
glyphs,
}
}
}
fn push_glyph(glyphs: &mut Vec<Glyph>, style: &Cell, ch: char, x: usize, y: usize) {
let mut cell = style.clone();
cell.ch = ch;
glyphs.push(Glyph {
index: glyphs.len(),
x,
y,
cell,
});
}
fn apply_sgr(params: &str, style: &mut Cell) {
let values: Vec<u16> = if params.trim().is_empty() {
vec![0]
} else {
params
.split(';')
.filter_map(|part| part.parse::<u16>().ok())
.collect()
};
let mut i = 0;
while i < values.len() {
match values[i] {
0 => *style = Cell::default(),
1 => style.bold = true,
2 => style.dim = true,
3 => style.italic = true,
4 => style.underline = true,
22 => {
style.bold = false;
style.dim = false;
}
23 => style.italic = false,
24 => style.underline = false,
30..=37 => style.colors.fg = Some(basic_color(values[i] as u8 - 30, false)),
40..=47 => style.colors.bg = Some(basic_color(values[i] as u8 - 40, false)),
90..=97 => style.colors.fg = Some(basic_color(values[i] as u8 - 90, true)),
100..=107 => style.colors.bg = Some(basic_color(values[i] as u8 - 100, true)),
38 | 48 => {
let is_fg = values[i] == 38;
if values.get(i + 1) == Some(&2) && i + 4 < values.len() {
let color = Color::rgb(
values[i + 2] as u8,
values[i + 3] as u8,
values[i + 4] as u8,
);
if is_fg {
style.colors.fg = Some(color)
} else {
style.colors.bg = Some(color)
}
i += 4;
} else if values.get(i + 1) == Some(&5) && i + 2 < values.len() {
let color = Color::from_xterm(values[i + 2] as u8);
if is_fg {
style.colors.fg = Some(color)
} else {
style.colors.bg = Some(color)
}
i += 2;
}
}
39 => style.colors.fg = None,
49 => style.colors.bg = None,
_ => {}
}
i += 1;
}
}
fn basic_color(index: u8, bright: bool) -> Color {
let code = if bright { index + 8 } else { index };
Color::from_xterm(code)
}