1use crate::screen::{Cell, Color, Screen, flags};
16
17const MAX_REPEAT: usize = 64 * 1024;
20
21#[derive(Debug, Default)]
22pub struct Avatar {
23 command: Option<Vec<u8>>,
25 pub insert: bool,
27}
28
29pub enum Outcome {
31 Pass,
33 Done,
35 Show(Vec<u8>),
37}
38
39impl Avatar {
40 pub fn busy(&self) -> bool {
42 self.command.is_some()
43 }
44
45 pub fn byte(&mut self, s: &mut Screen, b: u8) -> Outcome {
46 let Some(cmd) = self.command.as_mut() else {
47 return match b {
48 0x0C => {
49 self.insert = false;
50 set_attr(s, 3);
51 s.erase_display(2);
52 s.move_to(0, 0);
53 Outcome::Done
54 }
55 0x16 | 0x19 => {
56 self.command = Some(vec![b]);
57 Outcome::Done
58 }
59 _ => Outcome::Pass,
60 };
61 };
62 cmd.push(b);
63 let cmd = cmd.clone();
64 match self.complete(&cmd) {
65 Some(n) if n <= cmd.len() => {
66 self.command = None;
67 self.run(s, &cmd)
68 }
69 _ => Outcome::Done,
70 }
71 }
72
73 fn complete(&self, cmd: &[u8]) -> Option<usize> {
75 match cmd {
76 [0x19, ..] => Some(3),
77 [0x16] => None,
78 [0x16, op, rest @ ..] => match op {
79 0x01 => Some(3),
80 0x08 => Some(4),
81 0x0A | 0x0B => Some(7),
82 0x0C => Some(5),
83 0x0D => Some(6),
84 0x19 => rest.first().map(|&n| 3 + n as usize + 1),
86 _ => Some(2),
87 },
88 _ => Some(cmd.len()),
89 }
90 }
91
92 fn run(&mut self, s: &mut Screen, cmd: &[u8]) -> Outcome {
93 if cmd[0] == 0x19 {
94 return Outcome::Show(vec![cmd[1]; cmd[2] as usize]);
95 }
96 let (op, a) = (cmd[1], &cmd[2..]);
97 self.insert = op == 0x09;
99 let (x, y) = s.cursor();
100 match op {
101 0x01 => set_attr(s, a[0]),
102 0x02 => s.pen.flags |= flags::BLINK,
103 0x03 => s.move_by(0, -1),
104 0x04 => s.move_by(0, 1),
105 0x05 => s.move_by(-1, 0),
106 0x06 => s.move_by(1, 0),
107 0x07 => s.erase_line(0),
108 0x08 => s.move_to((a[1] as usize).saturating_sub(1), (a[0] as usize).saturating_sub(1)),
109 0x0A | 0x0B => {
110 let (n, top, left, bottom, right) = (a[0] as usize, a[1], a[2], a[3], a[4]);
111 let area = Area::from_corners(s, top, left, bottom, right);
112 scroll_area(s, area, n, op == 0x0A);
113 }
114 0x0C => {
115 let blank = Cell { ch: b' ', ..attr_cell(s, a[0]) };
116 set_attr(s, a[0]);
117 fill(s, Area { x, y, w: a[2] as usize, h: a[1] as usize }, blank);
118 }
119 0x0D => {
120 let cell = Cell { ch: a[1], ..attr_cell(s, a[0]) };
121 set_attr(s, a[0]);
122 fill(s, Area { x, y, w: a[3] as usize, h: a[2] as usize }, cell);
123 }
124 0x0E => s.delete_chars(1),
125 0x19 => {
126 let n = a[0] as usize;
127 let (pattern, count) = (&a[1..1 + n], a[1 + n] as usize);
128 let total = (pattern.len() * count).min(MAX_REPEAT);
129 return Outcome::Show(pattern.iter().copied().cycle().take(total).collect());
130 }
131 _ => log::debug!("unhandled AVATAR ^V {op:#04x}"),
132 }
133 Outcome::Done
134 }
135}
136
137fn attr_cell(s: &Screen, attr: u8) -> Cell {
140 let mut flags_ = s.pen.flags & !(flags::BOLD | flags::BLINK | flags::REVERSE | flags::FAINT);
141 if attr & 0x80 != 0 {
142 flags_ |= flags::BLINK;
143 }
144 Cell { ch: b' ', fg: Color::Pc(attr & 0x0F), bg: Color::Pc((attr >> 4) & 0x07), flags: flags_ }
145}
146
147fn set_attr(s: &mut Screen, attr: u8) {
148 s.pen = attr_cell(s, attr);
149}
150
151#[derive(Clone, Copy, Debug)]
153struct Area {
154 x: usize,
155 y: usize,
156 w: usize,
157 h: usize,
158}
159
160impl Area {
161 fn from_corners(s: &Screen, top: u8, left: u8, bottom: u8, right: u8) -> Area {
163 let (t, l) = ((top as usize).max(1) - 1, (left as usize).max(1) - 1);
164 let (b, r) = ((bottom as usize).clamp(1, s.rows()) - 1, (right as usize).clamp(1, s.cols()) - 1);
165 Area { x: l, y: t, w: (r + 1).saturating_sub(l), h: (b + 1).saturating_sub(t) }
166 }
167
168 fn clip(self, s: &Screen) -> Area {
169 let x = self.x.min(s.cols());
170 let y = self.y.min(s.rows());
171 Area { x, y, w: self.w.min(s.cols() - x), h: self.h.min(s.rows() - y) }
172 }
173}
174
175fn fill(s: &mut Screen, area: Area, cell: Cell) {
176 let a = area.clip(s);
177 s.fill_rect(a.x, a.y, a.w, a.h, cell);
178}
179
180fn scroll_area(s: &mut Screen, area: Area, n: usize, up: bool) {
183 let a = area.clip(s);
184 let blank = Cell { ch: b' ', ..s.pen };
185 let n = if n == 0 { a.h } else { n.min(a.h) };
186 let rows: Vec<Vec<Cell>> = (a.y..a.y + a.h).map(|y| s.row(y)[a.x..a.x + a.w].to_vec()).collect();
187 for i in 0..a.h {
188 let from = if up { i.checked_add(n).filter(|&f| f < a.h) } else { i.checked_sub(n) };
189 let line = from.map(|f| rows[f].clone()).unwrap_or_else(|| vec![blank; a.w]);
190 for (dx, cell) in line.into_iter().enumerate() {
191 if let Some(c) = s.cell_mut(a.x + dx, a.y + i) {
192 *c = cell;
193 }
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use crate::ansi::{Encoding, Terminal};
201 use crate::screen::Color;
202
203 fn term(data: &[u8]) -> Terminal {
204 let mut t = Terminal::new(80, 25, 0);
205 t.encoding = Encoding::Avatar;
206 t.feed(data);
207 t
208 }
209
210 fn line(t: &Terminal, y: usize) -> String {
211 crate::screen::Screen::row_text(t.screen.row(y)).trim_end().to_string()
212 }
213
214 #[test]
215 fn avt0_codes() {
216 let t = term(b"junk\x0c\x16\x01\x1eHi\x19-\x05\x16\x08\x03\x05X");
218 assert_eq!(line(&t, 0), "Hi-----");
219 let c = t.screen.row(0)[0];
220 assert_eq!((c.fg, c.bg), (Color::Pc(14), Color::Pc(1)));
221 assert_eq!(t.screen.row(2)[4].ch, b'X');
222 let t = term(b"\x0cA");
224 assert_eq!(t.screen.row(0)[0].fg, Color::Pc(3));
225 }
226
227 #[test]
228 fn avt0_plus_codes() {
229 let t = term(b"ABC\x16\x08\x01\x01\x16\x09XY\x16\x06Z");
231 assert_eq!(line(&t, 0), "XYAZC");
232 let t = term(b"\x16\x19\x02ab\x03\x1b[2;1HOK");
234 assert_eq!((line(&t, 0), line(&t, 1)), ("ababab".into(), "OK".into()));
235 let t = term(b"\x16\x08\x02\x02\x16\x0d\x07#\x02\x03\x16\x0a\x01\x02\x02\x03\x04");
237 assert_eq!((line(&t, 1), line(&t, 2)), (" ###".into(), "".into()));
238 let t = term(b"ABC\x16\x08\x01\x01\x16\x0e");
240 assert_eq!(line(&t, 0), "BC");
241 }
242}