1use crate::ansi::TermEvent;
18use crate::palette::Rgb;
19use crate::screen::{Cell, Color, Screen, flags};
20
21pub const COLS: usize = 40;
22pub const ROWS: usize = 24;
23pub const FONT: u8 = 250;
25
26pub const PALETTE: [Rgb; 8] = [0x000000, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF];
28
29fn color(i: u8) -> Color {
30 Color::Rgb(PALETTE[(i & 7) as usize])
31}
32
33pub fn glyph(c: u8) -> u8 {
36 match c {
37 b'#' => GLYPH_POUND,
38 b'[' => 0x1B, b'\\' => GLYPH_HALF,
40 b']' => 0x1A, b'^' => 0x18, b'_' => b'#',
43 b'`' => GLYPH_DASH,
44 b'{' => GLYPH_QUARTER,
45 b'|' => GLYPH_BARS,
46 b'}' => GLYPH_THREE_QUARTERS,
47 b'~' => GLYPH_DIVIDE,
48 0x7F => GLYPH_BLOCK,
49 c => c,
50 }
51}
52
53pub const GLYPH_POUND: u8 = 0x01;
55pub const GLYPH_HALF: u8 = 0x02;
56pub const GLYPH_QUARTER: u8 = 0x03;
57pub const GLYPH_BARS: u8 = 0x04;
58pub const GLYPH_DASH: u8 = 0x05;
59pub const GLYPH_BLOCK: u8 = 0x06;
60pub const GLYPH_THREE_QUARTERS: u8 = 0x07;
61pub const GLYPH_DIVIDE: u8 = 0x0E;
62
63fn is_code(b: u8) -> bool {
65 b < 0x20
66}
67
68#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
70pub enum Kind {
71 #[default]
72 Prestel,
73 Beeb,
79}
80
81pub const BEEB_ROWS: usize = 25;
83
84#[derive(Debug)]
85pub struct Viewdata {
86 pub kind: Kind,
87 page: Vec<[u8; COLS]>,
90 x: usize,
91 y: usize,
92 escape: bool,
94 pub reveal: bool,
96 vdu: (u8, usize, Vec<u8>),
99}
100
101impl Default for Viewdata {
102 fn default() -> Viewdata {
103 Viewdata::new(Kind::Prestel)
104 }
105}
106
107impl Viewdata {
108 pub fn new(kind: Kind) -> Viewdata {
109 let rows = if kind == Kind::Beeb { BEEB_ROWS } else { ROWS };
110 Viewdata {
111 kind,
112 page: vec![[b' '; COLS]; rows],
113 x: 0,
114 y: 0,
115 escape: false,
116 reveal: false,
117 vdu: (0, 0, Vec::new()),
118 }
119 }
120
121 fn rows(&self) -> usize {
122 self.page.len()
123 }
124
125 pub fn start(&mut self, s: &mut Screen) {
126 *self = Viewdata::new(self.kind);
127 s.look.fonts[0] = FONT;
128 s.base_font = FONT;
129 s.autowrap = false;
130 s.ice_colors = false;
131 self.draw(s);
132 }
133
134 pub fn toggle_reveal(&mut self, s: &mut Screen) -> bool {
136 self.reveal = !self.reveal;
137 self.draw(s);
138 self.reveal
139 }
140
141 pub fn feed(&mut self, s: &mut Screen, data: &[u8], events: &mut Vec<TermEvent>) {
143 for &b in data {
144 match self.kind {
145 Kind::Prestel => self.byte(s, b, events),
146 Kind::Beeb => self.beeb_byte(s, b, events),
147 }
148 }
149 self.draw(s);
150 }
151
152 fn byte(&mut self, s: &mut Screen, b: u8, events: &mut Vec<TermEvent>) {
153 let b = if b >= 0xA0 { b & 0x7F } else { b };
154 if self.escape {
155 self.escape = false;
156 if (0x40..=0x5F).contains(&b) {
159 self.put(b - 0x40);
160 }
161 return;
162 }
163 let rows = self.rows();
164 match b {
165 0x07 => events.push(TermEvent::Bell),
166 0x08 => self.back(),
167 0x09 => self.forward(),
168 0x0A => self.y = (self.y + 1) % rows,
169 0x0B => self.y = (self.y + rows - 1) % rows,
170 0x0C => self.clear(),
171 0x0D => self.x = 0,
172 0x11 => s.cursor_visible = true,
173 0x14 => s.cursor_visible = false,
174 0x1B => self.escape = true,
175 0x1E => (self.x, self.y) = (0, 0),
176 0x20..=0x7F => self.put(b),
177 0x80..=0x9F => self.put(b - 0x80),
179 _ => {}
180 }
181 }
182
183 fn beeb_byte(&mut self, s: &mut Screen, b: u8, events: &mut Vec<TermEvent>) {
185 if self.vdu.1 > 0 {
187 self.vdu.2.push(b);
188 self.vdu.1 -= 1;
189 if self.vdu.1 == 0 {
190 let args = std::mem::take(&mut self.vdu.2);
191 match self.vdu.0 {
192 23 if args.first() == Some(&1) => s.cursor_visible = args.get(1).copied().unwrap_or(1) != 0,
193 28 => {
194 let (x, y) = (args[0].saturating_sub(0x20) as usize, args[1].saturating_sub(0x20) as usize);
195 if x < COLS && y < self.rows() {
196 (self.x, self.y) = (x, y);
197 }
198 }
199 _ => {}
200 }
201 }
202 return;
203 }
204 let b = if b >= 0xA0 { b & 0x7F } else { b };
205 match b {
206 0x07 => events.push(TermEvent::Bell),
207 0x08 => self.beeb_back(),
208 0x09 => self.beeb_forward(),
209 0x0A => self.beeb_down(),
210 0x0B => {
211 if self.y == 0 {
212 self.page.rotate_right(1);
213 self.page[0] = [b' '; COLS];
214 } else {
215 self.y -= 1;
216 }
217 }
218 0x0C => self.clear(),
219 0x0D => self.x = 0,
220 0x17 => self.vdu = (23, 9, Vec::new()),
221 0x1C => self.vdu = (28, 2, Vec::new()),
222 0x1E => (self.x, self.y) = (0, 0),
223 0x7F => {
225 self.beeb_back();
226 self.page[self.y][self.x] = b' ';
227 }
228 0x23 => self.beeb_put(0x5F),
231 0x5F => self.beeb_put(0x60),
232 0x60 => self.beeb_put(0x23),
233 0x20..=0x7E => self.beeb_put(b),
234 0x80..=0x9F => self.beeb_put(b - 0x80),
235 _ => {}
236 }
237 }
238
239 fn beeb_put(&mut self, b: u8) {
240 self.page[self.y][self.x] = b;
241 self.beeb_forward();
242 }
243
244 fn beeb_down(&mut self) {
246 if self.y + 1 == self.rows() {
247 self.page.rotate_left(1);
248 let last = self.rows() - 1;
249 self.page[last] = [b' '; COLS];
250 } else {
251 self.y += 1;
252 }
253 }
254
255 fn beeb_forward(&mut self) {
256 self.x += 1;
257 if self.x == COLS {
258 self.x = 0;
259 self.beeb_down();
260 }
261 }
262
263 fn beeb_back(&mut self) {
264 if self.x > 0 {
265 self.x -= 1;
266 return;
267 }
268 self.x = COLS - 1;
269 if self.y == 0 {
270 self.page.rotate_right(1);
271 self.page[0] = [b' '; COLS];
272 } else {
273 self.y -= 1;
274 }
275 }
276
277 fn clear(&mut self) {
278 self.page.fill([b' '; COLS]);
279 (self.x, self.y) = (0, 0);
280 self.reveal = false;
281 }
282
283 fn put(&mut self, b: u8) {
284 self.page[self.y][self.x] = b;
285 self.forward();
286 }
287
288 fn forward(&mut self) {
290 self.x += 1;
291 if self.x == COLS {
292 self.x = 0;
293 self.y = (self.y + 1) % self.rows();
294 }
295 }
296
297 fn back(&mut self) {
298 if self.x == 0 {
299 self.x = COLS - 1;
300 self.y = (self.y + self.rows() - 1) % self.rows();
301 } else {
302 self.x -= 1;
303 }
304 }
305
306 pub fn draw(&self, s: &mut Screen) {
308 let rows = self.rows();
309 let mut above: Option<[Cell; COLS]> = None;
310 for y in 0..rows.min(s.rows()) {
311 let (row, top) = draw_row(&self.page[y], self.reveal);
312 let cells = match above.take() {
313 Some(top_cells) if self.kind == Kind::Prestel => top_cells.map(|c| {
316 if c.flags & flags::DOUBLE_TOP != 0 {
317 Cell { flags: (c.flags & !flags::DOUBLE_TOP) | flags::DOUBLE_BOTTOM, ..c }
318 } else {
319 Cell { ch: b' ', flags: c.flags & !flags::BLINK, ..c }
320 }
321 }),
322 Some(_) => row.map(|c| {
323 if c.flags & flags::DOUBLE_TOP != 0 {
324 Cell { flags: (c.flags & !flags::DOUBLE_TOP) | flags::DOUBLE_BOTTOM, ..c }
325 } else {
326 c
327 }
328 }),
329 None => {
330 if top && y + 1 < rows {
331 above = Some(row);
332 }
333 if top && y + 1 == rows {
334 row.map(|c| Cell { flags: c.flags & !flags::DOUBLE_TOP, ..c })
335 } else {
336 row
337 }
338 }
339 };
340 for (x, c) in cells.iter().enumerate() {
341 if let Some(cell) = s.cell_mut(x, y) {
342 *cell = *c;
343 }
344 }
345 }
346 s.move_to(self.x, self.y);
347 }
348}
349
350fn draw_row(page: &[u8; COLS], reveal: bool) -> ([Cell; COLS], bool) {
353 let (mut fg, mut bg) = (7u8, 0u8);
354 let (mut mosaic, mut separated, mut hold, mut flash, mut conceal, mut double) =
355 (false, false, false, false, false, false);
356 let mut held = b' ';
357 let mut any_double = false;
358 let mut cells = [Cell::default(); COLS];
359 for (x, &b) in page.iter().enumerate() {
360 let ch = if is_code(b) {
361 match b {
363 0x09 => flash = false,
364 0x0C => {
365 double = false;
366 hold = false;
367 held = b' ';
368 }
369 0x18 => conceal = true,
370 0x19 => separated = false,
371 0x1A => separated = true,
372 0x1C => bg = 0,
373 0x1D => bg = fg,
374 0x1E => hold = true,
375 _ => {}
376 }
377 if hold && mosaic { held } else { b' ' }
378 } else if mosaic && (b & 0x20 != 0) {
379 let pattern = (b & 0x1F) | ((b & 0x40) >> 1);
381 let g = if separated { 0xC0 } else { 0x80 } | pattern;
382 held = g;
383 g
384 } else {
385 glyph(b)
386 };
387 let mut f = 0;
388 if flash {
389 f |= flags::BLINK;
390 }
391 if conceal && !reveal {
392 f |= flags::CONCEAL;
393 }
394 if double {
395 f |= flags::DOUBLE_TOP;
396 }
397 cells[x] = Cell { ch, fg: color(fg), bg: color(bg), flags: f, tile: 0 };
398 if is_code(b) {
399 match b {
401 0x01..=0x07 => {
402 fg = b;
403 mosaic = false;
404 conceal = false;
405 hold = false;
406 held = b' ';
407 }
408 0x11..=0x17 => {
409 fg = b - 0x10;
410 mosaic = true;
411 conceal = false;
412 }
413 0x08 => flash = true,
414 0x0D => {
415 double = true;
416 any_double = true;
417 hold = false;
418 held = b' ';
419 }
420 0x1F => {
421 hold = false;
422 held = b' ';
423 }
424 _ => {}
425 }
426 }
427 }
428 (cells, any_double)
429}
430
431pub fn beeb_encode_char(c: char) -> Option<u8> {
433 match c {
434 '£' => Some(0x60),
435 '\r' => Some(0x0D),
436 c if c.is_ascii() && !c.is_ascii_control() => Some(c as u8),
437 _ => None,
438 }
439}
440
441pub fn encode_char(c: char) -> Option<u8> {
444 match c {
445 '\r' | '#' => Some(0x5F),
446 '£' => Some(0x23),
447 c if c.is_ascii() && !c.is_ascii_control() => Some(c as u8),
448 _ => None,
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 fn screen(data: &[u8]) -> Screen {
457 let mut s = Screen::new(COLS, ROWS, 0);
458 let mut v = Viewdata::default();
459 v.start(&mut s);
460 v.feed(&mut s, data, &mut Vec::new());
461 s
462 }
463
464 #[test]
465 fn serial_attributes_take_a_cell_and_color_the_rest() {
466 let s = screen(b"\x1bAHi\x1bD\x1b]X");
468 let row = s.row(0);
469 assert_eq!(row[0].ch, b' ', "the code's own cell is a space");
470 assert_eq!((row[1].ch, row[1].fg), (b'H', color(1)));
471 assert_eq!(row[3].ch, b' ');
472 assert_eq!(row[4].bg, color(4));
474 assert_eq!((row[5].ch, row[5].fg, row[5].bg), (b'X', color(4), color(4)));
475 assert_eq!(s.row(1)[0].fg, color(7));
477 }
478
479 #[test]
480 fn mosaics_hold_and_separation() {
481 let s = screen(b"\x1bR\x7f!\x1bZ\x7f\x1b^\x1bS");
484 let row = s.row(0);
485 assert_eq!(row[1].ch, 0x80 | 0x3F);
486 assert_eq!(row[2].ch, 0x80 | 0x01);
487 assert_eq!(row[4].ch, 0xC0 | 0x3F, "separated");
488 assert_eq!(row[6].ch, 0xC0 | 0x3F);
490 let s = screen(b"\x1bQA");
492 assert_eq!(s.row(0)[1].ch, b'A');
493 }
494
495 #[test]
496 fn overwriting_a_code_recolors_the_row() {
497 let mut s = Screen::new(COLS, ROWS, 0);
498 let mut v = Viewdata::default();
499 v.start(&mut s);
500 v.feed(&mut s, b"\x1bAred text", &mut Vec::new());
501 assert_eq!(s.row(0)[5].fg, color(1));
502 v.feed(&mut s, b"\r\x1bB", &mut Vec::new());
504 assert_eq!(s.row(0)[5].fg, color(2));
505 }
506
507 #[test]
508 fn reveal_shows_concealed_text() {
509 let mut s = Screen::new(COLS, ROWS, 0);
510 let mut v = Viewdata::default();
511 v.start(&mut s);
512 v.feed(&mut s, b"\x1bXsecret", &mut Vec::new());
513 assert!(s.row(0)[1].flags & flags::CONCEAL != 0);
514 assert!(v.toggle_reveal(&mut s));
515 assert!(s.row(0)[1].flags & flags::CONCEAL == 0);
516 v.feed(&mut s, b"\x0c\x1bXagain", &mut Vec::new());
518 assert!(s.row(0)[1].flags & flags::CONCEAL != 0);
519 }
520
521 #[test]
522 fn bbc_mode_7() {
523 let mut s = Screen::new(COLS, BEEB_ROWS, 0);
524 let mut v = Viewdata::new(Kind::Beeb);
525 v.start(&mut s);
526 v.feed(&mut s, b"\x81Hi #", &mut Vec::new());
528 assert_eq!((s.row(0)[1].ch, s.row(0)[1].fg), (b'H', color(1)));
529 assert_eq!(s.row(0)[4].ch, b'#');
530 v.feed(&mut s, b"\x1c\x20\x38last\n", &mut Vec::new());
532 assert_eq!(Screen::row_text(s.row(23)), "last");
533 assert_eq!(Screen::row_text(s.row(24)), "");
534 assert_eq!(s.cursor(), (4, 24));
535 v.feed(&mut s, b"\x0c\x8dBIG\r\n\x8dBIG", &mut Vec::new());
537 assert!(s.row(1)[1].flags & flags::DOUBLE_BOTTOM != 0);
538 assert_eq!(s.row(1)[1].ch, b'B');
539 v.feed(&mut s, &[23, 1, 0, 0, 0, 0, 0, 0, 0, 0], &mut Vec::new());
541 assert!(!s.cursor_visible);
542 }
543
544 #[test]
545 fn double_height_and_the_page_wrapping() {
546 let s = screen(b"\x1bMBig\r\nsmall");
547 assert!(s.row(0)[1].flags & flags::DOUBLE_TOP != 0);
548 assert_eq!(s.row(1)[1].ch, b'B');
550 assert!(s.row(1)[1].flags & flags::DOUBLE_BOTTOM != 0);
551 let s = screen(b"\x0b#");
553 assert_eq!(s.row(ROWS - 1)[0].ch, GLYPH_POUND);
554 assert_eq!(s.cursor(), (1, ROWS - 1));
555 let mut data = vec![0x1E, 0x0B];
557 data.extend([b'x'; COLS]);
558 assert_eq!(screen(&data).cursor(), (0, 0));
559 }
560}