use crate::ansi::TermEvent;
use crate::palette::Rgb;
use crate::screen::{Cell, Color, Screen, flags};
pub const COLS: usize = 40;
pub const ROWS: usize = 24;
pub const FONT: u8 = 250;
pub const PALETTE: [Rgb; 8] = [0x000000, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF];
fn color(i: u8) -> Color {
Color::Rgb(PALETTE[(i & 7) as usize])
}
pub fn glyph(c: u8) -> u8 {
match c {
b'#' => GLYPH_POUND,
b'[' => 0x1B, b'\\' => GLYPH_HALF,
b']' => 0x1A, b'^' => 0x18, b'_' => b'#',
b'`' => GLYPH_DASH,
b'{' => GLYPH_QUARTER,
b'|' => GLYPH_BARS,
b'}' => GLYPH_THREE_QUARTERS,
b'~' => GLYPH_DIVIDE,
0x7F => GLYPH_BLOCK,
c => c,
}
}
pub const GLYPH_POUND: u8 = 0x01;
pub const GLYPH_HALF: u8 = 0x02;
pub const GLYPH_QUARTER: u8 = 0x03;
pub const GLYPH_BARS: u8 = 0x04;
pub const GLYPH_DASH: u8 = 0x05;
pub const GLYPH_BLOCK: u8 = 0x06;
pub const GLYPH_THREE_QUARTERS: u8 = 0x07;
pub const GLYPH_DIVIDE: u8 = 0x0E;
fn is_code(b: u8) -> bool {
b < 0x20
}
#[derive(Debug)]
pub struct Viewdata {
page: [[u8; COLS]; ROWS],
x: usize,
y: usize,
escape: bool,
}
impl Default for Viewdata {
fn default() -> Viewdata {
Viewdata { page: [[b' '; COLS]; ROWS], x: 0, y: 0, escape: false }
}
}
impl Viewdata {
pub fn start(&mut self, s: &mut Screen) {
*self = Viewdata::default();
s.look.fonts[0] = FONT;
s.base_font = FONT;
s.autowrap = false;
s.ice_colors = false;
self.draw(s);
}
pub fn feed(&mut self, s: &mut Screen, data: &[u8], events: &mut Vec<TermEvent>) {
for &b in data {
self.byte(s, b, events);
}
self.draw(s);
}
fn byte(&mut self, s: &mut Screen, b: u8, events: &mut Vec<TermEvent>) {
let b = if b >= 0xA0 { b & 0x7F } else { b };
if self.escape {
self.escape = false;
if (0x40..=0x5F).contains(&b) {
self.put(b - 0x40);
}
return;
}
match b {
0x07 => events.push(TermEvent::Bell),
0x08 => self.back(),
0x09 => self.forward(),
0x0A => self.y = (self.y + 1) % ROWS,
0x0B => self.y = (self.y + ROWS - 1) % ROWS,
0x0C => {
self.page = [[b' '; COLS]; ROWS];
(self.x, self.y) = (0, 0);
}
0x0D => self.x = 0,
0x11 => s.cursor_visible = true,
0x14 => s.cursor_visible = false,
0x1B => self.escape = true,
0x1E => (self.x, self.y) = (0, 0),
0x20..=0x7F => self.put(b),
0x80..=0x9F => self.put(b - 0x80),
_ => {}
}
}
fn put(&mut self, b: u8) {
self.page[self.y][self.x] = b;
self.forward();
}
fn forward(&mut self) {
self.x += 1;
if self.x == COLS {
self.x = 0;
self.y = (self.y + 1) % ROWS;
}
}
fn back(&mut self) {
if self.x == 0 {
self.x = COLS - 1;
self.y = (self.y + ROWS - 1) % ROWS;
} else {
self.x -= 1;
}
}
pub fn draw(&self, s: &mut Screen) {
let mut above: Option<[Cell; COLS]> = None;
for y in 0..ROWS.min(s.rows()) {
let (row, top) = draw_row(&self.page[y]);
let cells = match above.take() {
Some(top_cells) => top_cells.map(|c| {
if c.flags & flags::DOUBLE_TOP != 0 {
Cell { flags: (c.flags & !flags::DOUBLE_TOP) | flags::DOUBLE_BOTTOM, ..c }
} else {
Cell { ch: b' ', flags: c.flags & !flags::BLINK, ..c }
}
}),
None => {
if top && y + 1 < ROWS {
above = Some(row);
}
if top && y + 1 == ROWS {
row.map(|c| Cell { flags: c.flags & !flags::DOUBLE_TOP, ..c })
} else {
row
}
}
};
for (x, c) in cells.iter().enumerate() {
if let Some(cell) = s.cell_mut(x, y) {
*cell = *c;
}
}
}
s.move_to(self.x, self.y);
}
}
fn draw_row(page: &[u8; COLS]) -> ([Cell; COLS], bool) {
let (mut fg, mut bg) = (7u8, 0u8);
let (mut mosaic, mut separated, mut hold, mut flash, mut conceal, mut double) =
(false, false, false, false, false, false);
let mut held = b' ';
let mut any_double = false;
let mut cells = [Cell::default(); COLS];
for (x, &b) in page.iter().enumerate() {
let ch = if is_code(b) {
match b {
0x09 => flash = false,
0x0C => {
double = false;
hold = false;
held = b' ';
}
0x18 => conceal = true,
0x19 => separated = false,
0x1A => separated = true,
0x1C => bg = 0,
0x1D => bg = fg,
0x1E => hold = true,
_ => {}
}
if hold && mosaic { held } else { b' ' }
} else if mosaic && (b & 0x20 != 0) {
let pattern = (b & 0x1F) | ((b & 0x40) >> 1);
let g = if separated { 0xC0 } else { 0x80 } | pattern;
held = g;
g
} else {
glyph(b)
};
let mut f = 0;
if flash {
f |= flags::BLINK;
}
if conceal {
f |= flags::CONCEAL;
}
if double {
f |= flags::DOUBLE_TOP;
}
cells[x] = Cell { ch, fg: color(fg), bg: color(bg), flags: f, tile: 0 };
if is_code(b) {
match b {
0x01..=0x07 => {
fg = b;
mosaic = false;
conceal = false;
hold = false;
held = b' ';
}
0x11..=0x17 => {
fg = b - 0x10;
mosaic = true;
conceal = false;
}
0x08 => flash = true,
0x0D => {
double = true;
any_double = true;
hold = false;
held = b' ';
}
0x1F => {
hold = false;
held = b' ';
}
_ => {}
}
}
}
(cells, any_double)
}
pub fn encode_char(c: char) -> Option<u8> {
match c {
'\r' | '#' => Some(0x5F),
'£' => Some(0x23),
c if c.is_ascii() && !c.is_ascii_control() => Some(c as u8),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn screen(data: &[u8]) -> Screen {
let mut s = Screen::new(COLS, ROWS, 0);
let mut v = Viewdata::default();
v.start(&mut s);
v.feed(&mut s, data, &mut Vec::new());
s
}
#[test]
fn serial_attributes_take_a_cell_and_color_the_rest() {
let s = screen(b"\x1bAHi\x1bD\x1b]X");
let row = s.row(0);
assert_eq!(row[0].ch, b' ', "the code's own cell is a space");
assert_eq!((row[1].ch, row[1].fg), (b'H', color(1)));
assert_eq!(row[3].ch, b' ');
assert_eq!(row[4].bg, color(4));
assert_eq!((row[5].ch, row[5].fg, row[5].bg), (b'X', color(4), color(4)));
assert_eq!(s.row(1)[0].fg, color(7));
}
#[test]
fn mosaics_hold_and_separation() {
let s = screen(b"\x1bR\x7f!\x1bZ\x7f\x1b^\x1bS");
let row = s.row(0);
assert_eq!(row[1].ch, 0x80 | 0x3F);
assert_eq!(row[2].ch, 0x80 | 0x01);
assert_eq!(row[4].ch, 0xC0 | 0x3F, "separated");
assert_eq!(row[6].ch, 0xC0 | 0x3F);
let s = screen(b"\x1bQA");
assert_eq!(s.row(0)[1].ch, b'A');
}
#[test]
fn overwriting_a_code_recolors_the_row() {
let mut s = Screen::new(COLS, ROWS, 0);
let mut v = Viewdata::default();
v.start(&mut s);
v.feed(&mut s, b"\x1bAred text", &mut Vec::new());
assert_eq!(s.row(0)[5].fg, color(1));
v.feed(&mut s, b"\r\x1bB", &mut Vec::new());
assert_eq!(s.row(0)[5].fg, color(2));
}
#[test]
fn double_height_and_the_page_wrapping() {
let s = screen(b"\x1bMBig\r\nsmall");
assert!(s.row(0)[1].flags & flags::DOUBLE_TOP != 0);
assert_eq!(s.row(1)[1].ch, b'B');
assert!(s.row(1)[1].flags & flags::DOUBLE_BOTTOM != 0);
let s = screen(b"\x0b#");
assert_eq!(s.row(ROWS - 1)[0].ch, GLYPH_POUND);
assert_eq!(s.cursor(), (1, ROWS - 1));
let mut data = vec![0x1E, 0x0B];
data.extend([b'x'; COLS]);
assert_eq!(screen(&data).cursor(), (0, 0));
}
}