spectrusty_utils/printer/
epson_gfx.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8use std::io;
9
10#[cfg(feature = "snapshot")]
11use serde::{Serialize, Deserialize};
12#[allow(unused_imports)]
13use log::{error, warn, info, debug, trace};
14
15use super::*;
16
17/// A simple EPSON printer graphics data interceptor that can produce images via [DotMatrixGfx] trait.
18///
19/// Use its [EpsonPrinterGfx::intercept] method to filter data being sent to the printer.
20///
21/// # Note
22/// This is a very simple implementation, which in reality is able to catch only the output of COPY and COPY EXP
23/// commands found in Spectrum's ROMs: 128k, +2, and +3.
24#[derive(Clone, Default, Debug)]
25#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
26#[cfg_attr(feature = "snapshot", serde(default))]
27pub struct EpsonPrinterGfx {
28    #[cfg_attr(feature = "snapshot", serde(skip))]
29    state: GrabberState,
30    #[cfg_attr(feature = "snapshot", serde(skip))]
31    line_mm: f32,
32    #[cfg_attr(feature = "snapshot", serde(skip))]
33    eo_line: usize,
34    #[cfg_attr(feature = "snapshot", serde(skip))]
35    buf: Vec<u8>
36}
37
38// http://www.lprng.com/RESOURCES/EPSON/epson.htm
39const ESC_CODE: u8 = 0x1B;
40const CR_CODE: u8 = 0x0D;
41const LF_CODE: u8 = 0x0A;
42const DATA_LINE_WIDTH: usize = 3 * 256;
43const GRAPHICS_LINE_START: &[u8;6] = &[ESC_CODE, b'1', ESC_CODE, b'L', 0x00, 0x03]; // with 7/72 inch line spacing
44const GRAPHICS_EXP_START: &[u8;6] = &[CR_CODE, LF_CODE, ESC_CODE, b'L', 0x00, 0x03];
45const GRAPHICS_SPACING: u8 = b'3';//, n]; // Select n/216 inch line spacing
46const GRAPHICS_ENDS:  u8 = b'2';
47const GRAPHICS_RESET: u8 = b'@';
48
49const INCH_MM: f32 = 25.4;
50const INCH7_BY_72_LINE_MM: f32 = 2.47;
51
52#[derive(Clone, Copy, Debug, PartialEq)]
53enum GrabberState {
54    NoMatch,
55    LineStart(usize),
56    SelectSpacing,
57    LineData(usize),
58}
59
60impl Default for GrabberState {
61    fn default() -> Self {
62        GrabberState::NoMatch
63    }
64}
65
66impl EpsonPrinterGfx {
67    /// Returns a reference to the byte sent to the printer or captures it if an escape code arrives.
68    ///
69    /// In this instance all subsequent writes are being buffered until one of two things happens:
70    ///
71    /// * Either a collected escape sequence matches the signature of graphic data being sent to the printer.
72    ///   In this instance, the image is being spooled and will be available via [DotMatrixGfx] methods.
73    /// * Or a collected escape sequence data does not match the signature. In this instance, a reference to
74    ///   the buffered data is being returned so it can be passed back to the upstream.
75    pub fn intercept<'a, 'b: 'a>(&'a mut self, ch: &'b u8) -> Option<&'a [u8]> {
76        match self.state {
77            GrabberState::NoMatch if *ch == ESC_CODE => {
78                self.buf.truncate(self.eo_line);
79                self.buf.push(*ch);
80                self.state = GrabberState::LineStart(1);
81                self.line_mm = INCH7_BY_72_LINE_MM;
82                None
83            }
84            GrabberState::NoMatch => Some(core::slice::from_ref(ch)),
85            GrabberState::SelectSpacing => {
86                self.line_mm = *ch as f32 / 216.0 * INCH_MM;
87                self.buf.truncate(self.eo_line);
88                self.state = GrabberState::LineStart(0);
89                None
90            }
91            GrabberState::LineStart(mut index) => {
92                self.buf.push(*ch);
93                if ch == &GRAPHICS_LINE_START[index] || ch == &GRAPHICS_EXP_START[index] {
94                    index +=1;
95                    if index == GRAPHICS_LINE_START.len() {
96                        debug_assert_eq!(GRAPHICS_LINE_START.len(), GRAPHICS_EXP_START.len());
97                        self.buf.truncate(self.eo_line);
98                        self.state = GrabberState::LineData(0);
99                    }
100                    else {
101                        self.state = GrabberState::LineStart(index);
102                    }
103                    None
104                }
105                else if index == 1 && (*ch == GRAPHICS_ENDS ||
106                                       *ch == GRAPHICS_RESET ||
107                                       *ch == GRAPHICS_SPACING) {
108                    self.buf.truncate(self.eo_line);
109                    self.state = if *ch == GRAPHICS_SPACING {
110                        GrabberState::SelectSpacing
111                    }
112                    else {
113                        GrabberState::NoMatch
114                    };
115                    None
116                }
117                else {
118                    self.state = GrabberState::NoMatch;
119                    Some(&self.buf[self.eo_line..])
120                }
121            }
122            GrabberState::LineData(width) => {
123                if width == DATA_LINE_WIDTH {
124                    self.state = GrabberState::NoMatch;
125                    if *ch == LF_CODE { // COPY line
126                        self.eo_line = self.buf.len();
127                        None
128                    }
129                    else if *ch == CR_CODE || *ch == ESC_CODE { // COPY EXP next line or end
130                        self.eo_line = self.buf.len();
131                        self.state = GrabberState::LineStart(1);
132                        None
133                    }
134                    else { // the printing must have been interrupted
135                           // since we don't know where did it stop exactly,
136                           // just flush out buffered data
137                        self.buf.push(*ch);
138                        Some(&self.buf[self.eo_line..])
139                    }
140                }
141                else {
142                    self.buf.push(*ch);
143                    self.state = GrabberState::LineData(width + 1);
144                    None
145                }
146            }
147        }
148    }
149}
150
151impl DotMatrixGfx for EpsonPrinterGfx {
152    fn is_spooling(&self) -> bool {
153        self.state != GrabberState::NoMatch
154    }
155
156    fn lines_buffered(&self) -> usize {
157        self.eo_line / DATA_LINE_WIDTH
158    }
159
160    fn clear(&mut self) {
161        self.buf.drain(..self.eo_line);
162        self.eo_line = 0;
163    }
164
165    fn write_svg_dot_gfx_lines(&self, description: &str, target: &mut dyn io::Write) -> io::Result<bool> {
166        let lines = self.lines_buffered();
167        if lines == 0 {
168            return Ok(false)
169        }
170        write!(target, r##"<?xml version="1.0" standalone="no"?>
171<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
172  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
173<svg width="190mm" height="{height_mm:.2}mm" version="1.1"
174     viewBox="0 0 {pixel_width} {pixel_height}" preserveAspectRatio="none"
175     xmlns="http://www.w3.org/2000/svg">
176  <desc>{description}</desc>
177  <g stroke-width="0.125" stroke="#333" fill="#000">
178"##,
179            description=description,
180            height_mm=self.line_mm * lines as f32,
181            pixel_width=DATA_LINE_WIDTH,
182            pixel_height = lines * 8)?;
183        for (row, line) in self.buf[..self.eo_line].chunks(DATA_LINE_WIDTH).enumerate() {
184            for (x, mut dots) in line.iter().copied().enumerate() {
185                let y = row * 8;
186                for i in 0..8 {
187                    dots = dots.rotate_left(1);
188                    if dots & 1 == 1 {
189                        write!(target, r##"<circle cx="{}" cy="{}" r="0.5"/>"##,
190                                x, y + i)?;
191                    }
192                }
193            }
194        }
195        target.write_all(b"</g></svg>")?;
196        Ok(true)
197    }
198
199    fn write_gfx_data(&mut self, _target: &mut Vec<u8>) -> Option<(u32, u32)> {
200        // TODO: implement
201        None
202    }
203}