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