1use std::collections::VecDeque;
5
6use crate::palette::{self, Rgb, VGA};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Color {
13 Pc(u8),
15 Xterm(u8),
17 Rgb(Rgb),
19}
20
21pub mod flags {
22 pub const BOLD: u8 = 1 << 0;
23 pub const BLINK: u8 = 1 << 1;
24 pub const UNDERLINE: u8 = 1 << 2;
25 pub const REVERSE: u8 = 1 << 3;
26 pub const CONCEAL: u8 = 1 << 4;
27 pub const FAINT: u8 = 1 << 5;
28 pub const DOUBLE_TOP: u8 = 1 << 6;
30 pub const DOUBLE_BOTTOM: u8 = 1 << 7;
32}
33
34pub const DEFAULT_FG: Color = Color::Pc(7);
35pub const DEFAULT_BG: Color = Color::Pc(0);
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct Cell {
39 pub ch: u8,
41 pub fg: Color,
42 pub bg: Color,
43 pub flags: u8,
44 pub tile: u32,
47}
48
49impl Default for Cell {
50 fn default() -> Self {
51 Cell { ch: b' ', fg: DEFAULT_FG, bg: DEFAULT_BG, flags: 0, tile: 0 }
52 }
53}
54
55impl Cell {
56 pub fn new(ch: u8, fg: Color, bg: Color) -> Self {
57 Cell { ch, fg, bg, flags: 0, tile: 0 }
58 }
59
60 pub fn is_blank(&self) -> bool {
61 (self.ch == b' ' || self.ch == 0) && self.bg == DEFAULT_BG && self.flags & flags::REVERSE == 0 && self.tile == 0
62 }
63
64 pub fn resolve(&self, ice: bool) -> (Rgb, Rgb, bool) {
69 self.resolve_with(ice, Look::standard())
70 }
71
72 pub fn resolve_with(&self, ice: bool, look: &Look) -> (Rgb, Rgb, bool) {
74 let bold = self.flags & flags::BOLD != 0 && !look.bright_plain;
75 let blink = self.flags & flags::BLINK != 0;
76 let mut fg = match self.fg {
77 Color::Pc(i) if bold && i < 8 => look.rgb(Color::Pc(i + 8)),
78 c => look.rgb(c),
79 };
80 let mut bg = match self.bg {
81 Color::Pc(i) if blink && ice && i < 8 => look.rgb(Color::Pc(i + 8)),
82 c => look.rgb(c),
83 };
84 if self.flags & flags::REVERSE != 0 {
85 std::mem::swap(&mut fg, &mut bg);
86 }
87 if self.flags & flags::CONCEAL != 0 {
88 fg = bg;
89 }
90 (fg, bg, blink && !ice && !look.blink_steady)
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct Look {
98 pub palette: [Rgb; 256],
101 pub fonts: [u8; 4],
103 pub bright_font: bool,
105 pub bright_plain: bool,
107 pub blink_font: bool,
109 pub blink_steady: bool,
111}
112
113impl Default for Look {
114 fn default() -> Look {
115 Look::standard().clone()
116 }
117}
118
119impl Look {
120 pub fn standard() -> &'static Look {
122 static STANDARD: std::sync::LazyLock<Look> = std::sync::LazyLock::new(|| Look {
123 palette: std::array::from_fn(|i| palette::xterm256(i as u8)),
124 fonts: [0; 4],
125 bright_font: false,
126 bright_plain: false,
127 blink_font: false,
128 blink_steady: false,
129 });
130 &STANDARD
131 }
132
133 pub fn rgb(&self, c: Color) -> Rgb {
134 match c {
135 Color::Pc(i) => {
137 let i = i & 15;
138 let ansi = palette::ANSI_TO_VGA.iter().position(|&v| v == i & 7).unwrap_or(0) as u8;
139 self.palette[(ansi + (i & 8)) as usize]
140 }
141 Color::Xterm(i) => self.palette[i as usize],
142 Color::Rgb(rgb) => rgb,
143 }
144 }
145
146 pub fn font_for(&self, cell: &Cell) -> u8 {
148 let bright = self.bright_font && cell.flags & flags::BOLD != 0;
149 let blink = self.blink_font && cell.flags & flags::BLINK != 0;
150 self.fonts[bright as usize | (blink as usize) << 1]
151 }
152}
153
154pub fn color_rgb(c: Color) -> Rgb {
155 match c {
156 Color::Pc(i) => VGA[(i & 15) as usize],
157 Color::Xterm(i) => palette::xterm256(i),
158 Color::Rgb(rgb) => rgb,
159 }
160}
161
162#[derive(Clone, Copy, Debug)]
163struct SavedCursor {
164 x: usize,
165 y: usize,
166 pen: Cell,
167}
168
169pub struct Screen {
170 cols: usize,
171 rows: usize,
172 cells: Vec<Cell>,
173 x: usize,
174 y: usize,
175 pending_wrap: bool,
178 pub pen: Cell,
180 saved: Option<SavedCursor>,
181 scroll_top: usize,
182 scroll_bottom: usize,
183 pub autowrap: bool,
184 pub cursor_visible: bool,
185 pub ice_colors: bool,
186 pub look: Look,
188 pub base_font: u8,
190 pub base_ice: bool,
192 scrollback: VecDeque<Vec<Cell>>,
193 scrollback_limit: usize,
194 pub tiles: Tiles,
196}
197
198impl Screen {
199 pub fn new(cols: usize, rows: usize, scrollback_limit: usize) -> Self {
200 let cols = cols.max(1);
201 let rows = rows.max(1);
202 Screen {
203 cols,
204 rows,
205 cells: vec![Cell::default(); cols * rows],
206 x: 0,
207 y: 0,
208 pending_wrap: false,
209 pen: Cell::default(),
210 saved: None,
211 scroll_top: 0,
212 scroll_bottom: rows - 1,
213 autowrap: true,
214 cursor_visible: true,
215 ice_colors: false,
216 look: Look::default(),
217 base_font: 0,
218 base_ice: false,
219 scrollback: VecDeque::new(),
220 scrollback_limit,
221 tiles: Tiles::default(),
222 }
223 }
224
225 pub fn cols(&self) -> usize {
226 self.cols
227 }
228
229 pub fn rows(&self) -> usize {
230 self.rows
231 }
232
233 pub fn cursor(&self) -> (usize, usize) {
234 (self.x, self.y)
235 }
236
237 pub fn row(&self, y: usize) -> &[Cell] {
238 &self.cells[y * self.cols..(y + 1) * self.cols]
239 }
240
241 pub fn cell_mut(&mut self, x: usize, y: usize) -> Option<&mut Cell> {
242 if x < self.cols && y < self.rows { Some(&mut self.cells[y * self.cols + x]) } else { None }
243 }
244
245 pub fn scrollback_len(&self) -> usize {
246 self.scrollback.len()
247 }
248
249 pub fn scrollback_line(&self, i: usize) -> &[Cell] {
251 &self.scrollback[i]
252 }
253
254 pub fn clear_scrollback(&mut self) {
255 self.scrollback.clear();
256 }
257
258 pub fn reset(&mut self) {
260 self.pen = Cell::default();
261 self.cells.fill(Cell::default());
262 self.x = 0;
263 self.y = 0;
264 self.pending_wrap = false;
265 self.saved = None;
266 self.scroll_top = 0;
267 self.scroll_bottom = self.rows - 1;
268 self.autowrap = true;
269 self.cursor_visible = true;
270 self.ice_colors = self.base_ice;
271 self.look = Look::default();
272 self.look.fonts[0] = self.base_font;
273 }
274
275 pub fn resize(&mut self, cols: usize, rows: usize) {
276 let cols = cols.max(1);
277 let rows = rows.max(1);
278 if cols == self.cols && rows == self.rows {
279 return;
280 }
281 let mut cells = vec![Cell::default(); cols * rows];
282 for y in 0..rows.min(self.rows) {
283 let n = cols.min(self.cols);
284 cells[y * cols..y * cols + n].copy_from_slice(&self.row(y)[..n]);
285 }
286 self.cells = cells;
287 self.cols = cols;
288 self.rows = rows;
289 self.x = self.x.min(cols - 1);
290 self.y = self.y.min(rows - 1);
291 self.scroll_top = 0;
292 self.scroll_bottom = rows - 1;
293 self.pending_wrap = false;
294 }
295
296 fn blank(&self) -> Cell {
299 Cell { ch: b' ', flags: self.pen.flags & (flags::BLINK | flags::REVERSE), ..self.pen }
300 }
301
302 pub fn put(&mut self, ch: u8) {
304 if self.pending_wrap {
305 self.pending_wrap = false;
306 self.x = 0;
307 self.line_feed();
308 }
309 let idx = self.y * self.cols + self.x;
310 self.cells[idx] = Cell { ch, ..self.pen };
311 if self.x + 1 < self.cols {
312 self.x += 1;
313 } else if self.autowrap {
314 self.pending_wrap = true;
315 }
316 }
317
318 pub fn carriage_return(&mut self) {
319 self.x = 0;
320 self.pending_wrap = false;
321 }
322
323 pub fn line_feed(&mut self) {
325 self.pending_wrap = false;
326 if self.y == self.scroll_bottom {
327 self.scroll_up(1);
328 } else if self.y + 1 < self.rows {
329 self.y += 1;
330 }
331 }
332
333 pub fn reverse_index(&mut self) {
335 self.pending_wrap = false;
336 if self.y == self.scroll_top {
337 self.scroll_down(1);
338 } else if self.y > 0 {
339 self.y -= 1;
340 }
341 }
342
343 pub fn backspace(&mut self) {
344 self.pending_wrap = false;
345 self.x = self.x.saturating_sub(1);
346 }
347
348 pub fn tab(&mut self) {
349 self.pending_wrap = false;
350 self.x = ((self.x / 8 + 1) * 8).min(self.cols - 1);
351 }
352
353 pub fn back_tab(&mut self, n: usize) {
354 for _ in 0..n {
355 self.x = self.x.saturating_sub(1) / 8 * 8;
356 }
357 self.pending_wrap = false;
358 }
359
360 pub fn move_to(&mut self, x: usize, y: usize) {
362 self.x = x.min(self.cols - 1);
363 self.y = y.min(self.rows - 1);
364 self.pending_wrap = false;
365 }
366
367 pub fn move_by(&mut self, dx: isize, dy: isize) {
368 let x = (self.x as isize + dx).max(0) as usize;
369 let y = (self.y as isize + dy).max(0) as usize;
370 self.move_to(x, y);
371 }
372
373 pub fn set_scroll_region(&mut self, top: usize, bottom: usize) {
374 let bottom = bottom.min(self.rows - 1);
375 if top < bottom {
376 self.scroll_top = top;
377 self.scroll_bottom = bottom;
378 } else {
379 self.scroll_top = 0;
380 self.scroll_bottom = self.rows - 1;
381 }
382 self.move_to(0, 0);
383 }
384
385 pub fn save_cursor(&mut self) {
386 self.saved = Some(SavedCursor { x: self.x, y: self.y, pen: self.pen });
387 }
388
389 pub fn restore_cursor(&mut self) {
390 if let Some(s) = self.saved {
391 self.move_to(s.x, s.y);
392 self.pen = s.pen;
393 }
394 }
395
396 fn push_scrollback(&mut self, line: Vec<Cell>) {
397 if self.scrollback_limit == 0 {
398 return;
399 }
400 if self.scrollback.len() >= self.scrollback_limit {
401 self.scrollback.pop_front();
402 }
403 self.scrollback.push_back(line);
404 }
405
406 pub fn scroll_up(&mut self, n: usize) {
409 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
410 let n = n.min(bottom - top + 1);
411 if top == 0 && bottom == self.rows - 1 {
412 for y in 0..n {
413 let line = self.row(y).to_vec();
414 self.push_scrollback(line);
415 }
416 }
417 let c = self.cols;
418 self.cells.copy_within((top + n) * c..(bottom + 1) * c, top * c);
419 let blank = self.blank();
420 self.cells[(bottom + 1 - n) * c..(bottom + 1) * c].fill(blank);
421 }
422
423 pub fn scroll_down(&mut self, n: usize) {
424 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
425 let n = n.min(bottom - top + 1);
426 let c = self.cols;
427 self.cells.copy_within(top * c..(bottom + 1 - n) * c, (top + n) * c);
428 let blank = self.blank();
429 self.cells[top * c..(top + n) * c].fill(blank);
430 }
431
432 pub fn erase_display(&mut self, mode: u32) {
438 let blank = self.blank();
439 let pos = self.y * self.cols + self.x;
440 match mode {
441 0 => self.cells[pos..].fill(blank),
442 1 => self.cells[..=pos].fill(blank),
443 _ => {
444 let used = (0..self.rows).rev().find(|&y| self.row(y).iter().any(|c| !c.is_blank()));
445 if let Some(last) = used {
446 for y in 0..=last {
447 let line = self.row(y).to_vec();
448 self.push_scrollback(line);
449 }
450 }
451 self.cells.fill(blank);
452 self.move_to(0, 0);
453 }
454 }
455 self.pending_wrap = false;
456 }
457
458 pub fn erase_line(&mut self, mode: u32) {
460 let blank = self.blank();
461 let start = self.y * self.cols;
462 let (a, b) = match mode {
463 0 => (self.x, self.cols),
464 1 => (0, self.x + 1),
465 _ => (0, self.cols),
466 };
467 self.cells[start + a..start + b].fill(blank);
468 self.pending_wrap = false;
469 }
470
471 pub fn erase_chars(&mut self, n: usize) {
473 let blank = self.blank();
474 let start = self.y * self.cols;
475 let end = (self.x + n).min(self.cols);
476 self.cells[start + self.x..start + end].fill(blank);
477 }
478
479 pub fn insert_chars(&mut self, n: usize) {
481 let blank = self.blank();
482 let start = self.y * self.cols;
483 let n = n.min(self.cols - self.x);
484 let line = &mut self.cells[start..start + self.cols];
485 line.copy_within(self.x..self.cols - n, self.x + n);
486 line[self.x..self.x + n].fill(blank);
487 }
488
489 pub fn delete_chars(&mut self, n: usize) {
491 let blank = self.blank();
492 let start = self.y * self.cols;
493 let n = n.min(self.cols - self.x);
494 let line = &mut self.cells[start..start + self.cols];
495 line.copy_within(self.x + n.., self.x);
496 let cols = self.cols;
497 line[cols - n..].fill(blank);
498 }
499
500 pub fn insert_lines(&mut self, n: usize) {
502 if self.y < self.scroll_top || self.y > self.scroll_bottom {
503 return;
504 }
505 let top = self.scroll_top;
506 self.scroll_top = self.y;
507 self.scroll_down(n);
508 self.scroll_top = top;
509 self.x = 0;
510 }
511
512 pub fn delete_lines(&mut self, n: usize) {
514 if self.y < self.scroll_top || self.y > self.scroll_bottom {
515 return;
516 }
517 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
518 self.scroll_top = self.y;
520 let n = n.min(bottom - self.y + 1);
521 let c = self.cols;
522 self.cells.copy_within((self.y + n) * c..(bottom + 1) * c, self.y * c);
523 let blank = self.blank();
524 self.cells[(bottom + 1 - n) * c..(bottom + 1) * c].fill(blank);
525 self.scroll_top = top;
526 self.x = 0;
527 }
528
529 pub fn draw_text(&mut self, x: usize, y: usize, text: &[u8], fg: Color, bg: Color) {
532 for (i, &ch) in text.iter().enumerate() {
533 if let Some(c) = self.cell_mut(x + i, y) {
534 *c = Cell::new(ch, fg, bg);
535 }
536 }
537 }
538
539 pub fn fill_rect(&mut self, x: usize, y: usize, w: usize, h: usize, cell: Cell) {
540 for yy in y..(y + h).min(self.rows) {
541 for xx in x..(x + w).min(self.cols) {
542 self.cells[yy * self.cols + xx] = cell;
543 }
544 }
545 }
546
547 pub fn cell_px(&self) -> (usize, usize) {
550 (8, if self.rows >= 43 { 8 } else { 16 })
551 }
552
553 pub fn size_px(&self) -> (usize, usize) {
555 let (w, h) = self.cell_px();
556 (self.cols * w, self.rows * h)
557 }
558
559 pub fn place_image(&mut self, img: &crate::sixel::Image, scrolling: bool) {
565 if img.width == 0 || img.height == 0 {
566 return;
567 }
568 let (tw, th) = self.cell_px();
569 let cx = self.x;
570 let rows = img.height.div_ceil(th);
571 let cols = img.width.div_ceil(tw);
572 if !scrolling {
573 for r in 0..rows.min(self.rows) {
574 self.tile_row(img, r, 0, r, cols);
575 }
576 return;
577 }
578 self.pending_wrap = false;
579 let last = img.height / th;
580 for r in 0..=last {
581 if r > 0 {
582 self.line_feed();
583 }
584 if r < rows {
585 let y = self.y;
586 self.tile_row(img, r, cx, y, cols);
587 }
588 }
589 self.x = cx;
590 }
591
592 fn tile_row(&mut self, img: &crate::sixel::Image, r: usize, x0: usize, y: usize, cols: usize) {
594 let (tw, th) = self.cell_px();
595 let (left, top) = ((x0 * tw) as isize, (y * th) as isize);
596 let rect = (left, top, cols * tw, th);
597 self.draw_pixels(rect, |px, py| {
598 let (ix, iy) = ((px - left) as usize, (py - top) as usize + r * th);
599 if ix < img.width && iy < img.height { img.pixels[iy * img.width + ix] } else { 0 }
600 });
601 }
602
603 pub fn draw_pixels(&mut self, rect: (isize, isize, usize, usize), pixel: impl Fn(isize, isize) -> u32) {
607 let (tw, th) = self.cell_px();
608 let (sw, sh) = self.size_px();
609 let (x0, y0) = (rect.0.max(0) as usize, rect.1.max(0) as usize);
610 let x1 = ((rect.0 + rect.2 as isize).max(0) as usize).min(sw);
611 let y1 = ((rect.1 + rect.3 as isize).max(0) as usize).min(sh);
612 if x0 >= x1 || y0 >= y1 {
613 return;
614 }
615 for cy in y0 / th..y1.div_ceil(th) {
616 for cx in x0 / tw..x1.div_ceil(tw) {
617 let idx = cy * self.cols + cx;
618 let mut px = match self.tiles.get(self.cells[idx].tile) {
619 Some(t) if (t.width, t.height) == (tw, th) => t.pixels.to_vec(),
620 _ => vec![0; tw * th],
621 };
622 let mut any = false;
623 for ty in 0..th {
624 let y = cy * th + ty;
625 if y < y0 || y >= y1 {
626 continue;
627 }
628 for tx in 0..tw {
629 let x = cx * tw + tx;
630 if x < x0 || x >= x1 {
631 continue;
632 }
633 let p = pixel(x as isize, y as isize);
634 if p != 0 {
635 px[ty * tw + tx] = p;
636 any = true;
637 }
638 }
639 }
640 if !any {
641 continue;
642 }
643 if self.tiles.full() {
644 let mut used = std::collections::HashSet::new();
645 used.extend(self.cells.iter().map(|c| c.tile));
646 used.extend(self.scrollback.iter().flatten().map(|c| c.tile));
647 self.tiles.collect(&used);
648 }
649 self.cells[idx].tile = self.tiles.add(Tile { width: tw, height: th, pixels: px.into() });
650 }
651 }
652 }
653
654 pub fn pixel_at(&self, x: usize, y: usize) -> crate::palette::Rgb {
658 let (tw, th) = self.cell_px();
659 let Some(cell) = self.cells.get((y / th) * self.cols + x / tw).filter(|_| x / tw < self.cols) else {
660 return 0;
661 };
662 if let Some(t) = self.tiles.get(cell.tile) {
663 let p = t.pixels.get((y % th) * t.width + x % tw).copied().unwrap_or(0);
664 if p != 0 {
665 return p & 0x00FF_FFFF;
666 }
667 }
668 cell.resolve_with(self.ice_colors, &self.look).1
669 }
670
671 pub fn row_text(row: &[Cell]) -> String {
673 let s: String = row.iter().map(|c| crate::cp437::to_unicode(c.ch.max(b' '))).collect();
674 s.trim_end().to_string()
675 }
676}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681
682 fn text(s: &Screen, y: usize) -> String {
683 Screen::row_text(s.row(y))
684 }
685
686 #[test]
687 fn delayed_wrap_at_last_column() {
688 let mut s = Screen::new(4, 3, 10);
689 for &b in b"abcd" {
690 s.put(b);
691 }
692 assert_eq!(s.cursor(), (3, 0));
693 s.carriage_return();
694 s.line_feed();
695 s.put(b'e');
696 assert_eq!(text(&s, 0), "abcd");
697 assert_eq!(text(&s, 1), "e");
698 }
699
700 #[test]
701 fn scrolling_feeds_scrollback() {
702 let mut s = Screen::new(4, 2, 10);
703 for &b in b"ab" {
704 s.put(b);
705 s.carriage_return();
706 s.line_feed();
707 }
708 assert_eq!(s.scrollback_len(), 1);
709 assert_eq!(Screen::row_text(s.scrollback_line(0)), "a");
710 assert_eq!(text(&s, 0), "b");
711 }
712
713 #[test]
714 fn clear_screen_saves_to_scrollback() {
715 let mut s = Screen::new(4, 3, 10);
716 s.put(b'x');
717 s.erase_display(2);
718 assert_eq!(s.scrollback_len(), 1);
719 assert_eq!(s.cursor(), (0, 0));
720 }
721
722 #[test]
723 fn insert_delete_chars() {
724 let mut s = Screen::new(5, 1, 0);
725 for &b in b"abcde" {
726 s.put(b);
727 }
728 s.move_to(1, 0);
729 s.delete_chars(2);
730 assert_eq!(text(&s, 0), "ade");
731 s.insert_chars(1);
732 assert_eq!(text(&s, 0), "a de");
733 }
734
735 #[test]
736 fn ice_colors_resolve() {
737 let c = Cell { ch: b'A', fg: Color::Pc(1), bg: Color::Pc(4), flags: flags::BOLD | flags::BLINK, tile: 0 };
738 assert_eq!(c.resolve(false), (VGA[9], VGA[4], true));
739 assert_eq!(c.resolve(true), (VGA[9], VGA[12], false));
740 }
741}
742
743#[derive(Clone, Debug, PartialEq, Eq)]
746pub struct Tile {
747 pub width: usize,
748 pub height: usize,
749 pub pixels: Box<[u32]>,
750}
751
752#[derive(Debug, Default)]
755pub struct Tiles {
756 store: Vec<Option<Tile>>,
757 free: Vec<u32>,
758 next_collect: usize,
760}
761
762impl Tiles {
763 pub fn get(&self, id: u32) -> Option<&Tile> {
764 id.checked_sub(1).and_then(|i| self.store.get(i as usize)).and_then(|t| t.as_ref())
765 }
766
767 fn full(&self) -> bool {
768 self.free.is_empty() && self.store.len() >= self.next_collect.max(4096)
769 }
770
771 fn collect(&mut self, used: &std::collections::HashSet<u32>) {
773 self.free.clear();
774 for (i, t) in self.store.iter_mut().enumerate() {
775 let id = i as u32 + 1;
776 if !used.contains(&id) {
777 *t = None;
778 self.free.push(id);
779 }
780 }
781 self.next_collect = (self.store.len() - self.free.len()) * 2;
782 }
783
784 fn add(&mut self, tile: Tile) -> u32 {
785 match self.free.pop() {
786 Some(id) => {
787 self.store[id as usize - 1] = Some(tile);
788 id
789 }
790 None => {
791 self.store.push(Some(tile));
792 self.store.len() as u32
793 }
794 }
795 }
796}