use crate::ansi::TermEvent;
use crate::palette::Rgb;
use crate::screen::{Color, Screen};
pub const COLS: usize = 40;
pub const ROWS: usize = 25;
pub const FONT_UPPER: u8 = 32;
pub const FONT_LOWER: u8 = 33;
pub const PALETTE: [Rgb; 16] = [
0x000000, 0xFFFFFF, 0x68372B, 0x70A4B2, 0x6F3D86, 0x588D43, 0x352879, 0xB8C76F, 0x6F4F25, 0x433900, 0x9A6759,
0x444444, 0x6C6C6C, 0x9AD284, 0x6C5EB5, 0x959595,
];
const DEFAULT_FG: usize = 14;
pub fn color(i: usize) -> Color {
Color::Rgb(PALETTE[i & 15])
}
#[derive(Debug, Default)]
pub struct Petscii {
reverse: bool,
}
impl Petscii {
pub fn start(&mut self, s: &mut Screen) {
self.reverse = false;
s.pen.fg = color(DEFAULT_FG);
s.pen.bg = Color::Rgb(PALETTE[0]);
s.look.fonts[0] = FONT_UPPER;
s.base_font = FONT_UPPER;
s.autowrap = true;
s.erase_display(2);
s.move_to(0, 0);
}
pub fn byte(&mut self, s: &mut Screen, b: u8, events: &mut Vec<TermEvent>) {
let ink = |s: &mut Screen, i: usize| s.pen.fg = color(i);
match b {
0x05 => ink(s, 1),
0x07 => events.push(TermEvent::Bell),
0x0D | 0x8D => {
s.carriage_return();
s.line_feed();
self.reverse = false;
}
0x0E => s.look.fonts[0] = FONT_LOWER,
0x8E => s.look.fonts[0] = FONT_UPPER,
0x11 => s.move_by(0, 1),
0x91 => s.move_by(0, -1),
0x1D => right(s),
0x9D => left(s),
0x12 => self.reverse = true,
0x92 => self.reverse = false,
0x13 => s.move_to(0, 0),
0x93 => {
s.erase_display(2);
s.move_to(0, 0);
}
0x14 => {
if s.cursor().0 > 0 {
s.move_by(-1, 0);
s.delete_chars(1);
}
}
0x94 => s.insert_chars(1),
0x1C => ink(s, 2),
0x1E => ink(s, 5),
0x1F => ink(s, 6),
0x81 => ink(s, 8),
0x90 => ink(s, 0),
0x95 => ink(s, 9),
0x96 => ink(s, 10),
0x97 => ink(s, 11),
0x98 => ink(s, 12),
0x99 => ink(s, 13),
0x9A => ink(s, 14),
0x9B => ink(s, 15),
0x9C => ink(s, 4),
0x9E => ink(s, 7),
0x9F => ink(s, 3),
_ => {
if let Some(code) = screen_code(b) {
s.put(if self.reverse { code | 0x80 } else { code });
}
}
}
}
}
fn right(s: &mut Screen) {
let (x, y) = s.cursor();
if x + 1 < s.cols() {
s.move_by(1, 0);
} else if y + 1 < s.rows() {
s.move_to(0, y + 1);
} else {
s.carriage_return();
s.line_feed();
}
}
fn left(s: &mut Screen) {
let (x, y) = s.cursor();
if x > 0 {
s.move_by(-1, 0);
} else if y > 0 {
s.move_to(s.cols() - 1, y - 1);
}
}
pub fn screen_code(b: u8) -> Option<u8> {
Some(match b {
0x20..=0x3F => b,
0x40..=0x5F => b - 0x40,
0x60..=0x7F => b - 0x20,
0xA0..=0xBF => b - 0x40,
0xC0..=0xFE => b - 0x80,
0xFF => 0x5E,
_ => return None,
})
}
pub fn encode_char(c: char) -> Option<u8> {
match c {
'a'..='z' => Some(c as u8 - b'a' + 0x41),
'A'..='Z' => Some(c as u8 - b'A' + 0xC1),
' '..='@' | '[' | ']' => Some(c as u8),
'\u{a3}' => Some(0x5C),
'\u{2191}' | '^' => Some(0x5E),
'\u{2190}' | '_' => Some(0x5F),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::screen::Screen;
fn run(bytes: &[u8]) -> (Screen, Petscii) {
let mut s = Screen::new(COLS, ROWS, 0);
let mut p = Petscii::default();
p.start(&mut s);
let mut ev = Vec::new();
for &b in bytes {
p.byte(&mut s, b, &mut ev);
}
(s, p)
}
#[test]
fn text_colors_and_reverse() {
let (s, _) = run(b"\x05HI\x1c\x12A\x92\x0dX");
let row = s.row(0);
assert_eq!((row[0].ch, row[1].ch), (0x08, 0x09));
assert_eq!(row[0].fg, color(1));
assert_eq!((row[2].ch, row[2].fg), (0x01 | 0x80, color(2)));
assert_eq!(s.row(1)[0].ch, 0x18);
assert_eq!(s.cursor(), (1, 1));
}
#[test]
fn cursor_clear_and_charsets() {
let (s, _) = run(b"ABC\x9d\x9d\x14\x13\x11\x1d*\x0e");
assert_eq!((s.row(0)[0].ch, s.row(0)[1].ch), (0x02, 0x03));
assert_eq!(s.row(1)[1].ch, b'*');
assert_eq!(s.look.fonts[0], FONT_LOWER);
let (s, _) = run(b"ABC\x93");
assert!(s.row(0).iter().all(|c| c.ch == b' '));
assert_eq!(s.cursor(), (0, 0));
assert_eq!(screen_code(0xC1), screen_code(0x61));
assert_eq!(screen_code(0xA0), Some(0x60));
assert_eq!((encode_char('a'), encode_char('A'), encode_char('1')), (Some(0x41), Some(0xC1), Some(b'1')));
}
}