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