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
362#[derive(Clone, Copy, Debug)]
364struct Cycle {
365 from: u8,
366 to: u8,
367 steps: u32,
368 step: Duration,
369}
370
371type Zone = (i32, i32, i32, i32, Vec<u8>);
373
374#[derive(Clone, Copy, Debug, PartialEq, Eq)]
376enum Esc {
377 None,
378 Start,
379 Row,
380 Col(u8),
381 Fg,
382 Bg,
383}
384
385#[derive(Clone, Copy, Debug, PartialEq, Eq)]
386enum Mode {
387 Text,
388 Command,
390}
391
392pub struct Igs {
393 pub res: Res,
394 pixels: Vec<u8>,
395 memory: Vec<u8>,
397 memory_rect: (i32, i32, i32, i32),
398 regs: [StColor; 16],
399 attrs: Attrs,
400 user_patterns: [[u16; 16]; 8],
401 pen_at: (i32, i32),
403 hollow: bool,
404 scaling: i32,
405 rand: (i32, i32),
406 rand_big: (i32, i32),
407 seed: u32,
408 col: usize,
410 row: usize,
411 fg: u8,
412 bg: u8,
413 reverse: bool,
414 wrap: bool,
415 pub cursor_on: bool,
416 destructive_bs: bool,
417 saved: (usize, usize),
418 esc: Esc,
419 mode: Mode,
421 g: bool,
423 buf: Vec<u8>,
424 stored: Vec<u8>,
426 run_stored: Option<Vec<u8>>,
427 pub hold: Option<Instant>,
429 cycle: Option<Cycle>,
431 kept_palette: [StColor; 16],
433 looping: Option<Loop>,
434 last_text: Vec<u8>,
435 zones: Vec<Option<Zone>>,
437 awaiting_zone: Option<(bool, bool)>,
439 pub replies: Vec<u8>,
441 pub bells: usize,
442}
443
444impl Default for Igs {
445 fn default() -> Igs {
446 let mut igs = Igs {
447 res: Res::Medium,
448 pixels: Vec::new(),
449 memory: Vec::new(),
450 memory_rect: (0, 0, 0, 0),
451 regs: DESKTOP,
452 attrs: Attrs::default(),
453 user_patterns: [ROM_UD_PATTERN; 8],
454 pen_at: (0, 0),
455 hollow: false,
456 scaling: 0,
457 rand: (0, 199),
458 rand_big: (0, 199),
459 seed: 0x1234_5678,
460 col: 0,
461 row: 0,
462 fg: 3,
463 bg: 0,
464 reverse: false,
465 wrap: true,
466 cursor_on: true,
467 destructive_bs: false,
468 saved: (0, 0),
469 esc: Esc::None,
470 mode: Mode::Text,
471 g: false,
472 buf: Vec::new(),
473 stored: Vec::new(),
474 run_stored: None,
475 hold: None,
476 cycle: None,
477 kept_palette: DESKTOP,
478 looping: None,
479 last_text: Vec::new(),
480 zones: vec![None; 48],
481 awaiting_zone: None,
482 replies: Vec::new(),
483 bells: 0,
484 };
485 igs.set_res(Res::Medium);
486 igs.desktop_palette();
487 igs
488 }
489}
490
491impl Igs {
492 pub fn width(&self) -> usize {
493 self.res.size().0
494 }
495
496 pub fn height(&self) -> usize {
497 self.res.size().1
498 }
499
500 pub fn pixels(&self) -> &[u8] {
502 &self.pixels
503 }
504
505 pub fn palette(&self) -> [Rgb; 16] {
506 std::array::from_fn(|i| rgb(self.regs[i]))
507 }
508
509 pub fn cursor(&self) -> (usize, usize) {
511 match self.res {
512 Res::Low => (self.col * 2, self.row),
513 Res::Medium => (self.col, self.row),
514 Res::High => (self.col, self.row / 2),
515 }
516 }
517
518 fn text_size(&self) -> (usize, usize) {
519 (self.width() / 8, self.height() / self.res.cell_h())
520 }
521
522 fn set_res(&mut self, res: Res) {
523 self.res = res;
524 let (w, h) = res.size();
525 self.pixels = vec![0; w * h];
526 self.memory = vec![0; w * h];
527 let (cols, rows) = self.text_size();
528 self.col = self.col.min(cols - 1);
529 self.row = self.row.min(rows - 1);
530 let mask = res.colors() - 1;
531 self.fg &= mask;
532 self.bg &= mask;
533 if res == Res::Medium {
534 self.fg = 3;
535 }
536 }
537
538 fn desktop_palette(&mut self) {
539 self.regs = DESKTOP;
540 match self.res {
541 Res::Medium => self.regs[..4].copy_from_slice(&DESKTOP_MEDIUM),
542 Res::High => self.regs[..2].copy_from_slice(&[0x777, 0x000]),
543 Res::Low => {}
544 }
545 }
546
547 fn random(&mut self, (lo, hi): (i32, i32)) -> i32 {
548 self.seed ^= self.seed << 13;
549 self.seed ^= self.seed >> 17;
550 self.seed ^= self.seed << 5;
551 lo + (self.seed % ((hi - lo).max(0) as u32 + 1)) as i32
552 }
553
554 pub fn feed(&mut self, data: &[u8]) {
556 self.buf.extend_from_slice(data);
557 let mut buf = std::mem::take(&mut self.buf);
558 let mut pos = 0;
559 while pos < buf.len() && self.hold.is_none() {
560 match self.mode {
561 Mode::Text => {
562 let b = buf[pos];
563 pos += 1;
564 self.text_byte(b);
565 }
566 Mode::Command => {
567 let mut r = Reader { data: &buf, pos };
568 match self.command(&mut r) {
569 Ok(chain) => {
570 pos = r.pos;
571 self.mode = if chain { Mode::Command } else { Mode::Text };
572 if let Some(run) = self.run_stored.take() {
574 buf.splice(pos..pos, run);
575 }
576 }
577 Err(Incomplete) => {
578 self.looping = None;
579 break;
580 }
581 }
582 }
583 }
584 }
585 buf.drain(..pos);
586 if buf.len() > 1 << 20 {
588 buf.clear();
589 self.mode = Mode::Text;
590 }
591 self.buf = buf;
592 }
593
594 pub fn poll(&mut self, now: Instant) {
597 if self.hold.is_some_and(|t| now >= t) {
598 self.hold = None;
599 if self.cycle.is_some() {
600 self.cycle_step();
601 if self.hold.is_some() {
602 return;
603 }
604 }
605 self.feed(&[]);
606 }
607 }
608
609 pub fn interrupt(&mut self) {
612 if self.hold.take().is_some() {
613 self.cycle = None;
614 self.feed(&[]);
615 }
616 }
617
618 fn cycle_step(&mut self) {
621 let Some(c) = self.cycle.as_mut() else { return };
622 let (lo, hi) = (c.from.min(c.to), c.from.max(c.to));
623 let regs: Vec<usize> = (lo..=hi).map(|pen| self.res.register(pen) as usize).collect();
624 let mut colors: Vec<StColor> = regs.iter().map(|&r| self.regs[r]).collect();
625 if c.from < c.to {
626 colors.rotate_right(1);
627 } else {
628 colors.rotate_left(1);
629 }
630 for (&r, &col) in regs.iter().zip(&colors) {
631 self.regs[r] = col;
632 }
633 c.steps -= 1;
634 if c.steps == 0 {
635 self.cycle = None;
636 } else {
637 self.hold = Some(Instant::now() + c.step);
638 }
639 }
640
641 fn text_byte(&mut self, b: u8) {
644 if self.g {
645 self.g = false;
646 if b == b'#' && self.esc == Esc::None {
647 self.mode = Mode::Command;
648 return;
649 }
650 self.vt52(b'G');
651 }
652 if b == b'G' && self.esc == Esc::None {
653 self.g = true;
654 return;
655 }
656 self.vt52(b);
657 }
658
659 fn vt52(&mut self, b: u8) {
660 let (cols, rows) = self.text_size();
661 match self.esc {
662 Esc::Start => {
663 self.esc = Esc::None;
664 self.vt52_escape(b);
665 return;
666 }
667 Esc::Row => {
668 self.esc = Esc::Col(b.saturating_sub(32));
669 return;
670 }
671 Esc::Col(r) => {
672 self.esc = Esc::None;
673 self.row = (r as usize).min(rows - 1);
674 self.col = (b.saturating_sub(32) as usize).min(cols - 1);
675 return;
676 }
677 Esc::Fg => {
678 self.esc = Esc::None;
679 self.fg = b & (self.res.colors() - 1);
680 return;
681 }
682 Esc::Bg => {
683 self.esc = Esc::None;
684 self.bg = b & (self.res.colors() - 1);
685 return;
686 }
687 Esc::None => {}
688 }
689 match b {
690 0x07 => self.bells += 1,
691 0x08 => {
692 self.col = self.col.saturating_sub(1);
693 if self.destructive_bs {
694 self.glyph_cell(b' ');
695 }
696 }
697 0x09 => self.col = ((self.col / 8 + 1) * 8).min(cols - 1),
698 0x0A..=0x0C => self.line_feed(),
699 0x0D => self.col = 0,
700 0x1B => self.esc = Esc::Start,
701 0x00..=0x1F => {}
702 _ => {
703 if self.col >= cols {
704 if !self.wrap {
705 self.col = cols - 1;
706 } else {
707 self.col = 0;
708 self.line_feed();
709 }
710 }
711 self.glyph_cell(b);
712 self.col += 1;
713 if self.col >= cols && !self.wrap {
714 self.col = cols - 1;
715 }
716 }
717 }
718 }
719
720 fn vt52_escape(&mut self, b: u8) {
721 let (cols, rows) = self.text_size();
722 match b {
723 b'A' => self.row = self.row.saturating_sub(1),
724 b'B' => self.row = (self.row + 1).min(rows - 1),
725 b'C' => self.col = (self.col + 1).min(cols - 1),
726 b'D' => self.col = self.col.saturating_sub(1),
727 b'E' => {
728 self.clear_text_rows(0, rows);
729 (self.col, self.row) = (0, 0);
730 }
731 b'H' => (self.col, self.row) = (0, 0),
732 b'I' => {
733 if self.row == 0 {
734 self.scroll_text(0, false);
735 } else {
736 self.row -= 1;
737 }
738 }
739 b'J' => {
740 self.clear_line_from(self.col);
741 self.clear_text_rows(self.row + 1, rows);
742 }
743 b'K' => self.clear_line_from(self.col),
744 b'L' => {
745 self.scroll_text(self.row, false);
746 self.col = 0;
747 }
748 b'M' => {
749 self.scroll_text(self.row, true);
750 self.col = 0;
751 }
752 b'Y' => self.esc = Esc::Row,
753 b'b' => self.esc = Esc::Fg,
754 b'c' => self.esc = Esc::Bg,
755 b'd' => {
756 self.clear_text_rows(0, self.row);
757 self.clear_line_to(self.col);
758 }
759 b'e' => self.cursor_on = true,
760 b'f' => self.cursor_on = false,
761 b'j' => self.saved = (self.col, self.row),
762 b'k' => (self.col, self.row) = self.saved,
763 b'l' => self.clear_text_rows(self.row, self.row + 1),
764 b'o' => self.clear_line_to(self.col),
765 b'p' => self.reverse = true,
766 b'q' => self.reverse = false,
767 b'v' => self.wrap = true,
768 b'w' => self.wrap = false,
769 _ => {}
770 }
771 }
772
773 fn line_feed(&mut self) {
774 let rows = self.text_size().1;
775 if self.row + 1 >= rows {
776 self.scroll_text(0, true);
777 } else {
778 self.row += 1;
779 }
780 }
781
782 fn scroll_text(&mut self, top: usize, up: bool) {
785 let (w, h) = self.res.size();
786 let ch = self.res.cell_h();
787 let (start, end) = (top * ch * w, h * w);
788 if start >= end {
789 return;
790 }
791 if up {
792 self.pixels.copy_within(start + ch * w..end, start);
793 self.pixels[end - ch * w..end].fill(self.bg);
794 } else {
795 self.pixels.copy_within(start..end - ch * w, start + ch * w);
796 self.pixels[start..start + ch * w].fill(self.bg);
797 }
798 }
799
800 fn clear_text_rows(&mut self, from: usize, to: usize) {
801 let w = self.width();
802 let ch = self.res.cell_h();
803 let (a, b) = ((from * ch * w).min(self.pixels.len()), (to * ch * w).min(self.pixels.len()));
804 if a < b {
805 self.pixels[a..b].fill(self.bg);
806 }
807 }
808
809 fn clear_cells(&mut self, row: usize, from: usize, to: usize) {
810 let w = self.width();
811 let ch = self.res.cell_h();
812 for y in row * ch..(row + 1) * ch {
813 let line = &mut self.pixels[y * w..(y + 1) * w];
814 line[(from * 8).min(w)..(to * 8).min(w)].fill(self.bg);
815 }
816 }
817
818 fn clear_line_from(&mut self, col: usize) {
819 let cols = self.text_size().0;
820 self.clear_cells(self.row, col, cols);
821 }
822
823 fn clear_line_to(&mut self, col: usize) {
824 self.clear_cells(self.row, 0, col + 1);
825 }
826
827 fn glyph_cell(&mut self, ch: u8) {
829 let w = self.width();
830 let h = self.res.cell_h();
831 let (fg, bg) = if self.reverse { (self.bg, self.fg) } else { (self.fg, self.bg) };
832 let (x0, y0) = (self.col * 8, self.row * h);
833 for gy in 0..h {
834 let bits = glyph_row(ch, gy, h);
835 let row = &mut self.pixels[(y0 + gy) * w + x0..][..8];
836 for (gx, p) in row.iter_mut().enumerate() {
837 *p = if bits & (0x80 >> gx) != 0 { fg } else { bg };
838 }
839 }
840 }
841
842 fn mask(&self) -> u8 {
845 self.res.colors() - 1
846 }
847
848 fn plot(&mut self, x: i32, y: i32, on: bool, color: u8, mode: u8) {
850 let (w, h) = self.res.size();
851 if x < 0 || y < 0 || x as usize >= w || y as usize >= h {
852 return;
853 }
854 let p = &mut self.pixels[y as usize * w + x as usize];
855 match mode {
856 2 if on => *p = color,
857 3 if on => *p ^= self.res.colors() - 1,
858 4 if !on => *p = color,
859 1 => *p = if on { color } else { 0 },
860 _ => {}
861 }
862 }
863
864 fn reg(&self, pen: u8) -> u8 {
865 self.res.register(pen)
866 }
867
868 fn pattern_bit(&self, x: i32, y: i32) -> bool {
870 let f = self.attrs.fill;
871 let fi = f.style.max(1) as usize - 1;
872 let word = match f.interior {
873 0 => return false,
874 1 => return true,
875 2 if fi < 8 => DITHER[fi * 4 + (y & 3) as usize],
876 2 => OEMPAT[((fi - 8).min(15)) * 8 + (y & 7) as usize],
877 3 if fi < 6 => HATCH0[fi * 8 + (y & 7) as usize],
878 3 => HATCH1[((fi - 6).min(5)) * 16 + (y & 15) as usize],
879 _ => self.user_patterns[fi.min(7)][(y & 15) as usize],
880 };
881 word & (0x8000 >> (x & 15)) != 0
882 }
883
884 fn fill_span(&mut self, x0: i32, x1: i32, y: i32) {
886 if self.attrs.fill.interior == 0 {
887 return;
888 }
889 let color = self.reg(self.attrs.fill.pen);
890 let mode = self.attrs.mode;
891 for x in x0.max(0)..=x1.min(self.width() as i32 - 1) {
892 let on = self.pattern_bit(x, y);
893 self.plot(x, y, on, color, mode);
894 }
895 }
896
897 fn polygon(&mut self, pts: &[(i32, i32)], perimeter: bool) {
899 if pts.is_empty() {
900 return;
901 }
902 let (ymin, ymax) = pts.iter().fold((i32::MAX, i32::MIN), |(a, b), p| (a.min(p.1), b.max(p.1)));
903 let h = self.height() as i32;
904 for y in ymin.max(0)..=ymax.min(h - 1) {
905 let mut xs: Vec<i32> = Vec::new();
906 for i in 0..pts.len() {
907 let (a, b) = (pts[i], pts[(i + 1) % pts.len()]);
908 let (lo, hi) = if a.1 < b.1 { (a, b) } else { (b, a) };
909 if y >= lo.1 && y < hi.1 || y == ymax && hi.1 == ymax && lo.1 != hi.1 {
910 let t = (y - lo.1) as f64 / (hi.1 - lo.1) as f64;
911 xs.push((lo.0 as f64 + t * (hi.0 - lo.0) as f64).round() as i32);
912 }
913 }
914 xs.sort_unstable();
915 for pair in xs.chunks(2) {
916 if let [a, b] = pair {
917 self.fill_span(*a, *b, y);
918 }
919 }
920 }
921 if perimeter {
922 let color = self.reg(self.attrs.fill.pen);
923 let mut closed = pts.to_vec();
924 closed.push(pts[0]);
925 for seg in closed.windows(2) {
926 self.thin_line(seg[0], seg[1], color, 0xFFFF, self.attrs.mode);
927 }
928 }
929 }
930
931 fn thin_line(&mut self, (x0, y0): (i32, i32), (x1, y1): (i32, i32), color: u8, style: u16, mode: u8) {
933 let (dx, dy) = ((x1 - x0).abs(), -(y1 - y0).abs());
934 let (sx, sy) = (if x0 < x1 { 1 } else { -1 }, if y0 < y1 { 1 } else { -1 });
935 let (mut x, mut y, mut err) = (x0, y0, dx + dy);
936 let mut bit = 0u32;
937 loop {
938 let on = style.rotate_left(bit) & 0x8000 != 0;
939 if on || mode == 1 || mode == 4 {
941 self.plot(x, y, on, color, mode);
942 }
943 bit += 1;
944 if x == x1 && y == y1 {
945 break;
946 }
947 let e2 = 2 * err;
948 if e2 >= dy {
949 err += dy;
950 x += sx;
951 }
952 if e2 <= dx {
953 err += dx;
954 y += sy;
955 }
956 }
957 }
958
959 fn line(&mut self, a: (i32, i32), b: (i32, i32)) {
962 let l = self.attrs.line;
963 let color = self.reg(l.pen);
964 let style = match l.style {
965 1..=6 => LINE_STYLES[l.style as usize - 1],
966 _ => {
967 let p = self.user_patterns[6];
968 p[0]
969 }
970 };
971 if l.width <= 1 {
972 self.thin_line(a, b, color, style, self.attrs.mode);
973 self.line_ends(a, b, color, 1);
974 return;
975 }
976 let saved = self.attrs.fill;
977 self.attrs.fill = FillAttr { pen: l.pen, interior: 1, style: 1, perimeter: false };
978 let (dx, dy) = ((b.0 - a.0) as f64, (b.1 - a.1) as f64);
979 let len = (dx * dx + dy * dy).sqrt().max(1.0);
980 let half = l.width as f64 / 2.0;
981 let (px, py) = (-dy / len * half, dx / len * half * self.res.aspect());
983 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);
984 self.polygon(&[q(a, 1.0), q(b, 1.0), q(b, -1.0), q(a, -1.0)], false);
985 self.line_ends(a, b, color, l.width);
986 self.attrs.fill = saved;
987 }
988
989 fn line_ends(&mut self, a: (i32, i32), b: (i32, i32), color: u8, width: i32) {
990 let ends = self.attrs.line.ends;
991 for (end, (at, from)) in [(ends.0, (a, b)), (ends.1, (b, a))] {
992 match end {
993 1 => self.arrow(at, from, color, width),
994 2 if width > 1 => {
995 let r = width / 2;
996 let saved = self.attrs.fill;
997 self.attrs.fill = FillAttr { pen: self.attrs.line.pen, interior: 1, style: 1, perimeter: false };
998 let pts = self.ellipse_points(at, r, (r as f64 * self.res.aspect()) as i32, 0, 3600);
999 self.polygon(&pts, false);
1000 self.attrs.fill = saved;
1001 }
1002 _ => {}
1003 }
1004 }
1005 }
1006
1007 fn arrow(&mut self, tip: (i32, i32), from: (i32, i32), _color: u8, width: i32) {
1008 let (dx, dy) = ((tip.0 - from.0) as f64, (tip.1 - from.1) as f64);
1009 let len = (dx * dx + dy * dy).sqrt().max(1.0);
1010 let size = (8 + 2 * width) as f64;
1011 let (ux, uy) = (dx / len, dy / len);
1012 let back = (tip.0 as f64 - ux * size, tip.1 as f64 - uy * size);
1013 let side = size / 2.0;
1014 let p1 = ((back.0 - uy * side) as i32, (back.1 + ux * side * self.res.aspect()) as i32);
1015 let p2 = ((back.0 + uy * side) as i32, (back.1 - ux * side * self.res.aspect()) as i32);
1016 let saved = self.attrs.fill;
1017 self.attrs.fill = FillAttr { pen: self.attrs.line.pen, interior: 1, style: 1, perimeter: false };
1018 self.polygon(&[tip, p1, p2], false);
1019 self.attrs.fill = saved;
1020 }
1021
1022 fn ellipse_points(&self, c: (i32, i32), rx: i32, ry: i32, start: i32, end: i32) -> Vec<(i32, i32)> {
1025 let mut span = end - start;
1026 if span <= 0 {
1027 span += 3600;
1028 }
1029 let steps = (rx.max(ry) / 4).clamp(32, 128);
1030 (0..=steps)
1031 .map(|i| {
1032 let a = (start as f64 + span as f64 * i as f64 / steps as f64).to_radians() / 10.0;
1033 ((c.0 as f64 + rx as f64 * a.cos()).round() as i32, (c.1 as f64 - ry as f64 * a.sin()).round() as i32)
1034 })
1035 .collect()
1036 }
1037
1038 fn polyline(&mut self, pts: &[(i32, i32)]) {
1039 for seg in pts.windows(2) {
1040 self.line(seg[0], seg[1]);
1041 }
1042 }
1043
1044 fn shape(&mut self, pts: &[(i32, i32)]) {
1046 self.polygon(pts, self.attrs.fill.perimeter);
1047 }
1048
1049 fn rounded_box(&mut self, (x0, y0, x1, y1): (i32, i32, i32, i32), filled: bool) {
1050 let (x0, x1) = (x0.min(x1), x0.max(x1));
1051 let (y0, y1) = (y0.min(y1), y0.max(y1));
1052 let rx = ((x1 - x0) / 2).min(8);
1053 let ry = (((y1 - y0) / 2) as f64).min(8.0 * self.res.aspect()) as i32;
1054 let mut pts = Vec::new();
1055 for (cx, cy, a0) in
1056 [(x1 - rx, y0 + ry, 0), (x0 + rx, y0 + ry, 900), (x0 + rx, y1 - ry, 1800), (x1 - rx, y1 - ry, 2700)]
1057 {
1058 pts.extend(self.ellipse_points((cx, cy), rx, ry, a0, a0 + 900));
1059 }
1060 if filled {
1061 self.shape(&pts);
1062 } else {
1063 pts.push(pts[0]);
1064 self.polyline(&pts);
1065 }
1066 }
1067
1068 fn flood(&mut self, x: i32, y: i32) {
1070 let (w, h) = self.res.size();
1071 if x < 0 || y < 0 || x as usize >= w || y as usize >= h {
1072 return;
1073 }
1074 let target = self.pixels[y as usize * w + x as usize];
1075 let mut seen = vec![false; w * h];
1076 let mut stack = vec![(x as usize, y as usize)];
1077 let mut spans = Vec::new();
1078 while let Some((x, y)) = stack.pop() {
1079 if seen[y * w + x] || self.pixels[y * w + x] != target {
1080 continue;
1081 }
1082 let (mut a, mut b) = (x, x);
1083 while a > 0 && !seen[y * w + a - 1] && self.pixels[y * w + a - 1] == target {
1084 a -= 1;
1085 }
1086 while b + 1 < w && !seen[y * w + b + 1] && self.pixels[y * w + b + 1] == target {
1087 b += 1;
1088 }
1089 for i in a..=b {
1090 seen[y * w + i] = true;
1091 for ny in [y.wrapping_sub(1), y + 1] {
1092 if ny < h && !seen[ny * w + i] && self.pixels[ny * w + i] == target {
1093 stack.push((i, ny));
1094 }
1095 }
1096 }
1097 spans.push((a as i32, b as i32, y as i32));
1098 }
1099 for (a, b, y) in spans {
1100 self.fill_span(a, b, y);
1101 }
1102 }
1103
1104 fn marker(&mut self, x: i32, y: i32) {
1105 let (pen, kind, size) = self.attrs.marker;
1106 let color = self.reg(pen);
1107 let shape = MARKERS[(kind.clamp(1, 6) - 1) as usize];
1108 let scale = if kind == 1 { 0.0 } else { size.max(1) as f64 };
1109 for poly in shape {
1110 let pts: Vec<(i32, i32)> =
1111 poly.iter().map(|&(dx, dy)| (x + (dx as f64 * scale) as i32, y + (dy as f64 * scale) as i32)).collect();
1112 for seg in pts.windows(2) {
1113 self.thin_line(seg[0], seg[1], color, 0xFFFF, self.attrs.mode);
1114 }
1115 }
1116 }
1117
1118 fn write_text(&mut self, x: i32, y: i32, text: &[u8]) {
1120 let t = self.attrs.text;
1121 let color = self.reg(t.pen);
1122 let (cell, scale) = match t.points {
1125 ..=8 => (6, 1),
1126 9 => (8, 1),
1127 10..=15 => (16, 1),
1128 16..=17 => (6, 2),
1129 18..=19 => (8, 2),
1130 _ => (16, 2),
1131 };
1132 let ascent = match cell {
1133 6 => 4,
1134 8 => 6,
1135 _ => 13,
1136 } * scale;
1137 let (cw, ch) = (cell.min(8) * scale, cell * scale);
1138 for (i, &c) in text.iter().enumerate() {
1139 for gy in 0..ch {
1140 let mut bits = glyph_row(c, (gy / scale) as usize, cell as usize) as u32;
1141 if t.effects & 1 != 0 {
1142 bits |= bits >> 1;
1143 }
1144 if t.effects & 2 != 0 && gy % 2 == 1 {
1145 bits &= 0x55;
1146 }
1147 if t.effects & 8 != 0 && gy == ascent + scale {
1148 bits = 0xFF;
1149 }
1150 let skew = if t.effects & 4 != 0 { (ch - gy) / 4 } else { 0 };
1151 for gx in 0..cw {
1152 let on = bits & (0x80 >> (gx / scale)) != 0;
1153 if !on && self.attrs.mode != 1 {
1154 continue;
1155 }
1156 let (ax, ay) = ((i as i32) * cw + gx + skew, gy - ascent);
1158 let (px, py) = match t.rotation % 4 {
1159 1 => (x + ay, y - ax),
1160 2 => (x - ax, y - ay),
1161 3 => (x - ay, y + ax),
1162 _ => (x + ax, y + ay),
1163 };
1164 self.plot(px, py, on, color, self.attrs.mode);
1165 }
1166 }
1167 }
1168 }
1169
1170 fn blit(&mut self, from_memory: bool, src: (i32, i32, i32, i32), to_memory: bool, dst: (i32, i32), op: u8) {
1173 let (w, h) = self.res.size();
1174 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));
1175 let source: Vec<u8> = if from_memory { self.memory.clone() } else { self.pixels.clone() };
1176 let mask = self.mask();
1177 for y in 0..=(y1 - y0) {
1178 for x in 0..=(x1 - x0) {
1179 let (sx, sy, dx, dy) = (x0 + x, y0 + y, dst.0 + x, dst.1 + y);
1180 if sx < 0 || sy < 0 || dx < 0 || dy < 0 {
1181 continue;
1182 }
1183 let (sx, sy, dx, dy) = (sx as usize, sy as usize, dx as usize, dy as usize);
1184 if sx >= w || sy >= h || dx >= w || dy >= h {
1185 continue;
1186 }
1187 let s = source[sy * w + sx];
1188 let target = if to_memory { &mut self.memory } else { &mut self.pixels };
1189 let d = target[dy * w + dx];
1190 let v = match op {
1191 0 => 0,
1192 1 => s & d,
1193 2 => s & !d,
1194 4 => !s & d,
1195 5 => d,
1196 6 => s ^ d,
1197 7 => s | d,
1198 8 => !(s | d),
1199 9 => !(s ^ d),
1200 10 => !d,
1201 11 => s | !d,
1202 12 => !s,
1203 13 => !s | d,
1204 14 => !(s & d),
1205 15 => 0xFF,
1206 _ => s,
1207 };
1208 target[dy * w + dx] = v & mask;
1209 }
1210 }
1211 }
1212
1213 fn clear_screen(&mut self) {
1214 self.pixels.fill(0);
1215 }
1216}
1217
1218fn glyph_row(c: u8, y: usize, h: usize) -> u8 {
1221 match h {
1222 16 => FONT16[c as usize * 16 + y.min(15)],
1223 6 if (0x20..0x7F).contains(&c) => FONT6[c as usize * 6 + y.min(5)],
1224 6 => FONT8[c as usize * 8 + (y * 8 / 6).min(7)],
1225 _ => FONT8[c as usize * 8 + y.min(7)],
1226 }
1227}
1228
1229impl Igs {
1231 fn val(&mut self, r: &mut Reader, axis: u8) -> Res2<i32> {
1234 let tok = match self.looping.as_mut() {
1235 Some(lp) if lp.running || lp.at != 0 => lp.next(),
1236 _ => r.token()?,
1237 };
1238 let mut n = match tok {
1239 Tok::Num(n) | Tok::Plus(n) | Tok::Bang(n) => n,
1240 Tok::Minus(n) => -n,
1241 Tok::Rand => self.random(self.rand),
1242 Tok::RandBig => self.random(self.rand_big),
1243 Tok::X | Tok::Y => 0,
1244 };
1245 if self.scaling == 1 && axis > 0 && n > 0 {
1246 let max = if axis == 1 { self.width() - 1 } else { self.height() - 1 } as f64;
1247 n = (n as f64 * max / 9999.0).round() as i32;
1248 } else if self.scaling == 2 && axis == 2 && self.res == Res::High {
1249 n *= 2;
1250 }
1251 Ok(n)
1252 }
1253
1254 fn point(&mut self, r: &mut Reader) -> Res2<(i32, i32)> {
1255 Ok((self.val(r, 1)?, self.val(r, 2)?))
1256 }
1257
1258 fn command(&mut self, r: &mut Reader) -> Res2<bool> {
1261 let mut key = r.next()?;
1262 while key == b'G' && r.data.get(r.pos) == Some(&b'#') {
1264 r.pos += 1;
1265 key = r.next()?;
1266 }
1267 let chain = r.next()? == b'>';
1268 if key == b'&' {
1269 self.loop_command(r)?;
1270 } else {
1271 self.run(key, r)?;
1272 }
1273 Ok(chain)
1274 }
1275
1276 fn loop_command(&mut self, r: &mut Reader) -> Res2<()> {
1278 let (from, to, step, _delay) = (r.token()?, r.token()?, r.token()?, r.token()?);
1279 let num = |t: Tok| if let Tok::Num(n) = t { n } else { 0 };
1280 let (from, to, step) = (num(from), num(to), num(step));
1281 let mut step = if from > to { -step } else { step };
1282 if step == 0 {
1283 step = 1;
1284 }
1285 let key = r.next()?;
1286 let gang = if key == b'>' { Some(r.text()?) } else { None };
1287 let kind = r.next()?;
1288 let count = (num(r.token()?)).clamp(1, 2048) as usize;
1289 let mut params = Vec::with_capacity(count);
1290 for _ in 0..count {
1291 params.push(r.token()?);
1292 }
1293 let plain = !params.iter().any(|t| !matches!(t, Tok::Num(_) | Tok::Rand | Tok::RandBig));
1294 self.looping = Some(Loop {
1295 params,
1296 at: 0,
1297 value: from,
1298 to,
1299 step,
1300 from,
1301 kind,
1302 x_count: 0,
1303 first: 0,
1304 second: 0,
1305 was: from,
1306 plain,
1307 running: true,
1308 });
1309 let mut guard = 0;
1310 loop {
1311 let cmd = match &gang {
1312 Some(g) => {
1313 let i = self.val(r, 0)?.clamp(0, 127) as usize;
1314 g.get(i).copied().unwrap_or(b' ')
1315 }
1316 None => key,
1317 };
1318 self.run(cmd, r)?;
1320 guard += 1;
1321 let done = self.looping.as_ref().is_none_or(|lp| !lp.running && lp.at == 0);
1322 if done || guard > 100_000 {
1323 break;
1324 }
1325 }
1326 self.looping = None;
1327 Ok(())
1328 }
1329
1330 fn in_loop(&self) -> bool {
1331 self.looping.is_some()
1332 }
1333
1334 fn run(&mut self, key: u8, r: &mut Reader) -> Res2<()> {
1335 match key {
1336 b'A' => {
1337 let (t, p, b) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1338 let f = &mut self.attrs.fill;
1339 f.interior = if (0..=4).contains(&t) { t as u8 } else { 0 };
1340 if f.interior == 4 && p >= 8 {
1341 self.user_patterns[0] = if p == 8 {
1343 std::array::from_fn(|_| {
1344 self.seed ^= self.seed << 13;
1345 self.seed ^= self.seed >> 17;
1346 self.seed ^= self.seed << 5;
1347 (self.seed & self.seed >> 7) as u16
1348 })
1349 } else {
1350 ROM_UD_PATTERN
1351 };
1352 self.attrs.fill.style = 1;
1353 } else {
1354 self.attrs.fill.style =
1355 if self.attrs.fill.interior == 4 { (p + 1).clamp(1, 8) as u8 } else { p.clamp(1, 24) as u8 };
1356 }
1357 self.attrs.fill.perimeter = b != 0;
1358 }
1359 b'B' => {
1360 let (a, b, round) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1361 if round != 0 {
1362 self.rounded_box((a.0, a.1, b.0, b.1), true);
1363 } else {
1364 self.shape(&[(a.0, a.1), (b.0, a.1), (b.0, b.1), (a.0, b.1)]);
1365 }
1366 }
1367 b'C' => {
1368 let (which, pen) = (self.val(r, 0)?, self.val(r, 0)?);
1369 let pen = if (0..=15).contains(&pen) { pen as u8 } else { 0 };
1370 match which {
1371 0 => self.attrs.marker.0 = pen,
1372 1 => self.attrs.line.pen = pen,
1373 2 => self.attrs.fill.pen = pen,
1374 3 => self.attrs.text.pen = pen,
1375 _ => self.attrs.marker.0 = pen,
1376 }
1377 }
1378 b'D' => {
1379 let to = self.point(r)?;
1380 let from = self.pen_at;
1381 self.line(from, to);
1382 self.pen_at = to;
1383 }
1384 b'E' => {
1385 let (fx, size, rot) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1386 self.attrs.text.effects = fx as u8;
1387 self.attrs.text.points = size.clamp(8, 20);
1388 self.attrs.text.rotation = if (0..=4).contains(&rot) { rot as u8 } else { 0 };
1389 }
1390 b'F' => {
1391 let (x, y) = self.point(r)?;
1392 self.flood(x, y);
1393 }
1394 b'f' | b'z' => {
1395 let n = self.val(r, 0)?.clamp(0, 128) as usize;
1396 let mut pts = Vec::with_capacity(n);
1397 for _ in 0..n {
1398 pts.push(self.point(r)?);
1399 }
1400 if key == b'f' {
1401 self.shape(&pts);
1402 } else if n >= 2 {
1403 self.polyline(&pts);
1404 }
1405 }
1406 b'G' => self.grab(r)?,
1407 b'g' => {
1408 let v = self.val(r, 0)?;
1409 self.scaling = if (0..=2).contains(&v) { v } else { 0 };
1410 }
1411 b'H' => {
1412 self.hollow = self.val(r, 0)? != 0;
1413 if self.hollow {
1414 self.attrs.fill.interior = 0;
1415 self.attrs.mode = 2;
1416 self.attrs.fill.perimeter = true;
1417 } else {
1418 self.attrs.fill.interior = 1;
1419 self.attrs.mode = 1;
1420 self.attrs.fill.perimeter = false;
1421 }
1422 }
1423 b'I' => match self.val(r, 0)? {
1424 1 => self.desktop_palette(),
1425 2 => self.attrs = Attrs::default(),
1426 3 => self.regs = IG_PALETTE,
1427 4 => self.desktop_palette(),
1428 5 => {
1429 if self.res == Res::Low {
1430 self.set_res(Res::Medium);
1431 }
1432 }
1433 _ => {
1434 self.desktop_palette();
1435 self.attrs = Attrs::default();
1436 }
1437 },
1438 b'J' | b'Y' => {
1439 let (c, rx, ry) = (self.point(r)?, self.val(r, 1)?, self.val(r, 2)?);
1440 let (a0, a1) = (self.val(r, 0)? * 10, self.val(r, 0)? * 10);
1441 let pts = self.ellipse_points(c, rx, ry, a0, a1);
1442 if key == b'J' {
1443 self.polyline(&pts);
1444 } else {
1445 let mut pie = vec![c];
1446 pie.extend(pts);
1447 self.shape(&pie);
1448 }
1449 }
1450 b'K' | b'V' => {
1451 let (c, rad) = (self.point(r)?, self.val(r, 1)?);
1452 let (a0, a1) = (self.val(r, 0)? * 10, self.val(r, 0)? * 10);
1453 let ry = (rad as f64 * self.res.aspect()).round() as i32;
1454 let pts = self.ellipse_points(c, rad, ry, a0, a1);
1455 if key == b'K' {
1456 self.polyline(&pts);
1457 } else {
1458 let mut pie = vec![c];
1459 pie.extend(pts);
1460 self.shape(&pie);
1461 }
1462 }
1463 b'k' => match self.val(r, 0)? {
1464 0 => self.cursor_on = false,
1465 1 => self.cursor_on = true,
1466 2 => self.destructive_bs = true,
1467 _ => self.destructive_bs = false,
1468 },
1469 b'L' => {
1470 let (a, b) = (self.point(r)?, self.point(r)?);
1471 self.line(a, b);
1472 self.pen_at = b;
1473 }
1474 b'M' => {
1475 let m = self.val(r, 0)?;
1476 self.attrs.mode = if (1..=4).contains(&m) { m as u8 } else { 1 };
1477 }
1478 b'N' => self.noise(r)?,
1479 b'n' => {
1480 for _ in 0..6 {
1481 self.val(r, 0)?;
1482 }
1483 }
1484 b'O' => {
1485 let (c, rad) = (self.point(r)?, self.val(r, 1)?);
1486 let ry = (rad as f64 * self.res.aspect()).round() as i32;
1487 let pts = self.ellipse_points(c, rad, ry, 0, 3600);
1488 self.shape(&pts);
1489 }
1490 b'P' => {
1491 let p = self.point(r)?;
1492 self.marker(p.0, p.1);
1493 self.pen_at = p;
1494 }
1495 b'Q' => {
1496 let (c, rx, ry) = (self.point(r)?, self.val(r, 1)?, self.val(r, 2)?);
1497 let pts = self.ellipse_points(c, rx, ry, 0, 3600);
1498 self.shape(&pts);
1499 }
1500 b'q' => {
1501 let n = self.val(r, 0)?;
1502 if (1..=180).contains(&n) && !self.in_loop() {
1503 self.hold = Some(Instant::now() + Duration::from_millis(n as u64 * 1000 / 60));
1504 }
1505 }
1506 b'R' => {
1507 let (res, pal) = (self.val(r, 0)?, self.val(r, 0)?);
1508 let want = match res {
1509 0 => Res::Low,
1510 1 => Res::Medium,
1511 _ => self.res,
1512 };
1513 if self.res != Res::High && want != self.res {
1514 self.set_res(want);
1515 }
1516 match pal {
1517 1 | 3 => self.desktop_palette(),
1518 2 => self.regs = IG_PALETTE,
1519 _ => {}
1520 }
1521 }
1522 b'S' => {
1523 let pen = self.val(r, 0)?;
1524 let (cr, cg, cb) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1525 let pen = if (0..=15).contains(&pen) { pen as u8 } else { 0 };
1526 let c = |v: i32| v.clamp(0, 7) as u16;
1527 let reg = self.reg(pen) as usize;
1528 self.regs[reg] = c(cr) << 8 | c(cg) << 4 | c(cb);
1529 }
1530 b's' => match self.val(r, 0)? {
1531 0 => self.vt52_escape(b'E'),
1532 1 => self.vt52_escape(b'd'),
1533 2 => self.vt52_escape(b'J'),
1534 3 => self.clear_screen(),
1535 4 => {
1536 self.clear_screen();
1537 (self.col, self.row) = (0, 0);
1538 }
1539 _ => {
1540 self.reverse = false;
1541 self.bg = 0;
1542 self.fg = 3 & self.mask();
1543 self.vt52_escape(b'E');
1544 }
1545 },
1546 b'T' => {
1547 let which = self.val(r, 0)?;
1548 if !(1..=2).contains(&which) {
1549 return Ok(());
1550 }
1551 let kind = self.val(r, 0)?;
1552 let size = self.val(r, 0)?;
1553 if which == 1 {
1554 self.attrs.marker.1 = if (1..=6).contains(&kind) { kind as u8 } else { 1 };
1555 self.attrs.marker.2 = if (1..=8).contains(&size) { size } else { 1 };
1556 } else {
1557 let kind = if (1..=7).contains(&kind) { kind as u8 } else { 1 };
1558 if (1..42).contains(&size) {
1559 self.attrs.line.style = kind;
1560 self.attrs.line.width = if kind > 1 { 1 } else { size };
1561 }
1562 let ends = match size {
1563 0 => Some((0, 0)),
1564 50 => Some((1, 1)),
1565 51 => Some((1, 0)),
1566 52 => Some((0, 1)),
1567 53 => Some((1, 2)),
1568 54 => Some((2, 1)),
1569 60 => Some((2, 2)),
1570 61 => Some((2, 0)),
1571 62 => Some((0, 2)),
1572 63 => Some((2, 1)),
1573 64 => Some((1, 2)),
1574 _ => None,
1575 };
1576 if let Some(e) = ends {
1577 self.attrs.line.ends = e;
1578 }
1579 }
1580 }
1581 b't' => {
1582 let n = self.val(r, 0)?;
1583 if n > 0 && !self.in_loop() {
1584 self.hold = Some(Instant::now() + Duration::from_secs(n.min(30) as u64));
1585 }
1586 }
1587 b'U' => {
1588 let (a, b, filled) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1589 self.rounded_box((a.0, a.1, b.0, b.1), filled != 0);
1590 }
1591 b'W' => {
1592 let p = self.point(r)?;
1593 let reads = self.looping.as_ref().is_none_or(|lp| lp.kind == b'@');
1594 if reads {
1595 self.last_text = r.text()?;
1596 }
1597 let text = self.last_text.clone();
1598 self.write_text(p.0, p.1, &text);
1599 }
1600 b'X' => self.extended(r)?,
1601 b'Z' => {
1602 let (a, b) = (self.point(r)?, self.point(r)?);
1603 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));
1604 for y in y0..=y1 {
1605 self.fill_span(x0, x1, y);
1606 }
1607 }
1608 b'b' => match self.val(r, 0)? {
1609 20 => {
1610 for _ in 0..6 {
1611 self.val(r, 0)?;
1612 }
1613 }
1614 22 | 23 => {
1615 self.val(r, 0)?;
1616 }
1617 _ => {}
1618 },
1619 b'<' => {
1620 let (cr, kind, show) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1621 if (2..=10).contains(&kind) {
1622 self.awaiting_zone = Some((cr != 0, show < 2));
1623 }
1624 }
1625 b'?' => {
1626 let q = self.val(r, 0)?;
1627 match q {
1628 0 => self.replies.extend_from_slice(VERSION),
1629 1 => {
1630 self.val(r, 0)?;
1631 let (c, rw) = (self.col as u8 + 32, self.row as u8 + 32);
1632 self.replies.extend_from_slice(&[c, rw, 32]);
1633 }
1634 2 => {
1635 self.val(r, 0)?;
1636 self.replies.extend_from_slice(b"0,0,0:");
1637 }
1638 3 => self.replies.extend_from_slice(&[b'0' + self.res.number(), b':']),
1639 _ => {}
1640 }
1641 }
1642 b'c' => {
1644 let (which, reg) = (self.val(r, 0)?, self.val(r, 0)?);
1645 let reg = reg as u8 & self.mask();
1646 if which == 0 {
1647 self.bg = reg;
1648 } else {
1649 self.fg = reg;
1650 }
1651 }
1652 b'd' => {
1653 for _ in 0..self.val(r, 0)?.clamp(0, 50) {
1654 self.vt52_escape(b'M');
1655 }
1656 }
1657 b'i' => {
1658 let (kind, n) = (self.val(r, 0)?, self.val(r, 0)?);
1659 for _ in 0..n.clamp(0, 50) {
1660 if kind == 0 {
1661 self.vt52_escape(b'I');
1662 } else {
1663 self.vt52_escape(b'L');
1664 }
1665 }
1666 }
1667 b'l' => match self.val(r, 0)? {
1668 0 => {
1669 self.vt52_escape(b'l');
1670 self.col = 0;
1671 }
1672 1 => self.vt52_escape(b'o'),
1673 _ => self.vt52_escape(b'K'),
1674 },
1675 b'm' => {
1676 let (dir, n) = (self.val(r, 0)?, self.val(r, 0)?);
1677 let esc = match dir {
1678 1 => b'A',
1679 2 => b'B',
1680 3 => b'C',
1681 4 => b'D',
1682 _ => b'H',
1683 };
1684 for _ in 0..n.clamp(1, 80) {
1685 self.vt52_escape(esc);
1686 }
1687 }
1688 b'p' => {
1689 let (c, rw) = (self.val(r, 0)?, self.val(r, 0)?);
1690 let (cols, rows) = self.text_size();
1691 self.col = (c.max(0) as usize).min(cols - 1);
1692 self.row = (rw.max(0) as usize).min(rows - 1);
1693 }
1694 b'r' => {
1695 if self.val(r, 0)? == 0 {
1696 self.saved = (self.col, self.row);
1697 } else {
1698 (self.col, self.row) = self.saved;
1699 }
1700 }
1701 b'v' => self.reverse = self.val(r, 0)? != 0,
1702 b'w' => self.wrap = self.val(r, 0)? != 0,
1703 _ => log::debug!("unknown IGS command {:?}", key as char),
1704 }
1705 Ok(())
1706 }
1707
1708 fn grab(&mut self, r: &mut Reader) -> Res2<()> {
1709 let (kind, op) = (self.val(r, 0)?, self.val(r, 0)?);
1710 let op = if (0..=15).contains(&op) { op as u8 } else { 3 };
1711 let a = self.point(r)?;
1712 match kind {
1713 0 => {
1714 let b = self.point(r)?;
1715 let d = self.point(r)?;
1716 self.blit(false, (a.0, a.1, b.0, b.1), false, d, op);
1717 }
1718 1 => {
1719 let b = self.point(r)?;
1720 self.memory_rect = (a.0, a.1, b.0, b.1);
1721 self.blit(false, self.memory_rect, true, (a.0.min(b.0), a.1.min(b.1)), op);
1722 }
1723 2 => self.blit(true, self.memory_rect, false, a, op),
1724 _ => {
1725 let b = self.point(r)?;
1726 let d = self.point(r)?;
1727 self.blit(true, (a.0, a.1, b.0, b.1), kind == 4, d, op);
1728 }
1729 }
1730 Ok(())
1731 }
1732
1733 fn noise(&mut self, r: &mut Reader) -> Res2<()> {
1735 match self.val(r, 0)? {
1736 0 | 1 | 3 | 4 => {
1737 let n = self.val(r, 0)?.clamp(0, 10_001) as usize;
1738 for _ in 0..n {
1739 r.next()?;
1740 }
1741 }
1742 6 => {
1743 self.val(r, 0)?;
1744 self.val(r, 0)?;
1745 }
1746 _ => {}
1747 }
1748 Ok(())
1749 }
1750
1751 fn extended(&mut self, r: &mut Reader) -> Res2<()> {
1752 match self.val(r, 0)? {
1753 0 => {
1755 let (a, rx, ry, n) = (self.point(r)?, self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1756 for _ in 0..n.clamp(0, 9999) {
1757 let x = a.0 + self.random((0, rx.clamp(0, 255)));
1758 let y = a.1 + self.random((0, ry.clamp(0, 255)));
1759 self.marker(x, y);
1760 }
1761 }
1762 1 => {
1764 let (reg, v) = (self.val(r, 0)?, self.val(r, 0)?);
1765 self.regs[(reg & 15) as usize] = (v as u16) & 0x777;
1766 }
1767 2 => {
1768 let (a, b) = (self.val(r, 0)?, self.val(r, 0)?);
1769 if a == b {
1770 let c = self.val(r, 0)?;
1771 self.rand_big = (b, c);
1772 } else {
1773 self.rand = (a, b);
1774 }
1775 }
1776 3 => match self.val(r, 0)? {
1778 1 => {
1779 self.val(r, 0)?;
1780 }
1781 2 => {
1782 let (_on, _cr, len) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1783 for _ in 0..len.clamp(0, 80) + 1 {
1784 r.next()?;
1785 }
1786 }
1787 _ => {}
1788 },
1789 4 => {
1791 let id = self.val(r, 0)?;
1792 if id >= 9997 {
1793 if id == 9999 {
1794 self.zones = vec![None; 48];
1795 }
1796 return Ok(());
1797 }
1798 let (a, b, len) = (self.point(r)?, self.point(r)?, self.val(r, 0)?);
1799 let mut text = Vec::new();
1800 for _ in 0..len.clamp(0, 80) {
1801 text.push(r.next()?);
1802 }
1803 r.next()?;
1804 if let Some(z) = self.zones.get_mut(id.clamp(0, 47) as usize) {
1805 *z = Some((a.0.min(b.0), a.1.min(b.1), a.0.max(b.0), a.1.max(b.1), text));
1806 }
1807 }
1808 5 => {
1809 if matches!(self.val(r, 0)?, 2 | 3) {
1810 self.val(r, 0)?;
1811 self.val(r, 0)?;
1812 }
1813 }
1814 6 => {
1815 self.val(r, 0)?;
1816 }
1817 7 => {
1819 let slot = self.val(r, 0)?.clamp(0, 7) as usize;
1820 let mut rows = [0u16; 16];
1821 for row in &mut rows {
1822 let text = r.text()?;
1823 *row =
1824 text.iter().take(16).enumerate().fold(0, |acc, (i, &c)| {
1825 if c.eq_ignore_ascii_case(&b'X') { acc | 0x8000 >> i } else { acc }
1826 });
1827 }
1828 self.user_patterns[slot] = rows;
1829 }
1830 8 => {
1833 let (a, b, times, delay) = (self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?, self.val(r, 0)?);
1834 if (a, b, times, delay) == (1, 1, 1, 1) {
1835 self.kept_palette = self.regs;
1836 }
1837 let clamp = |v: i32| if (0..=15).contains(&v) { v as u8 } else { 15 };
1838 let (a, b) = (clamp(a), clamp(b));
1839 if times == 0 {
1840 self.regs = self.kept_palette;
1841 } else if a != b {
1842 let step =
1845 Duration::from_millis(((a.abs_diff(b) as u64 + 2) * 1000 / 60) + delay.max(0) as u64 * 5);
1846 self.cycle = Some(Cycle { from: a, to: b, steps: times as u32 + 1, step });
1847 self.cycle_step();
1848 }
1849 }
1850 9 => match self.val(r, 0)? {
1852 0 => {
1853 let mut stored = Vec::new();
1854 while !stored.ends_with(b"||}") {
1855 stored.push(r.next()?);
1856 }
1857 stored.truncate(stored.len() - 3);
1858 self.stored = stored;
1859 }
1860 1 => self.run_stored = Some(self.stored.clone()),
1861 _ => self.stored.clear(),
1862 },
1863 10 => self.pen_at = self.point(r)?,
1864 _ => {}
1865 }
1866 Ok(())
1867 }
1868
1869 pub fn click(&mut self, x: i32, y: i32) -> Option<Vec<u8>> {
1872 let (cr, send) = self.awaiting_zone?;
1873 let hit = self.zones[..47]
1874 .iter()
1875 .flatten()
1876 .find(|z| x >= z.0 && x <= z.2 && y >= z.1 && y <= z.3)
1877 .or(self.zones[47].as_ref())?;
1878 let mut out = if send { hit.4.clone() } else { Vec::new() };
1879 self.awaiting_zone = None;
1880 if cr && send {
1881 out.push(b'\r');
1882 }
1883 Some(out)
1884 }
1885}
1886
1887#[cfg(test)]
1888mod tests {
1889 use super::*;
1890
1891 fn igs(data: &[u8]) -> Igs {
1892 let mut g = Igs::default();
1893 g.feed(data);
1894 g
1895 }
1896
1897 fn at(g: &Igs, x: usize, y: usize) -> u8 {
1898 g.pixels[y * g.width() + x]
1899 }
1900
1901 #[test]
1902 fn numbers_as_ig_reads_them() {
1903 let mut r = Reader { data: b"12,7:_\r\n34,1\r\n5\r\n2,x,+10,-3,!99,r,12345,", pos: 0 };
1904 let toks: Vec<Tok> = (0..9).map(|_| r.token().unwrap()).collect();
1905 assert_eq!(
1906 toks,
1907 [
1908 Tok::Num(12),
1909 Tok::Num(7),
1910 Tok::Num(34),
1911 Tok::Num(152),
1912 Tok::X,
1913 Tok::Plus(10),
1914 Tok::Minus(3),
1915 Tok::Bang(99),
1916 Tok::Rand,
1917 ]
1918 );
1919 assert_eq!(r.token().unwrap(), Tok::Num(0), "five digits are too many");
1920 assert!(Reader { data: b"12", pos: 0 }.token().is_err());
1921 }
1922
1923 #[test]
1924 fn lines_boxes_and_chains() {
1925 let g = igs(b"G#A>1,1,0:B>10,10,20,20,0:C>1,2:L 0,50,100,50:ok");
1928 assert_eq!(at(&g, 15, 15), 3);
1929 assert_eq!(at(&g, 25, 15), 0);
1930 assert_eq!(at(&g, 50, 50), 1);
1931 assert!((0..8).any(|y| (0..16).any(|x| at(&g, x, y) == 3)));
1933 }
1934
1935 #[test]
1936 fn commands_split_across_reads() {
1937 let mut g = Igs::default();
1938 g.feed(b"G#C>2,2:B>0,0,");
1939 assert_eq!(at(&g, 5, 5), 0);
1940 g.feed(b"10,10,0:");
1941 assert_eq!(at(&g, 5, 5), 1);
1942 }
1943
1944 #[test]
1945 fn loops_step_and_read_data() {
1946 let g = igs(b"G#C>1,1:&>0,30,10,0,L,4,x,0,x,9:");
1948 for x in [0, 10, 20, 30] {
1949 assert_eq!(at(&g, x, 5), 3, "x={x}");
1950 }
1951 assert_eq!(at(&g, 5, 5), 0);
1952 assert_eq!(at(&g, 40, 5), 0);
1953 let g = igs(b"G#C>1,1:&>0,0,1,0,L,8,100,0,100,9,200,0,200,9:");
1955 assert_eq!((at(&g, 100, 5), at(&g, 200, 5)), (3, 3));
1956 }
1957
1958 #[test]
1959 fn flood_fill_and_patterns() {
1960 let g = igs(b"G#A>0,1,1:B>0,0,40,20,0:A>2,4,0:F>10,10:");
1962 assert_eq!(at(&g, 0, 0), 3, "the border");
1963 let inside: Vec<u8> = (8..12).map(|x| at(&g, x, 10)).collect();
1964 assert!(inside.contains(&3) && inside.contains(&0), "{inside:?}");
1965 assert_eq!(at(&g, 50, 10), 0, "outside");
1966 }
1967
1968 #[test]
1969 fn vt52_text_and_queries() {
1970 let mut g = igs(b"\x1bY\x22\x25X\x1bb\x01Y");
1971 assert!((16..24).any(|y| (40..48).any(|x| at(&g, x, y) == 3)));
1973 assert!((16..24).any(|y| (48..56).any(|x| at(&g, x, y) == 1)));
1974 g.feed(b"G#?>0:?>3:");
1975 assert_eq!(g.replies, b"2.191:");
1976 g.feed(b"G#R>0,0:C>2,15:Z>0,0,9,9:");
1978 assert_eq!((g.width(), at(&g, 5, 5)), (320, 13));
1979 }
1980
1981 #[test]
1982 fn mouse_zones() {
1983 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:");
1984 assert_eq!(g.click(50, 50), Some(b"f/L\r".to_vec()));
1985 assert_eq!(g.click(50, 50), None, "only while waiting");
1986 g.feed(b"G#< 0,2,1:");
1987 assert_eq!(g.click(300, 150), Some(b"Q".to_vec()));
1988 }
1989
1990 #[test]
1991 fn color_cycling_steps_over_time() {
1992 let mut g = igs(b"G#R>0,0:X>8,1,1,1,1:X>8,1,3,1,0:C>2,1:Z>0,0,9,9:");
1994 let pens = |g: &Igs| [1u8, 2, 3].map(|p| g.regs[g.res.register(p) as usize]);
1995 let start = [DESKTOP[15], DESKTOP[1], DESKTOP[2]];
1996 assert_eq!(pens(&g), [start[2], start[0], start[1]]);
1998 assert_eq!(at(&g, 5, 5), 0, "the rest waits for the cycle");
1999 g.poll(Instant::now() + Duration::from_secs(1));
2000 assert_eq!(pens(&g), [start[1], start[2], start[0]]);
2001 assert_eq!(at(&g, 5, 5), 15, "then carries on");
2002 g.feed(b"G#X>8,1,3,0,0:");
2004 assert_eq!(pens(&g), start);
2005 }
2006
2007 #[test]
2008 fn pauses_hold_the_stream() {
2009 let mut g = igs(b"G#t>1:C>2,2:Z>0,0,9,9:");
2010 assert!(g.hold.is_some());
2011 assert_eq!(at(&g, 5, 5), 0);
2012 g.poll(Instant::now() + Duration::from_secs(2));
2013 assert_eq!(at(&g, 5, 5), 1);
2014 }
2015}