1use crate::ansi::TermEvent;
10use crate::palette::Rgb;
11use crate::screen::{Color, Screen};
12
13pub const COLS: usize = 40;
14pub const ROWS: usize = 25;
15pub const FONT_UPPER: u8 = 32;
17pub const FONT_LOWER: u8 = 33;
18
19pub const PALETTE: [Rgb; 16] = [
21 0x000000, 0xFFFFFF, 0x68372B, 0x70A4B2, 0x6F3D86, 0x588D43, 0x352879, 0xB8C76F, 0x6F4F25, 0x433900, 0x9A6759,
22 0x444444, 0x6C6C6C, 0x9AD284, 0x6C5EB5, 0x959595,
23];
24const DEFAULT_FG: usize = 14;
26
27pub fn color(i: usize) -> Color {
28 Color::Rgb(PALETTE[i & 15])
29}
30
31#[derive(Debug, Default)]
33pub struct Petscii {
34 reverse: bool,
35}
36
37impl Petscii {
38 pub fn start(&mut self, s: &mut Screen) {
40 self.reverse = false;
41 s.pen.fg = color(DEFAULT_FG);
42 s.pen.bg = Color::Rgb(PALETTE[0]);
43 s.look.fonts[0] = FONT_UPPER;
44 s.base_font = FONT_UPPER;
45 s.autowrap = true;
46 s.erase_display(2);
47 s.move_to(0, 0);
48 }
49
50 pub fn byte(&mut self, s: &mut Screen, b: u8, events: &mut Vec<TermEvent>) {
51 let ink = |s: &mut Screen, i: usize| s.pen.fg = color(i);
52 match b {
53 0x05 => ink(s, 1),
54 0x07 => events.push(TermEvent::Bell),
55 0x0D | 0x8D => {
56 s.carriage_return();
57 s.line_feed();
58 self.reverse = false;
59 }
60 0x0E => s.look.fonts[0] = FONT_LOWER,
61 0x8E => s.look.fonts[0] = FONT_UPPER,
62 0x11 => s.move_by(0, 1),
63 0x91 => s.move_by(0, -1),
64 0x1D => right(s),
65 0x9D => left(s),
66 0x12 => self.reverse = true,
67 0x92 => self.reverse = false,
68 0x13 => s.move_to(0, 0),
69 0x93 => {
70 s.erase_display(2);
71 s.move_to(0, 0);
72 }
73 0x14 => {
75 if s.cursor().0 > 0 {
76 s.move_by(-1, 0);
77 s.delete_chars(1);
78 }
79 }
80 0x94 => s.insert_chars(1),
81 0x1C => ink(s, 2),
82 0x1E => ink(s, 5),
83 0x1F => ink(s, 6),
84 0x81 => ink(s, 8),
85 0x90 => ink(s, 0),
86 0x95 => ink(s, 9),
87 0x96 => ink(s, 10),
88 0x97 => ink(s, 11),
89 0x98 => ink(s, 12),
90 0x99 => ink(s, 13),
91 0x9A => ink(s, 14),
92 0x9B => ink(s, 15),
93 0x9C => ink(s, 4),
94 0x9E => ink(s, 7),
95 0x9F => ink(s, 3),
96 _ => {
97 if let Some(code) = screen_code(b) {
98 s.put(if self.reverse { code | 0x80 } else { code });
99 }
100 }
101 }
102 }
103}
104
105fn right(s: &mut Screen) {
107 let (x, y) = s.cursor();
108 if x + 1 < s.cols() {
109 s.move_by(1, 0);
110 } else if y + 1 < s.rows() {
111 s.move_to(0, y + 1);
112 } else {
113 s.carriage_return();
114 s.line_feed();
115 }
116}
117
118fn left(s: &mut Screen) {
120 let (x, y) = s.cursor();
121 if x > 0 {
122 s.move_by(-1, 0);
123 } else if y > 0 {
124 s.move_to(s.cols() - 1, y - 1);
125 }
126}
127
128pub fn screen_code(b: u8) -> Option<u8> {
130 Some(match b {
131 0x20..=0x3F => b,
132 0x40..=0x5F => b - 0x40,
133 0x60..=0x7F => b - 0x20,
134 0xA0..=0xBF => b - 0x40,
135 0xC0..=0xFE => b - 0x80,
136 0xFF => 0x5E,
137 _ => return None,
138 })
139}
140
141pub fn encode_char(c: char) -> Option<u8> {
145 match c {
146 'a'..='z' => Some(c as u8 - b'a' + 0x41),
147 'A'..='Z' => Some(c as u8 - b'A' + 0xC1),
148 ' '..='@' | '[' | ']' => Some(c as u8),
149 '\u{a3}' => Some(0x5C),
150 '\u{2191}' | '^' => Some(0x5E),
151 '\u{2190}' | '_' => Some(0x5F),
152 _ => None,
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use crate::screen::Screen;
160
161 fn run(bytes: &[u8]) -> (Screen, Petscii) {
162 let mut s = Screen::new(COLS, ROWS, 0);
163 let mut p = Petscii::default();
164 p.start(&mut s);
165 let mut ev = Vec::new();
166 for &b in bytes {
167 p.byte(&mut s, b, &mut ev);
168 }
169 (s, p)
170 }
171
172 #[test]
173 fn text_colors_and_reverse() {
174 let (s, _) = run(b"\x05HI\x1c\x12A\x92\x0dX");
176 let row = s.row(0);
177 assert_eq!((row[0].ch, row[1].ch), (0x08, 0x09));
178 assert_eq!(row[0].fg, color(1));
179 assert_eq!((row[2].ch, row[2].fg), (0x01 | 0x80, color(2)));
180 assert_eq!(s.row(1)[0].ch, 0x18);
181 assert_eq!(s.cursor(), (1, 1));
182 }
183
184 #[test]
185 fn cursor_clear_and_charsets() {
186 let (s, _) = run(b"ABC\x9d\x9d\x14\x13\x11\x1d*\x0e");
187 assert_eq!((s.row(0)[0].ch, s.row(0)[1].ch), (0x02, 0x03));
189 assert_eq!(s.row(1)[1].ch, b'*');
190 assert_eq!(s.look.fonts[0], FONT_LOWER);
191 let (s, _) = run(b"ABC\x93");
192 assert!(s.row(0).iter().all(|c| c.ch == b' '));
193 assert_eq!(s.cursor(), (0, 0));
194 assert_eq!(screen_code(0xC1), screen_code(0x61));
196 assert_eq!(screen_code(0xA0), Some(0x60));
197 assert_eq!((encode_char('a'), encode_char('A'), encode_char('1')), (Some(0x41), Some(0xC1), Some(b'1')));
198 }
199}