1use std::time::{Duration, Instant};
20
21use crate::palette::Rgb;
22
23static FONT8: &[u8; 2048] = include_bytes!("../assets/vga_8x8.bin");
24static FONT16: &[u8; 4096] = include_bytes!("../assets/vga_8x16.bin");
25static FONT6: &[u8; 1536] = include_bytes!("../assets/font6x6.bin");
27
28pub const MAX_WIDTH: usize = 640;
29pub const MAX_HEIGHT: usize = 400;
30pub const COLS: usize = 80;
32pub const ROWS: usize = 25;
33const VERSION: &[u8] = b"2.19";
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum Res {
38 Low,
39 Medium,
40 High,
41}
42
43impl Res {
44 pub fn size(self) -> (usize, usize) {
45 match self {
46 Res::Low => (320, 200),
47 Res::Medium => (640, 200),
48 Res::High => (640, 400),
49 }
50 }
51
52 fn colors(self) -> u8 {
53 match self {
54 Res::Low => 16,
55 Res::Medium => 4,
56 Res::High => 2,
57 }
58 }
59
60 fn cell_h(self) -> usize {
62 if self == Res::High { 16 } else { 8 }
63 }
64
65 fn register(self, pen: u8) -> u8 {
67 const LOW: [u8; 16] = [0, 15, 1, 2, 4, 6, 3, 5, 7, 8, 9, 10, 12, 14, 11, 13];
68 match self {
69 Res::Low => LOW[(pen & 15) as usize],
70 Res::Medium => [0, 3, 1, 2].get(pen as usize).copied().unwrap_or(3),
71 Res::High => (pen != 0) as u8,
72 }
73 }
74
75 fn aspect(self) -> f64 {
77 match self {
78 Res::Low => 338.0 / 372.0,
79 Res::Medium => 169.0 / 372.0,
80 Res::High => 1.0,
81 }
82 }
83
84 fn number(self) -> u8 {
85 match self {
86 Res::Low => 0,
87 Res::Medium => 1,
88 Res::High => 2,
89 }
90 }
91}
92
93type StColor = u16;
95
96const DESKTOP: [StColor; 16] =
98 [0x777, 0x700, 0x070, 0x770, 0x007, 0x707, 0x077, 0x555, 0x333, 0x733, 0x373, 0x773, 0x337, 0x737, 0x377, 0x000];
99const DESKTOP_MEDIUM: [StColor; 4] = [0x777, 0x700, 0x070, 0x000];
100const IG_PALETTE: [StColor; 16] =
102 [0x777, 0x700, 0x070, 0x000, 0x007, 0x770, 0x077, 0x546, 0x532, 0x743, 0x043, 0x030, 0x333, 0x210, 0x134, 0x000];
103
104fn rgb(c: StColor) -> Rgb {
105 let v = |n: u16| ((n & 7) as u32 * 255 + 3) / 7;
106 v(c >> 8) << 16 | v(c >> 4) << 8 | v(c)
107}
108
109const ROM_UD_PATTERN: [u16; 16] = [
111 0x07E0, 0x0FF0, 0x1FD8, 0x1808, 0x1808, 0x1008, 0x1E78, 0x1348, 0x1108, 0x0810, 0x0B70, 0x0650, 0x07A0, 0x1E20,
112 0x1BC0, 0x1800,
113];
114const DITHER: [u16; 32] = [
115 0x0000, 0x4444, 0x0000, 0x1111, 0x0000, 0x5555, 0x0000, 0x5555, 0x8888, 0x5555, 0x2222, 0x5555, 0xAAAA, 0x5555,
116 0xAAAA, 0x5555, 0xAAAA, 0xDDDD, 0xAAAA, 0x7777, 0xAAAA, 0xFFFF, 0xAAAA, 0xFFFF, 0xEEEE, 0xFFFF, 0xBBBB, 0xFFFF,
117 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
118];
119const OEMPAT: [u16; 128] = [
120 0xFFFF, 0x8080, 0x8080, 0x8080, 0xFFFF, 0x0808, 0x0808, 0x0808, 0x2020, 0x4040, 0x8080, 0x4141, 0x2222, 0x1414,
121 0x0808, 0x1010, 0x0000, 0x0000, 0x1010, 0x2828, 0x0000, 0x0000, 0x0101, 0x8282, 0x0202, 0x0202, 0xAAAA, 0x5050,
122 0x2020, 0x2020, 0xAAAA, 0x0505, 0x4040, 0x8080, 0x0000, 0x0808, 0x0404, 0x0202, 0x0000, 0x2020, 0x6606, 0xC6C6,
123 0xD8D8, 0x1818, 0x8181, 0x8DB1, 0x0C33, 0x6000, 0x0000, 0x0000, 0x0400, 0x0000, 0x0010, 0x0000, 0x8000, 0x0000,
124 0xF8F8, 0x6C6C, 0xC6C6, 0x8F8F, 0x1F1F, 0x3636, 0x6363, 0xF1F1, 0xAAAA, 0x0000, 0x8888, 0x1414, 0x2222, 0x4141,
125 0x8888, 0x0000, 0x0808, 0x0000, 0xAAAA, 0x0000, 0x0808, 0x0000, 0x8888, 0x0000, 0x7777, 0x9898, 0xF8F8, 0xF8F8,
126 0x7777, 0x8989, 0x8F8F, 0x8F8F, 0x8080, 0x8080, 0x4141, 0x3E3E, 0x0808, 0x0808, 0x1414, 0xE3E3, 0x8181, 0x4242,
127 0x2424, 0x1818, 0x0606, 0x0101, 0x8080, 0x8080, 0xF0F0, 0xF0F0, 0xF0F0, 0xF0F0, 0x0F0F, 0x0F0F, 0x0F0F, 0x0F0F,
128 0x0808, 0x1C1C, 0x3E3E, 0x7F7F, 0xFFFF, 0x7F7F, 0x3E3E, 0x1C1C, 0x1111, 0x2222, 0x4444, 0xFFFF, 0x8888, 0x4444,
129 0x2222, 0xFFFF,
130];
131const HATCH0: [u16; 48] = [
132 0x0101, 0x0202, 0x0404, 0x0808, 0x1010, 0x2020, 0x4040, 0x8080, 0x6060, 0xC0C0, 0x8181, 0x0303, 0x0606, 0x0C0C,
133 0x1818, 0x3030, 0x4242, 0x8181, 0x8181, 0x4242, 0x2424, 0x1818, 0x1818, 0x2424, 0x8080, 0x8080, 0x8080, 0x8080,
134 0x8080, 0x8080, 0x8080, 0x8080, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8080,
135 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
136];
137const HATCH1: [u16; 96] = [
138 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000,
139 0x4000, 0x8000, 0x8003, 0x0007, 0x000E, 0x001C, 0x0038, 0x0070, 0x00E0, 0x01C0, 0x0380, 0x0700, 0x0E00, 0x1C00,
140 0x3800, 0x7000, 0xE000, 0xC001, 0x8001, 0x4002, 0x2004, 0x1008, 0x0810, 0x0420, 0x0240, 0x0180, 0x0180, 0x0240,
141 0x0420, 0x0810, 0x1008, 0x2004, 0x4002, 0x8001, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000,
142 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
143 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8080, 0x8080, 0x8080,
144 0x8080, 0x8080, 0x8080, 0x8080, 0xFFFF, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080, 0x8080,
145];
146const LINE_STYLES: [u16; 6] = [0xFFFF, 0xFFF0, 0xC0C0, 0xFF18, 0xFF00, 0xF191];
147const MARKERS: [&[&[(i32, i32)]]; 6] = [
150 &[&[(0, 0), (0, 0)]],
151 &[&[(0, -3), (0, 3)], &[(-4, 0), (4, 0)]],
152 &[&[(0, -3), (0, 3)], &[(3, 2), (-3, -2)], &[(3, -2), (-3, 2)]],
153 &[&[(-4, -3), (4, -3), (4, 3), (-4, 3), (-4, -3)]],
154 &[&[(-4, -3), (4, 3)], &[(-4, 3), (4, -3)]],
155 &[&[(-4, 0), (0, -3), (4, 0), (0, 3), (-4, 0)]],
156];
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160enum Tok {
161 Num(i32),
162 X,
164 Y,
166 Rand,
168 RandBig,
169 Plus(i32),
171 Minus(i32),
172 Bang(i32),
173}
174
175#[derive(Debug)]
177struct Incomplete;
178
179type Res2<T> = Result<T, Incomplete>;
180
181struct Reader<'a> {
182 data: &'a [u8],
183 pos: usize,
184}
185
186impl Reader<'_> {
187 fn next(&mut self) -> Res2<u8> {
188 let b = *self.data.get(self.pos).ok_or(Incomplete)?;
189 self.pos += 1;
190 Ok(b)
191 }
192
193 fn token(&mut self) -> Res2<Tok> {
195 let (mut num, mut digits) = (0i32, 0);
196 let mut sign: Option<u8> = None;
197 loop {
198 match self.next()? {
199 d @ b'0'..=b'9' => {
200 num = num * 10 + (d - b'0') as i32;
201 digits += 1;
202 }
203 b'\r' | b'\n' | b'_' => {}
206 c @ (b'x' | b'y' | b'r' | b'R') if digits == 0 => {
207 self.next()?;
208 return Ok(match c {
209 b'x' => Tok::X,
210 b'y' => Tok::Y,
211 b'r' => Tok::Rand,
212 _ => Tok::RandBig,
213 });
214 }
215 c @ (b'+' | b'-' | b'!') if digits == 0 && sign.is_none() => sign = Some(c),
216 _ => break,
217 }
218 }
219 if digits > 4 {
220 num = 0;
221 }
222 Ok(match sign {
223 Some(b'+') => Tok::Plus(num),
224 Some(b'-') => Tok::Minus(num),
225 Some(_) => Tok::Bang(num),
226 None => Tok::Num(num),
227 })
228 }
229
230 fn text(&mut self) -> Res2<Vec<u8>> {
232 let mut out = Vec::new();
233 loop {
234 match self.next()? {
235 b'@' => return Ok(out),
236 b'\r' | b'\n' => {}
237 b if out.len() < 128 => out.push(b),
238 _ => return Ok(out),
239 }
240 }
241 }
242}
243
244struct Loop {
246 params: Vec<Tok>,
247 at: usize,
248 value: i32,
250 to: i32,
251 step: i32,
252 from: i32,
253 kind: u8,
255 x_count: i32,
256 first: i32,
257 second: i32,
258 was: i32,
259 plain: bool,
261 running: bool,
262}
263
264impl Loop {
265 fn next(&mut self) -> Tok {
266 if self.at == 0 {
267 self.x_count = 0;
268 }
269 let t = self.params[self.at];
270 let relative = !matches!(t, Tok::Num(_) | Tok::Rand | Tok::RandBig);
271 let mut out = t;
272 if relative {
273 self.x_count += 1;
274 if self.second == 0 {
275 self.was = self.value;
276 }
277 if self.x_count < 2 && self.first != 0 {
278 self.value += self.step;
279 self.second += 1;
280 if self.second == 1 && self.kind == b'|' {
281 self.value = self.was;
282 }
283 if self.second == 2 && self.kind == b'|' {
284 self.second = 0;
285 }
286 }
287 self.first += 1;
288 out = Tok::Num(match t {
289 Tok::X => self.value,
290 Tok::Y => match self.from.cmp(&self.to) {
291 std::cmp::Ordering::Less => self.to - self.value,
292 std::cmp::Ordering::Greater => self.from - self.value,
293 std::cmp::Ordering::Equal => 0,
294 },
295 Tok::Plus(n) => n + self.value,
296 Tok::Minus(n) => self.value - n,
297 Tok::Bang(n) => n - self.value,
298 t => return t,
299 });
300 }
301 if self.plain && self.at == 0 {
302 self.value += self.step;
303 }
304 let last = self.at == self.params.len() - 1;
305 if last && (self.step >= 1 && self.value >= self.to || self.step < 0 && self.value <= self.to) {
306 self.running = false;
307 }
308 self.at = (self.at + 1) % self.params.len();
309 out
310 }
311}
312
313#[derive(Clone, Copy)]
314struct LineAttr {
315 pen: u8,
316 style: u8,
317 width: i32,
318 ends: (u8, u8),
320}
321
322#[derive(Clone, Copy)]
323struct FillAttr {
324 pen: u8,
325 interior: u8,
327 style: u8,
328 perimeter: bool,
329}
330
331#[derive(Clone, Copy)]
332struct TextAttr {
333 pen: u8,
334 effects: u8,
335 points: i32,
336 rotation: u8,
338}
339
340#[derive(Clone, Copy)]
341struct Attrs {
342 line: LineAttr,
343 fill: FillAttr,
344 text: TextAttr,
345 marker: (u8, u8, i32),
346 mode: u8,
348}
349
350impl Default for Attrs {
351 fn default() -> Attrs {
352 Attrs {
353 line: LineAttr { pen: 1, style: 1, width: 1, ends: (0, 0) },
354 fill: FillAttr { pen: 1, interior: 1, style: 1, perimeter: true },
355 text: TextAttr { pen: 1, effects: 0, points: 9, rotation: 0 },
356 marker: (1, 1, 1),
357 mode: 1,
358 }
359 }
360}
361
362type Zone = (i32, i32, i32, i32, Vec<u8>);
364
365#[derive(Clone, Copy, Debug, PartialEq, Eq)]
367enum Esc {
368 None,
369 Start,
370 Row,
371 Col(u8),
372 Fg,
373 Bg,
374}
375
376#[derive(Clone, Copy, Debug, PartialEq, Eq)]
377enum Mode {
378 Text,
379 Command,
381}
382
383pub struct Igs {
384 pub res: Res,
385 pixels: Vec<u8>,
386 memory: Vec<u8>,
388 memory_rect: (i32, i32, i32, i32),
389 regs: [StColor; 16],
390 attrs: Attrs,
391 user_patterns: [[u16; 16]; 8],
392 pen_at: (i32, i32),
394 hollow: bool,
395 scaling: i32,
396 rand: (i32, i32),
397 rand_big: (i32, i32),
398 seed: u32,
399 col: usize,
401 row: usize,
402 fg: u8,
403 bg: u8,
404 reverse: bool,
405 wrap: bool,
406 pub cursor_on: bool,
407 destructive_bs: bool,
408 saved: (usize, usize),
409 esc: Esc,
410 mode: Mode,
412 g: bool,
414 buf: Vec<u8>,
415 stored: Vec<u8>,
417 run_stored: Option<Vec<u8>>,
418 pub hold: Option<Instant>,
420 looping: Option<Loop>,
421 last_text: Vec<u8>,
422 zones: Vec<Option<Zone>>,
424 awaiting_zone: Option<(bool, bool)>,
426 pub replies: Vec<u8>,
428 pub bells: usize,
429}
430
431impl Default for Igs {
432 fn default() -> Igs {
433 let mut igs = Igs {
434 res: Res::Medium,
435 pixels: Vec::new(),
436 memory: Vec::new(),
437 memory_rect: (0, 0, 0, 0),
438 regs: DESKTOP,
439 attrs: Attrs::default(),
440 user_patterns: [ROM_UD_PATTERN; 8],
441 pen_at: (0, 0),
442 hollow: false,
443 scaling: 0,
444 rand: (0, 199),
445 rand_big: (0, 199),
446 seed: 0x1234_5678,
447 col: 0,
448 row: 0,
449 fg: 3,
450 bg: 0,
451 reverse: false,
452 wrap: true,
453 cursor_on: true,
454 destructive_bs: false,
455 saved: (0, 0),
456 esc: Esc::None,
457 mode: Mode::Text,
458 g: false,
459 buf: Vec::new(),
460 stored: Vec::new(),
461 run_stored: None,
462 hold: None,
463 looping: None,
464 last_text: Vec::new(),
465 zones: vec![None; 48],
466 awaiting_zone: None,
467 replies: Vec::new(),
468 bells: 0,
469 };
470 igs.set_res(Res::Medium);
471 igs.desktop_palette();
472 igs
473 }
474}
475
476impl Igs {
477 pub fn width(&self) -> usize {
478 self.res.size().0
479 }
480
481 pub fn height(&self) -> usize {
482 self.res.size().1
483 }
484
485 pub fn pixels(&self) -> &[u8] {
487 &self.pixels
488 }
489
490 pub fn palette(&self) -> [Rgb; 16] {
491 std::array::from_fn(|i| rgb(self.regs[i]))
492 }
493
494 pub fn cursor(&self) -> (usize, usize) {
496 match self.res {
497 Res::Low => (self.col * 2, self.row),
498 Res::Medium => (self.col, self.row),
499 Res::High => (self.col, self.row / 2),
500 }
501 }
502
503 fn text_size(&self) -> (usize, usize) {
504 (self.width() / 8, self.height() / self.res.cell_h())
505 }
506
507 fn set_res(&mut self, res: Res) {
508 self.res = res;
509 let (w, h) = res.size();
510 self.pixels = vec![0; w * h];
511 self.memory = vec![0; w * h];
512 let (cols, rows) = self.text_size();
513 self.col = self.col.min(cols - 1);
514 self.row = self.row.min(rows - 1);
515 let mask = res.colors() - 1;
516 self.fg &= mask;
517 self.bg &= mask;
518 if res == Res::Medium {
519 self.fg = 3;
520 }
521 }
522
523 fn desktop_palette(&mut self) {
524 self.regs = DESKTOP;
525 match self.res {
526 Res::Medium => self.regs[..4].copy_from_slice(&DESKTOP_MEDIUM),
527 Res::High => self.regs[..2].copy_from_slice(&[0x777, 0x000]),
528 Res::Low => {}
529 }
530 }
531
532 fn random(&mut self, (lo, hi): (i32, i32)) -> i32 {
533 self.seed ^= self.seed << 13;
534 self.seed ^= self.seed >> 17;
535 self.seed ^= self.seed << 5;
536 lo + (self.seed % ((hi - lo).max(0) as u32 + 1)) as i32
537 }
538
539 pub fn feed(&mut self, data: &[u8]) {
541 self.buf.extend_from_slice(data);
542 let mut buf = std::mem::take(&mut self.buf);
543 let mut pos = 0;
544 while pos < buf.len() && self.hold.is_none() {
545 match self.mode {
546 Mode::Text => {
547 let b = buf[pos];
548 pos += 1;
549 self.text_byte(b);
550 }
551 Mode::Command => {
552 let mut r = Reader { data: &buf, pos };
553 match self.command(&mut r) {
554 Ok(chain) => {
555 pos = r.pos;
556 self.mode = if chain { Mode::Command } else { Mode::Text };
557 if let Some(run) = self.run_stored.take() {
559 buf.splice(pos..pos, run);
560 }
561 }
562 Err(Incomplete) => {
563 self.looping = None;
564 break;
565 }
566 }
567 }
568 }
569 }
570 buf.drain(..pos);
571 if buf.len() > 1 << 20 {
573 buf.clear();
574 self.mode = Mode::Text;
575 }
576 self.buf = buf;
577 }
578
579 pub fn poll(&mut self, now: Instant) {
581 if self.hold.is_some_and(|t| now >= t) {
582 self.hold = None;
583 self.feed(&[]);
584 }
585 }
586
587 fn text_byte(&mut self, b: u8) {
590 if self.g {
591 self.g = false;
592 if b == b'#' && self.esc == Esc::None {
593 self.mode = Mode::Command;
594 return;
595 }
596 self.vt52(b'G');
597 }
598 if b == b'G' && self.esc == Esc::None {
599 self.g = true;
600 return;
601 }
602 self.vt52(b);
603 }
604
605 fn vt52(&mut self, b: u8) {
606 let (cols, rows) = self.text_size();
607 match self.esc {
608 Esc::Start => {
609 self.esc = Esc::None;
610 self.vt52_escape(b);
611 return;
612 }
613 Esc::Row => {
614 self.esc = Esc::Col(b.saturating_sub(32));
615 return;
616 }
617 Esc::Col(r) => {
618 self.esc = Esc::None;
619 self.row = (r as usize).min(rows - 1);
620 self.col = (b.saturating_sub(32) as usize).min(cols - 1);
621 return;
622 }
623 Esc::Fg => {
624 self.esc = Esc::None;
625 self.fg = b & (self.res.colors() - 1);
626 return;
627 }
628 Esc::Bg => {
629 self.esc = Esc::None;
630 self.bg = b & (self.res.colors() - 1);
631 return;
632 }
633 Esc::None => {}
634 }
635 match b {
636 0x07 => self.bells += 1,
637 0x08 => {
638 self.col = self.col.saturating_sub(1);
639 if self.destructive_bs {
640 self.glyph_cell(b' ');
641 }
642 }
643 0x09 => self.col = ((self.col / 8 + 1) * 8).min(cols - 1),
644 0x0A..=0x0C => self.line_feed(),
645 0x0D => self.col = 0,
646 0x1B => self.esc = Esc::Start,
647 0x00..=0x1F => {}
648 _ => {
649 if self.col >= cols {
650 if !self.wrap {
651 self.col = cols - 1;
652 } else {
653 self.col = 0;
654 self.line_feed();
655 }
656 }
657 self.glyph_cell(b);
658 self.col += 1;
659 if self.col >= cols && !self.wrap {
660 self.col = cols - 1;
661 }
662 }
663 }
664 }
665
666 fn vt52_escape(&mut self, b: u8) {
667 let (cols, rows) = self.text_size();
668 match b {
669 b'A' => self.row = self.row.saturating_sub(1),
670 b'B' => self.row = (self.row + 1).min(rows - 1),
671 b'C' => self.col = (self.col + 1).min(cols - 1),
672 b'D' => self.col = self.col.saturating_sub(1),
673 b'E' => {
674 self.clear_text_rows(0, rows);
675 (self.col, self.row) = (0, 0);
676 }
677 b'H' => (self.col, self.row) = (0, 0),
678 b'I' => {
679 if self.row == 0 {
680 self.scroll_text(0, false);
681 } else {
682 self.row -= 1;
683 }
684 }
685 b'J' => {
686 self.clear_line_from(self.col);
687 self.clear_text_rows(self.row + 1, rows);
688 }
689 b'K' => self.clear_line_from(self.col),
690 b'L' => {
691 self.scroll_text(self.row, false);
692 self.col = 0;
693 }
694 b'M' => {
695 self.scroll_text(self.row, true);
696 self.col = 0;
697 }
698 b'Y' => self.esc = Esc::Row,
699 b'b' => self.esc = Esc::Fg,
700 b'c' => self.esc = Esc::Bg,
701 b'd' => {
702 self.clear_text_rows(0, self.row);
703 self.clear_line_to(self.col);
704 }
705 b'e' => self.cursor_on = true,
706 b'f' => self.cursor_on = false,
707 b'j' => self.saved = (self.col, self.row),
708 b'k' => (self.col, self.row) = self.saved,
709 b'l' => self.clear_text_rows(self.row, self.row + 1),
710 b'o' => self.clear_line_to(self.col),
711 b'p' => self.reverse = true,
712 b'q' => self.reverse = false,
713 b'v' => self.wrap = true,
714 b'w' => self.wrap = false,
715 _ => {}
716 }
717 }
718
719 fn line_feed(&mut self) {
720 let rows = self.text_size().1;
721 if self.row + 1 >= rows {
722 self.scroll_text(0, true);
723 } else {
724 self.row += 1;
725 }
726 }
727
728 fn scroll_text(&mut self, top: usize, up: bool) {
731 let (w, h) = self.res.size();
732 let ch = self.res.cell_h();
733 let (start, end) = (top * ch * w, h * w);
734 if start >= end {
735 return;
736 }
737 if up {
738 self.pixels.copy_within(start + ch * w..end, start);
739 self.pixels[end - ch * w..end].fill(self.bg);
740 } else {
741 self.pixels.copy_within(start..end - ch * w, start + ch * w);
742 self.pixels[start..start + ch * w].fill(self.bg);
743 }
744 }
745
746 fn clear_text_rows(&mut self, from: usize, to: usize) {
747 let w = self.width();
748 let ch = self.res.cell_h();
749 let (a, b) = ((from * ch * w).min(self.pixels.len()), (to * ch * w).min(self.pixels.len()));
750 if a < b {
751 self.pixels[a..b].fill(self.bg);
752 }
753 }
754
755 fn clear_cells(&mut self, row: usize, from: usize, to: usize) {
756 let w = self.width();
757 let ch = self.res.cell_h();
758 for y in row * ch..(row + 1) * ch {
759 let line = &mut self.pixels[y * w..(y + 1) * w];
760 line[(from * 8).min(w)..(to * 8).min(w)].fill(self.bg);
761 }
762 }
763
764 fn clear_line_from(&mut self, col: usize) {
765 let cols = self.text_size().0;
766 self.clear_cells(self.row, col, cols);
767 }
768
769 fn clear_line_to(&mut self, col: usize) {
770 self.clear_cells(self.row, 0, col + 1);
771 }
772
773 fn glyph_cell(&mut self, ch: u8) {
775 let w = self.width();
776 let h = self.res.cell_h();
777 let (fg, bg) = if self.reverse { (self.bg, self.fg) } else { (self.fg, self.bg) };
778 let (x0, y0) = (self.col * 8, self.row * h);
779 for gy in 0..h {
780 let bits = glyph_row(ch, gy, h);
781 let row = &mut self.pixels[(y0 + gy) * w + x0..][..8];
782 for (gx, p) in row.iter_mut().enumerate() {
783 *p = if bits & (0x80 >> gx) != 0 { fg } else { bg };
784 }
785 }
786 }
787
788 fn mask(&self) -> u8 {
791 self.res.colors() - 1
792 }
793
794 fn plot(&mut self, x: i32, y: i32, on: bool, color: u8, mode: u8) {
796 let (w, h) = self.res.size();
797 if x < 0 || y < 0 || x as usize >= w || y as usize >= h {
798 return;
799 }
800 let p = &mut self.pixels[y as usize * w + x as usize];
801 match mode {
802 2 if on => *p = color,
803 3 if on => *p ^= self.res.colors() - 1,
804 4 if !on => *p = color,
805 1 => *p = if on { color } else { 0 },
806 _ => {}
807 }
808 }
809
810 fn reg(&self, pen: u8) -> u8 {
811 self.res.register(pen)
812 }
813
814 fn pattern_bit(&self, x: i32, y: i32) -> bool {
816 let f = self.attrs.fill;
817 let fi = f.style.max(1) as usize - 1;
818 let word = match f.interior {
819 0 => return false,
820 1 => return true,
821 2 if fi < 8 => DITHER[fi * 4 + (y & 3) as usize],
822 2 => OEMPAT[((fi - 8).min(15)) * 8 + (y & 7) as usize],
823 3 if fi < 6 => HATCH0[fi * 8 + (y & 7) as usize],
824 3 => HATCH1[((fi - 6).min(5)) * 16 + (y & 15) as usize],
825 _ => self.user_patterns[fi.min(7)][(y & 15) as usize],
826 };
827 word & (0x8000 >> (x & 15)) != 0
828 }
829
830 fn fill_span(&mut self, x0: i32, x1: i32, y: i32) {
832 if self.attrs.fill.interior == 0 {
833 return;
834 }
835 let color = self.reg(self.attrs.fill.pen);
836 let mode = self.attrs.mode;
837 for x in x0.max(0)..=x1.min(self.width() as i32 - 1) {
838 let on = self.pattern_bit(x, y);
839 self.plot(x, y, on, color, mode);
840 }
841 }
842
843 fn polygon(&mut self, pts: &[(i32, i32)], perimeter: bool) {
845 if pts.is_empty() {
846 return;
847 }
848 let (ymin, ymax) = pts.iter().fold((i32::MAX, i32::MIN), |(a, b), p| (a.min(p.1), b.max(p.1)));
849 let h = self.height() as i32;
850 for y in ymin.max(0)..=ymax.min(h - 1) {
851 let mut xs: Vec<i32> = Vec::new();
852 for i in 0..pts.len() {
853 let (a, b) = (pts[i], pts[(i + 1) % pts.len()]);
854 let (lo, hi) = if a.1 < b.1 { (a, b) } else { (b, a) };
855 if y >= lo.1 && y < hi.1 || y == ymax && hi.1 == ymax && lo.1 != hi.1 {
856 let t = (y - lo.1) as f64 / (hi.1 - lo.1) as f64;
857 xs.push((lo.0 as f64 + t * (hi.0 - lo.0) as f64).round() as i32);
858 }
859 }
860 xs.sort_unstable();
861 for pair in xs.chunks(2) {
862 if let [a, b] = pair {
863 self.fill_span(*a, *b, y);
864 }
865 }
866 }
867 if perimeter {
868 let color = self.reg(self.attrs.fill.pen);
869 let mut closed = pts.to_vec();
870 closed.push(pts[0]);
871 for seg in closed.windows(2) {
872 self.thin_line(seg[0], seg[1], color, 0xFFFF, self.attrs.mode);
873 }
874 }
875 }
876
877 fn thin_line(&mut self, (x0, y0): (i32, i32), (x1, y1): (i32, i32), color: u8, style: u16, mode: u8) {
879 let (dx, dy) = ((x1 - x0).abs(), -(y1 - y0).abs());
880 let (sx, sy) = (if x0 < x1 { 1 } else { -1 }, if y0 < y1 { 1 } else { -1 });
881 let (mut x, mut y, mut err) = (x0, y0, dx + dy);
882 let mut bit = 0u32;
883 loop {
884 let on = style.rotate_left(bit) & 0x8000 != 0;
885 if on || mode == 1 || mode == 4 {
887 self.plot(x, y, on, color, mode);
888 }
889 bit += 1;
890 if x == x1 && y == y1 {
891 break;
892 }
893 let e2 = 2 * err;
894 if e2 >= dy {
895 err += dy;
896 x += sx;
897 }
898 if e2 <= dx {
899 err += dx;
900 y += sy;
901 }
902 }
903 }
904
905 fn line(&mut self, a: (i32, i32), b: (i32, i32)) {
908 let l = self.attrs.line;
909 let color = self.reg(l.pen);
910 let style = match l.style {
911 1..=6 => LINE_STYLES[l.style as usize - 1],
912 _ => {
913 let p = self.user_patterns[6];
914 p[0]
915 }
916 };
917 if l.width <= 1 {
918 self.thin_line(a, b, color, style, self.attrs.mode);
919 self.line_ends(a, b, color, 1);
920 return;
921 }
922 let saved = self.attrs.fill;
923 self.attrs.fill = FillAttr { pen: l.pen, interior: 1, style: 1, perimeter: false };
924 let (dx, dy) = ((b.0 - a.0) as f64, (b.1 - a.1) as f64);
925 let len = (dx * dx + dy * dy).sqrt().max(1.0);
926 let half = l.width as f64 / 2.0;
927 let (px, py) = (-dy / len * half, dx / len * half * self.res.aspect());
929 let q = |p: (i32, i32), s: f64| ((p.0 as f64 + px * s).round() as i32, (p.1 as f64 + py * s).round() as i32);
930 self.polygon(&[q(a, 1.0), q(b, 1.0), q(b, -1.0), q(a, -1.0)], false);
931 self.line_ends(a, b, color, l.width);
932 self.attrs.fill = saved;
933 }
934
935 fn line_ends(&mut self, a: (i32, i32), b: (i32, i32), color: u8, width: i32) {
936 let ends = self.attrs.line.ends;
937 for (end, (at, from)) in [(ends.0, (a, b)), (ends.1, (b, a))] {
938 match end {
939 1 => self.arrow(at, from, color, width),
940 2 if width > 1 => {
941 let r = width / 2;
942 let saved = self.attrs.fill;
943 self.attrs.fill = FillAttr { pen: self.attrs.line.pen, interior: 1, style: 1, perimeter: false };
944 let pts = self.ellipse_points(at, r, (r as f64 * self.res.aspect()) as i32, 0, 3600);
945 self.polygon(&pts, false);
946 self.attrs.fill = saved;
947 }
948 _ => {}
949 }
950 }
951 }
952
953 fn arrow(&mut self, tip: (i32, i32), from: (i32, i32), _color: u8, width: i32) {
954 let (dx, dy) = ((tip.0 - from.0) as f64, (tip.1 - from.1) as f64);
955 let len = (dx * dx + dy * dy).sqrt().max(1.0);
956 let size = (8 + 2 * width) as f64;
957 let (ux, uy) = (dx / len, dy / len);
958 let back = (tip.0 as f64 - ux * size, tip.1 as f64 - uy * size);
959 let side = size / 2.0;
960 let p1 = ((back.0 - uy * side) as i32, (back.1 + ux * side * self.res.aspect()) as i32);
961 let p2 = ((back.0 + uy * side) as i32, (back.1 - ux * side * self.res.aspect()) as i32);
962 let saved = self.attrs.fill;
963 self.attrs.fill = FillAttr { pen: self.attrs.line.pen, interior: 1, style: 1, perimeter: false };
964 self.polygon(&[tip, p1, p2], false);
965 self.attrs.fill = saved;
966 }
967
968 fn ellipse_points(&self, c: (i32, i32), rx: i32, ry: i32, start: i32, end: i32) -> Vec<(i32, i32)> {
971 let mut span = end - start;
972 if span <= 0 {
973 span += 3600;
974 }
975 let steps = (rx.max(ry) / 4).clamp(32, 128);
976 (0..=steps)
977 .map(|i| {
978 let a = (start as f64 + span as f64 * i as f64 / steps as f64).to_radians() / 10.0;
979 ((c.0 as f64 + rx as f64 * a.cos()).round() as i32, (c.1 as f64 - ry as f64 * a.sin()).round() as i32)
980 })
981 .collect()
982 }
983
984 fn polyline(&mut self, pts: &[(i32, i32)]) {
985 for seg in pts.windows(2) {
986 self.line(seg[0], seg[1]);
987 }
988 }
989
990 fn shape(&mut self, pts: &[(i32, i32)]) {
992 self.polygon(pts, self.attrs.fill.perimeter);
993 }
994
995 fn rounded_box(&mut self, (x0, y0, x1, y1): (i32, i32, i32, i32), filled: bool) {
996 let (x0, x1) = (x0.min(x1), x0.max(x1));
997 let (y0, y1) = (y0.min(y1), y0.max(y1));
998 let rx = ((x1 - x0) / 2).min(8);
999 let ry = (((y1 - y0) / 2) as f64).min(8.0 * self.res.aspect()) as i32;
1000 let mut pts = Vec::new();
1001 for (cx, cy, a0) in
1002 [(x1 - rx, y0 + ry, 0), (x0 + rx, y0 + ry, 900), (x0 + rx, y1 - ry, 1800), (x1 - rx, y1 - ry, 2700)]
1003 {
1004 pts.extend(self.ellipse_points((cx, cy), rx, ry, a0, a0 + 900));
1005 }
1006 if filled {
1007 self.shape(&pts);
1008 } else {
1009 pts.push(pts[0]);
1010 self.polyline(&pts);
1011 }
1012 }
1013
1014 fn flood(&mut self, x: i32, y: i32) {
1016 let (w, h) = self.res.size();
1017 if x < 0 || y < 0 || x as usize >= w || y as usize >= h {
1018 return;
1019 }
1020 let target = self.pixels[y as usize * w + x as usize];
1021 let mut seen = vec![false; w * h];
1022 let mut stack = vec![(x as usize, y as usize)];
1023 let mut spans = Vec::new();
1024 while let Some((x, y)) = stack.pop() {
1025 if seen[y * w + x] || self.pixels[y * w + x] != target {
1026 continue;
1027 }
1028 let (mut a, mut b) = (x, x);
1029 while a > 0 && !seen[y * w + a - 1] && self.pixels[y * w + a - 1] == target {
1030 a -= 1;
1031 }
1032 while b + 1 < w && !seen[y * w + b + 1] && self.pixels[y * w + b + 1] == target {
1033 b += 1;
1034 }
1035 for i in a..=b {
1036 seen[y * w + i] = true;
1037 for ny in [y.wrapping_sub(1), y + 1] {
1038 if ny < h && !seen[ny * w + i] && self.pixels[ny * w + i] == target {
1039 stack.push((i, ny));
1040 }
1041 }
1042 }
1043 spans.push((a as i32, b as i32, y as i32));
1044 }
1045 for (a, b, y) in spans {
1046 self.fill_span(a, b, y);
1047 }
1048 }
1049
1050 fn marker(&mut self, x: i32, y: i32) {
1051 let (pen, kind, size) = self.attrs.marker;
1052 let color = self.reg(pen);
1053 let shape = MARKERS[(kind.clamp(1, 6) - 1) as usize];
1054 let scale = if kind == 1 { 0.0 } else { size.max(1) as f64 };
1055 for poly in shape {
1056 let pts: Vec<(i32, i32)> =
1057 poly.iter().map(|&(dx, dy)| (x + (dx as f64 * scale) as i32, y + (dy as f64 * scale) as i32)).collect();
1058 for seg in pts.windows(2) {
1059 self.thin_line(seg[0], seg[1], color, 0xFFFF, self.attrs.mode);
1060 }
1061 }
1062 }
1063
1064 fn write_text(&mut self, x: i32, y: i32, text: &[u8]) {
1066 let t = self.attrs.text;
1067 let color = self.reg(t.pen);
1068 let (cell, scale) = match t.points {
1071 ..=8 => (6, 1),
1072 9 => (8, 1),
1073 10..=15 => (16, 1),
1074 16..=17 => (6, 2),
1075 18..=19 => (8, 2),
1076 _ => (16, 2),
1077 };
1078 let ascent = match cell {
1079 6 => 4,
1080 8 => 6,
1081 _ => 13,
1082 } * scale;
1083 let (cw, ch) = (cell.min(8) * scale, cell * scale);
1084 for (i, &c) in text.iter().enumerate() {
1085 for gy in 0..ch {
1086 let mut bits = glyph_row(c, (gy / scale) as usize, cell as usize) as u32;
1087 if t.effects & 1 != 0 {
1088 bits |= bits >> 1;
1089 }
1090 if t.effects & 2 != 0 && gy % 2 == 1 {
1091 bits &= 0x55;
1092 }
1093 if t.effects & 8 != 0 && gy == ascent + scale {
1094 bits = 0xFF;
1095 }
1096 let skew = if t.effects & 4 != 0 { (ch - gy) / 4 } else { 0 };
1097 for gx in 0..cw {
1098 let on = bits & (0x80 >> (gx / scale)) != 0;
1099 if !on && self.attrs.mode != 1 {
1100 continue;
1101 }
1102 let (ax, ay) = ((i as i32) * cw + gx + skew, gy - ascent);
1104 let (px, py) = match t.rotation % 4 {
1105 1 => (x + ay, y - ax),
1106 2 => (x - ax, y - ay),
1107 3 => (x - ay, y + ax),
1108 _ => (x + ax, y + ay),
1109 };
1110 self.plot(px, py, on, color, self.attrs.mode);
1111 }
1112 }
1113 }
1114 }
1115
1116 fn blit(&mut self, from_memory: bool, src: (i32, i32, i32, i32), to_memory: bool, dst: (i32, i32), op: u8) {
1119 let (w, h) = self.res.size();
1120 let (x0, y0, x1, y1) = (src.0.min(src.2), src.1.min(src.3), src.0.max(src.2), src.1.max(src.3));
1121 let source: Vec<u8> = if from_memory { self.memory.clone() } else { self.pixels.clone() };
1122 let mask = self.mask();
1123 for y in 0..=(y1 - y0) {
1124 for x in 0..=(x1 - x0) {
1125 let (sx, sy, dx, dy) = (x0 + x, y0 + y, dst.0 + x, dst.1 + y);
1126 if sx < 0 || sy < 0 || dx < 0 || dy < 0 {
1127 continue;
1128 }
1129 let (sx, sy, dx, dy) = (sx as usize, sy as usize, dx as usize, dy as usize);
1130 if sx >= w || sy >= h || dx >= w || dy >= h {
1131 continue;
1132 }
1133 let s = source[sy * w + sx];
1134 let target = if to_memory { &mut self.memory } else { &mut self.pixels };
1135 let d = target[dy * w + dx];
1136 let v = match op {
1137 0 => 0,
1138 1 => s & d,
1139 2 => s & !d,
1140 4 => !s & d,
1141 5 => d,
1142 6 => s ^ d,
1143 7 => s | d,
1144 8 => !(s | d),
1145 9 => !(s ^ d),
1146 10 => !d,
1147 11 => s | !d,
1148 12 => !s,
1149 13 => !s | d,
1150 14 => !(s & d),
1151 15 => 0xFF,
1152 _ => s,
1153 };
1154 target[dy * w + dx] = v & mask;
1155 }
1156 }
1157 }
1158
1159 fn clear_screen(&mut self) {
1160 self.pixels.fill(0);
1161 }
1162}
1163
1164fn glyph_row(c: u8, y: usize, h: usize) -> u8 {
1167 match h {
1168 16 => FONT16[c as usize * 16 + y.min(15)],
1169 6 if (0x20..0x7F).contains(&c) => FONT6[c as usize * 6 + y.min(5)],
1170 6 => FONT8[c as usize * 8 + (y * 8 / 6).min(7)],
1171 _ => FONT8[c as usize * 8 + y.min(7)],
1172 }
1173}
1174
1175impl Igs {
1177 fn val(&mut self, r: &mut Reader, axis: u8) -> Res2<i32> {
1180 let tok = match self.looping.as_mut() {
1181 Some(lp) if lp.running || lp.at != 0 => lp.next(),
1182 _ => r.token()?,
1183 };
1184 let mut n = match tok {
1185 Tok::Num(n) | Tok::Plus(n) | Tok::Bang(n) => n,
1186 Tok::Minus(n) => -n,
1187 Tok::Rand => self.random(self.rand),
1188 Tok::RandBig => self.random(self.rand_big),
1189 Tok::X | Tok::Y => 0,
1190 };
1191 if self.scaling == 1 && axis > 0 && n > 0 {
1192 let max = if axis == 1 { self.width() - 1 } else { self.height() - 1 } as f64;
1193 n = (n as f64 * max / 9999.0).round() as i32;
1194 } else if self.scaling == 2 && axis == 2 && self.res == Res::High {
1195 n *= 2;
1196 }
1197 Ok(n)
1198 }
1199
1200 fn point(&mut self, r: &mut Reader) -> Res2<(i32, i32)> {
1201 Ok((self.val(r, 1)?, self.val(r, 2)?))
1202 }
1203
1204 fn command(&mut self, r: &mut Reader) -> Res2<bool> {
1207 let mut key = r.next()?;
1208 while key == b'G' && r.data.get(r.pos) == Some(&b'#') {
1210 r.pos += 1;
1211 key = r.next()?;
1212 }
1213 let chain = r.next()? == b'>';
1214 if key == b'&' {
1215 self.loop_command(r)?;
1216 } else {
1217 self.run(key, r)?;
1218 }
1219 Ok(chain)
1220 }
1221
1222 fn loop_command(&mut self, r: &mut Reader) -> Res2<()> {
1224 let (from, to, step, _delay) = (r.token()?, r.token()?, r.token()?, r.token()?);
1225 let num = |t: Tok| if let Tok::Num(n) = t { n } else { 0 };
1226 let (from, to, step) = (num(from), num(to), num(step));
1227 let mut step = if from > to { -step } else { step };
1228 if step == 0 {
1229 step = 1;
1230 }
1231 let key = r.next()?;
1232 let gang = if key == b'>' { Some(r.text()?) } else { None };
1233 let kind = r.next()?;
1234 let count = (num(r.token()?)).clamp(1, 2048) as usize;
1235 let mut params = Vec::with_capacity(count);
1236 for _ in 0..count {
1237 params.push(r.token()?);
1238 }
1239 let plain = !params.iter().any(|t| !matches!(t, Tok::Num(_) | Tok::Rand | Tok::RandBig));
1240 self.looping = Some(Loop {
1241 params,
1242 at: 0,
1243 value: from,
1244 to,
1245 step,
1246 from,
1247 kind,
1248 x_count: 0,
1249 first: 0,
1250 second: 0,
1251 was: from,
1252 plain,
1253 running: true,
1254 });
1255 let mut guard = 0;
1256 loop {
1257 let cmd = match &gang {
1258 Some(g) => {
1259 let i = self.val(r, 0)?.clamp(0, 127) as usize;
1260 g.get(i).copied().unwrap_or(b' ')
1261 }
1262 None => key,
1263 };
1264 self.run(cmd, r)?;
1266 guard += 1;
1267 let done = self.looping.as_ref().is_none_or(|lp| !lp.running && lp.at == 0);
1268 if done || guard > 100_000 {
1269 break;
1270 }
1271 }
1272 self.looping = None;
1273 Ok(())
1274 }
1275
1276 fn in_loop(&self) -> bool {
1277 self.looping.is_some()
1278 }
1279
1280 fn run(&mut self, key: u8, r: &mut Reader) -> Res2<()> {
1281 match key {
1282 b'A' => {
1283 let (t, p, b) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1284 let f = &mut self.attrs.fill;
1285 f.interior = if (0..=4).contains(&t) { t as u8 } else { 0 };
1286 if f.interior == 4 && p >= 8 {
1287 self.user_patterns[0] = if p == 8 {
1289 std::array::from_fn(|_| {
1290 self.seed ^= self.seed << 13;
1291 self.seed ^= self.seed >> 17;
1292 self.seed ^= self.seed << 5;
1293 (self.seed & self.seed >> 7) as u16
1294 })
1295 } else {
1296 ROM_UD_PATTERN
1297 };
1298 self.attrs.fill.style = 1;
1299 } else {
1300 self.attrs.fill.style =
1301 if self.attrs.fill.interior == 4 { (p + 1).clamp(1, 8) as u8 } else { p.clamp(1, 24) as u8 };
1302 }
1303 self.attrs.fill.perimeter = b != 0;
1304 }
1305 b'B' => {
1306 let (a, b, round) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1307 if round != 0 {
1308 self.rounded_box((a.0, a.1, b.0, b.1), true);
1309 } else {
1310 self.shape(&[(a.0, a.1), (b.0, a.1), (b.0, b.1), (a.0, b.1)]);
1311 }
1312 }
1313 b'C' => {
1314 let (which, pen) = (self.val(r, 0)?, self.val(r, 0)?);
1315 let pen = if (0..=15).contains(&pen) { pen as u8 } else { 0 };
1316 match which {
1317 0 => self.attrs.marker.0 = pen,
1318 1 => self.attrs.line.pen = pen,
1319 2 => self.attrs.fill.pen = pen,
1320 3 => self.attrs.text.pen = pen,
1321 _ => self.attrs.marker.0 = pen,
1322 }
1323 }
1324 b'D' => {
1325 let to = self.point(r)?;
1326 let from = self.pen_at;
1327 self.line(from, to);
1328 self.pen_at = to;
1329 }
1330 b'E' => {
1331 let (fx, size, rot) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1332 self.attrs.text.effects = fx as u8;
1333 self.attrs.text.points = size.clamp(8, 20);
1334 self.attrs.text.rotation = if (0..=4).contains(&rot) { rot as u8 } else { 0 };
1335 }
1336 b'F' => {
1337 let (x, y) = self.point(r)?;
1338 self.flood(x, y);
1339 }
1340 b'f' | b'z' => {
1341 let n = self.val(r, 0)?.clamp(0, 128) as usize;
1342 let mut pts = Vec::with_capacity(n);
1343 for _ in 0..n {
1344 pts.push(self.point(r)?);
1345 }
1346 if key == b'f' {
1347 self.shape(&pts);
1348 } else if n >= 2 {
1349 self.polyline(&pts);
1350 }
1351 }
1352 b'G' => self.grab(r)?,
1353 b'g' => {
1354 let v = self.val(r, 0)?;
1355 self.scaling = if (0..=2).contains(&v) { v } else { 0 };
1356 }
1357 b'H' => {
1358 self.hollow = self.val(r, 0)? != 0;
1359 if self.hollow {
1360 self.attrs.fill.interior = 0;
1361 self.attrs.mode = 2;
1362 self.attrs.fill.perimeter = true;
1363 } else {
1364 self.attrs.fill.interior = 1;
1365 self.attrs.mode = 1;
1366 self.attrs.fill.perimeter = false;
1367 }
1368 }
1369 b'I' => match self.val(r, 0)? {
1370 1 => self.desktop_palette(),
1371 2 => self.attrs = Attrs::default(),
1372 3 => self.regs = IG_PALETTE,
1373 4 => self.desktop_palette(),
1374 5 => {
1375 if self.res == Res::Low {
1376 self.set_res(Res::Medium);
1377 }
1378 }
1379 _ => {
1380 self.desktop_palette();
1381 self.attrs = Attrs::default();
1382 }
1383 },
1384 b'J' | b'Y' => {
1385 let (c, rx, ry) = (self.point(r)?, self.val(r, 1)?, self.val(r, 2)?);
1386 let (a0, a1) = (self.val(r, 0)? * 10, self.val(r, 0)? * 10);
1387 let pts = self.ellipse_points(c, rx, ry, a0, a1);
1388 if key == b'J' {
1389 self.polyline(&pts);
1390 } else {
1391 let mut pie = vec![c];
1392 pie.extend(pts);
1393 self.shape(&pie);
1394 }
1395 }
1396 b'K' | b'V' => {
1397 let (c, rad) = (self.point(r)?, self.val(r, 1)?);
1398 let (a0, a1) = (self.val(r, 0)? * 10, self.val(r, 0)? * 10);
1399 let ry = (rad as f64 * self.res.aspect()).round() as i32;
1400 let pts = self.ellipse_points(c, rad, ry, a0, a1);
1401 if key == b'K' {
1402 self.polyline(&pts);
1403 } else {
1404 let mut pie = vec![c];
1405 pie.extend(pts);
1406 self.shape(&pie);
1407 }
1408 }
1409 b'k' => match self.val(r, 0)? {
1410 0 => self.cursor_on = false,
1411 1 => self.cursor_on = true,
1412 2 => self.destructive_bs = true,
1413 _ => self.destructive_bs = false,
1414 },
1415 b'L' => {
1416 let (a, b) = (self.point(r)?, self.point(r)?);
1417 self.line(a, b);
1418 self.pen_at = b;
1419 }
1420 b'M' => {
1421 let m = self.val(r, 0)?;
1422 self.attrs.mode = if (1..=4).contains(&m) { m as u8 } else { 1 };
1423 }
1424 b'N' => self.noise(r)?,
1425 b'n' => {
1426 for _ in 0..6 {
1427 self.val(r, 0)?;
1428 }
1429 }
1430 b'O' => {
1431 let (c, rad) = (self.point(r)?, self.val(r, 1)?);
1432 let ry = (rad as f64 * self.res.aspect()).round() as i32;
1433 let pts = self.ellipse_points(c, rad, ry, 0, 3600);
1434 self.shape(&pts);
1435 }
1436 b'P' => {
1437 let p = self.point(r)?;
1438 self.marker(p.0, p.1);
1439 self.pen_at = p;
1440 }
1441 b'Q' => {
1442 let (c, rx, ry) = (self.point(r)?, self.val(r, 1)?, self.val(r, 2)?);
1443 let pts = self.ellipse_points(c, rx, ry, 0, 3600);
1444 self.shape(&pts);
1445 }
1446 b'q' => {
1447 let n = self.val(r, 0)?;
1448 if (1..=180).contains(&n) && !self.in_loop() {
1449 self.hold = Some(Instant::now() + Duration::from_millis(n as u64 * 1000 / 60));
1450 }
1451 }
1452 b'R' => {
1453 let (res, pal) = (self.val(r, 0)?, self.val(r, 0)?);
1454 let want = match res {
1455 0 => Res::Low,
1456 1 => Res::Medium,
1457 _ => self.res,
1458 };
1459 if self.res != Res::High && want != self.res {
1460 self.set_res(want);
1461 }
1462 match pal {
1463 1 | 3 => self.desktop_palette(),
1464 2 => self.regs = IG_PALETTE,
1465 _ => {}
1466 }
1467 }
1468 b'S' => {
1469 let pen = self.val(r, 0)?;
1470 let (cr, cg, cb) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1471 let pen = if (0..=15).contains(&pen) { pen as u8 } else { 0 };
1472 let c = |v: i32| v.clamp(0, 7) as u16;
1473 let reg = self.reg(pen) as usize;
1474 self.regs[reg] = c(cr) << 8 | c(cg) << 4 | c(cb);
1475 }
1476 b's' => match self.val(r, 0)? {
1477 0 => self.vt52_escape(b'E'),
1478 1 => self.vt52_escape(b'd'),
1479 2 => self.vt52_escape(b'J'),
1480 3 => self.clear_screen(),
1481 4 => {
1482 self.clear_screen();
1483 (self.col, self.row) = (0, 0);
1484 }
1485 _ => {
1486 self.reverse = false;
1487 self.bg = 0;
1488 self.fg = 3 & self.mask();
1489 self.vt52_escape(b'E');
1490 }
1491 },
1492 b'T' => {
1493 let which = self.val(r, 0)?;
1494 if !(1..=2).contains(&which) {
1495 return Ok(());
1496 }
1497 let kind = self.val(r, 0)?;
1498 let size = self.val(r, 0)?;
1499 if which == 1 {
1500 self.attrs.marker.1 = if (1..=6).contains(&kind) { kind as u8 } else { 1 };
1501 self.attrs.marker.2 = if (1..=8).contains(&size) { size } else { 1 };
1502 } else {
1503 let kind = if (1..=7).contains(&kind) { kind as u8 } else { 1 };
1504 if (1..42).contains(&size) {
1505 self.attrs.line.style = kind;
1506 self.attrs.line.width = if kind > 1 { 1 } else { size };
1507 }
1508 let ends = match size {
1509 0 => Some((0, 0)),
1510 50 => Some((1, 1)),
1511 51 => Some((1, 0)),
1512 52 => Some((0, 1)),
1513 53 => Some((1, 2)),
1514 54 => Some((2, 1)),
1515 60 => Some((2, 2)),
1516 61 => Some((2, 0)),
1517 62 => Some((0, 2)),
1518 63 => Some((2, 1)),
1519 64 => Some((1, 2)),
1520 _ => None,
1521 };
1522 if let Some(e) = ends {
1523 self.attrs.line.ends = e;
1524 }
1525 }
1526 }
1527 b't' => {
1528 let n = self.val(r, 0)?;
1529 if n > 0 && !self.in_loop() {
1530 self.hold = Some(Instant::now() + Duration::from_secs(n.min(30) as u64));
1531 }
1532 }
1533 b'U' => {
1534 let (a, b, filled) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1535 self.rounded_box((a.0, a.1, b.0, b.1), filled != 0);
1536 }
1537 b'W' => {
1538 let p = self.point(r)?;
1539 let reads = self.looping.as_ref().is_none_or(|lp| lp.kind == b'@');
1540 if reads {
1541 self.last_text = r.text()?;
1542 }
1543 let text = self.last_text.clone();
1544 self.write_text(p.0, p.1, &text);
1545 }
1546 b'X' => self.extended(r)?,
1547 b'Z' => {
1548 let (a, b) = (self.point(r)?, self.point(r)?);
1549 let (x0, x1, y0, y1) = (a.0.min(b.0), a.0.max(b.0), a.1.min(b.1), a.1.max(b.1));
1550 for y in y0..=y1 {
1551 self.fill_span(x0, x1, y);
1552 }
1553 }
1554 b'b' => match self.val(r, 0)? {
1555 20 => {
1556 for _ in 0..6 {
1557 self.val(r, 0)?;
1558 }
1559 }
1560 22 | 23 => {
1561 self.val(r, 0)?;
1562 }
1563 _ => {}
1564 },
1565 b'<' => {
1566 let (cr, kind, show) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1567 if (2..=10).contains(&kind) {
1568 self.awaiting_zone = Some((cr != 0, show < 2));
1569 }
1570 }
1571 b'?' => {
1572 let q = self.val(r, 0)?;
1573 match q {
1574 0 => self.replies.extend_from_slice(VERSION),
1575 1 => {
1576 self.val(r, 0)?;
1577 let (c, rw) = (self.col as u8 + 32, self.row as u8 + 32);
1578 self.replies.extend_from_slice(&[c, rw, 32]);
1579 }
1580 2 => {
1581 self.val(r, 0)?;
1582 self.replies.extend_from_slice(b"0,0,0:");
1583 }
1584 3 => self.replies.extend_from_slice(&[b'0' + self.res.number(), b':']),
1585 _ => {}
1586 }
1587 }
1588 b'c' => {
1590 let (which, reg) = (self.val(r, 0)?, self.val(r, 0)?);
1591 let reg = reg as u8 & self.mask();
1592 if which == 0 {
1593 self.bg = reg;
1594 } else {
1595 self.fg = reg;
1596 }
1597 }
1598 b'd' => {
1599 for _ in 0..self.val(r, 0)?.clamp(0, 50) {
1600 self.vt52_escape(b'M');
1601 }
1602 }
1603 b'i' => {
1604 let (kind, n) = (self.val(r, 0)?, self.val(r, 0)?);
1605 for _ in 0..n.clamp(0, 50) {
1606 if kind == 0 {
1607 self.vt52_escape(b'I');
1608 } else {
1609 self.vt52_escape(b'L');
1610 }
1611 }
1612 }
1613 b'l' => match self.val(r, 0)? {
1614 0 => {
1615 self.vt52_escape(b'l');
1616 self.col = 0;
1617 }
1618 1 => self.vt52_escape(b'o'),
1619 _ => self.vt52_escape(b'K'),
1620 },
1621 b'm' => {
1622 let (dir, n) = (self.val(r, 0)?, self.val(r, 0)?);
1623 let esc = match dir {
1624 1 => b'A',
1625 2 => b'B',
1626 3 => b'C',
1627 4 => b'D',
1628 _ => b'H',
1629 };
1630 for _ in 0..n.clamp(1, 80) {
1631 self.vt52_escape(esc);
1632 }
1633 }
1634 b'p' => {
1635 let (c, rw) = (self.val(r, 0)?, self.val(r, 0)?);
1636 let (cols, rows) = self.text_size();
1637 self.col = (c.max(0) as usize).min(cols - 1);
1638 self.row = (rw.max(0) as usize).min(rows - 1);
1639 }
1640 b'r' => {
1641 if self.val(r, 0)? == 0 {
1642 self.saved = (self.col, self.row);
1643 } else {
1644 (self.col, self.row) = self.saved;
1645 }
1646 }
1647 b'v' => self.reverse = self.val(r, 0)? != 0,
1648 b'w' => self.wrap = self.val(r, 0)? != 0,
1649 _ => log::debug!("unknown IGS command {:?}", key as char),
1650 }
1651 Ok(())
1652 }
1653
1654 fn grab(&mut self, r: &mut Reader) -> Res2<()> {
1655 let (kind, op) = (self.val(r, 0)?, self.val(r, 0)?);
1656 let op = if (0..=15).contains(&op) { op as u8 } else { 3 };
1657 let a = self.point(r)?;
1658 match kind {
1659 0 => {
1660 let b = self.point(r)?;
1661 let d = self.point(r)?;
1662 self.blit(false, (a.0, a.1, b.0, b.1), false, d, op);
1663 }
1664 1 => {
1665 let b = self.point(r)?;
1666 self.memory_rect = (a.0, a.1, b.0, b.1);
1667 self.blit(false, self.memory_rect, true, (a.0.min(b.0), a.1.min(b.1)), op);
1668 }
1669 2 => self.blit(true, self.memory_rect, false, a, op),
1670 _ => {
1671 let b = self.point(r)?;
1672 let d = self.point(r)?;
1673 self.blit(true, (a.0, a.1, b.0, b.1), kind == 4, d, op);
1674 }
1675 }
1676 Ok(())
1677 }
1678
1679 fn noise(&mut self, r: &mut Reader) -> Res2<()> {
1681 match self.val(r, 0)? {
1682 0 | 1 | 3 | 4 => {
1683 let n = self.val(r, 0)?.clamp(0, 10_001) as usize;
1684 for _ in 0..n {
1685 r.next()?;
1686 }
1687 }
1688 6 => {
1689 self.val(r, 0)?;
1690 self.val(r, 0)?;
1691 }
1692 _ => {}
1693 }
1694 Ok(())
1695 }
1696
1697 fn extended(&mut self, r: &mut Reader) -> Res2<()> {
1698 match self.val(r, 0)? {
1699 0 => {
1701 let (a, rx, ry, n) = (self.point(r)?, self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1702 for _ in 0..n.clamp(0, 9999) {
1703 let x = a.0 + self.random((0, rx.clamp(0, 255)));
1704 let y = a.1 + self.random((0, ry.clamp(0, 255)));
1705 self.marker(x, y);
1706 }
1707 }
1708 1 => {
1710 let (reg, v) = (self.val(r, 0)?, self.val(r, 0)?);
1711 self.regs[(reg & 15) as usize] = (v as u16) & 0x777;
1712 }
1713 2 => {
1714 let (a, b) = (self.val(r, 0)?, self.val(r, 0)?);
1715 if a == b {
1716 let c = self.val(r, 0)?;
1717 self.rand_big = (b, c);
1718 } else {
1719 self.rand = (a, b);
1720 }
1721 }
1722 3 => match self.val(r, 0)? {
1724 1 => {
1725 self.val(r, 0)?;
1726 }
1727 2 => {
1728 let (_on, _cr, len) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1729 for _ in 0..len.clamp(0, 80) + 1 {
1730 r.next()?;
1731 }
1732 }
1733 _ => {}
1734 },
1735 4 => {
1737 let id = self.val(r, 0)?;
1738 if id >= 9997 {
1739 if id == 9999 {
1740 self.zones = vec![None; 48];
1741 }
1742 return Ok(());
1743 }
1744 let (a, b, len) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1745 let mut text = Vec::new();
1746 for _ in 0..len.clamp(0, 80) {
1747 text.push(r.next()?);
1748 }
1749 r.next()?;
1750 if let Some(z) = self.zones.get_mut(id.clamp(0, 47) as usize) {
1751 *z = Some((a.0.min(b.0), a.1.min(b.1), a.0.max(b.0), a.1.max(b.1), text));
1752 }
1753 }
1754 5 => {
1755 if matches!(self.val(r, 0)?, 2 | 3) {
1756 self.val(r, 0)?;
1757 self.val(r, 0)?;
1758 }
1759 }
1760 6 => {
1761 self.val(r, 0)?;
1762 }
1763 7 => {
1765 let slot = self.val(r, 0)?.clamp(0, 7) as usize;
1766 let mut rows = [0u16; 16];
1767 for row in &mut rows {
1768 let text = r.text()?;
1769 *row =
1770 text.iter().take(16).enumerate().fold(0, |acc, (i, &c)| {
1771 if c.eq_ignore_ascii_case(&b'X') { acc | 0x8000 >> i } else { acc }
1772 });
1773 }
1774 self.user_patterns[slot] = rows;
1775 }
1776 8 => {
1778 let (a, b, times, _delay) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1779 let (lo, hi) = (a.min(b).clamp(0, 15) as usize, a.max(b).clamp(0, 15) as usize);
1780 if times > 0 && lo < hi {
1781 let n = times as usize % (hi - lo + 1);
1782 if a < b {
1783 self.regs[lo..=hi].rotate_right(n);
1784 } else {
1785 self.regs[lo..=hi].rotate_left(n);
1786 }
1787 }
1788 }
1789 9 => match self.val(r, 0)? {
1791 0 => {
1792 let mut stored = Vec::new();
1793 while !stored.ends_with(b"||}") {
1794 stored.push(r.next()?);
1795 }
1796 stored.truncate(stored.len() - 3);
1797 self.stored = stored;
1798 }
1799 1 => self.run_stored = Some(self.stored.clone()),
1800 _ => self.stored.clear(),
1801 },
1802 10 => self.pen_at = self.point(r)?,
1803 _ => {}
1804 }
1805 Ok(())
1806 }
1807
1808 pub fn click(&mut self, x: i32, y: i32) -> Option<Vec<u8>> {
1811 let (cr, send) = self.awaiting_zone?;
1812 let hit = self.zones[..47]
1813 .iter()
1814 .flatten()
1815 .find(|z| x >= z.0 && x <= z.2 && y >= z.1 && y <= z.3)
1816 .or(self.zones[47].as_ref())?;
1817 let mut out = if send { hit.4.clone() } else { Vec::new() };
1818 self.awaiting_zone = None;
1819 if cr && send {
1820 out.push(b'\r');
1821 }
1822 Some(out)
1823 }
1824}
1825
1826#[cfg(test)]
1827mod tests {
1828 use super::*;
1829
1830 fn igs(data: &[u8]) -> Igs {
1831 let mut g = Igs::default();
1832 g.feed(data);
1833 g
1834 }
1835
1836 fn at(g: &Igs, x: usize, y: usize) -> u8 {
1837 g.pixels[y * g.width() + x]
1838 }
1839
1840 #[test]
1841 fn numbers_as_ig_reads_them() {
1842 let mut r = Reader { data: b"12,7:_\r\n34,1\r\n5\r\n2,x,+10,-3,!99,r,12345,", pos: 0 };
1843 let toks: Vec<Tok> = (0..9).map(|_| r.token().unwrap()).collect();
1844 assert_eq!(
1845 toks,
1846 [
1847 Tok::Num(12),
1848 Tok::Num(7),
1849 Tok::Num(34),
1850 Tok::Num(152),
1851 Tok::X,
1852 Tok::Plus(10),
1853 Tok::Minus(3),
1854 Tok::Bang(99),
1855 Tok::Rand,
1856 ]
1857 );
1858 assert_eq!(r.token().unwrap(), Tok::Num(0), "five digits are too many");
1859 assert!(Reader { data: b"12", pos: 0 }.token().is_err());
1860 }
1861
1862 #[test]
1863 fn lines_boxes_and_chains() {
1864 let g = igs(b"G#A>1,1,0:B>10,10,20,20,0:C>1,2:L 0,50,100,50:ok");
1867 assert_eq!(at(&g, 15, 15), 3);
1868 assert_eq!(at(&g, 25, 15), 0);
1869 assert_eq!(at(&g, 50, 50), 1);
1870 assert!((0..8).any(|y| (0..16).any(|x| at(&g, x, y) == 3)));
1872 }
1873
1874 #[test]
1875 fn commands_split_across_reads() {
1876 let mut g = Igs::default();
1877 g.feed(b"G#C>2,2:B>0,0,");
1878 assert_eq!(at(&g, 5, 5), 0);
1879 g.feed(b"10,10,0:");
1880 assert_eq!(at(&g, 5, 5), 1);
1881 }
1882
1883 #[test]
1884 fn loops_step_and_read_data() {
1885 let g = igs(b"G#C>1,1:&>0,30,10,0,L,4,x,0,x,9:");
1887 for x in [0, 10, 20, 30] {
1888 assert_eq!(at(&g, x, 5), 3, "x={x}");
1889 }
1890 assert_eq!(at(&g, 5, 5), 0);
1891 assert_eq!(at(&g, 40, 5), 0);
1892 let g = igs(b"G#C>1,1:&>0,0,1,0,L,8,100,0,100,9,200,0,200,9:");
1894 assert_eq!((at(&g, 100, 5), at(&g, 200, 5)), (3, 3));
1895 }
1896
1897 #[test]
1898 fn flood_fill_and_patterns() {
1899 let g = igs(b"G#A>0,1,1:B>0,0,40,20,0:A>2,4,0:F>10,10:");
1901 assert_eq!(at(&g, 0, 0), 3, "the border");
1902 let inside: Vec<u8> = (8..12).map(|x| at(&g, x, 10)).collect();
1903 assert!(inside.contains(&3) && inside.contains(&0), "{inside:?}");
1904 assert_eq!(at(&g, 50, 10), 0, "outside");
1905 }
1906
1907 #[test]
1908 fn vt52_text_and_queries() {
1909 let mut g = igs(b"\x1bY\x22\x25X\x1bb\x01Y");
1910 assert!((16..24).any(|y| (40..48).any(|x| at(&g, x, y) == 3)));
1912 assert!((16..24).any(|y| (48..56).any(|x| at(&g, x, y) == 1)));
1913 g.feed(b"G#?>0:?>3:");
1914 assert_eq!(g.replies, b"2.191:");
1915 g.feed(b"G#R>0,0:C>2,15:Z>0,0,9,9:");
1917 assert_eq!((g.width(), at(&g, 5, 5)), (320, 13));
1918 }
1919
1920 #[test]
1921 fn mouse_zones() {
1922 let mut g = igs(b"G#X>4,1,0,0,99,99,3,f/L:X>4,47,0,0,0,0,1,Q:<>1,2,1:");
1923 assert_eq!(g.click(50, 50), Some(b"f/L\r".to_vec()));
1924 assert_eq!(g.click(50, 50), None, "only while waiting");
1925 g.feed(b"G#< 0,2,1:");
1926 assert_eq!(g.click(300, 150), Some(b"Q".to_vec()));
1927 }
1928
1929 #[test]
1930 fn pauses_hold_the_stream() {
1931 let mut g = igs(b"G#t>1:C>2,2:Z>0,0,9,9:");
1932 assert!(g.hold.is_some());
1933 assert_eq!(at(&g, 5, 5), 0);
1934 g.poll(Instant::now() + Duration::from_secs(2));
1935 assert_eq!(at(&g, 5, 5), 1);
1936 }
1937}