1use std::{error, fmt};
4
5pub const HEAD_ROWS: usize = 32;
6pub const MAX_COLUMNS: usize = 8000;
8
9const BYTES_PER_COLUMN: usize = HEAD_ROWS / 8;
10const MAX_BODY_LENGTH: usize = MAX_COLUMNS * BYTES_PER_COLUMN + 28;
11const MAGIC: [u8; 2] = [0x12, 0x34];
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Error {
15 InvalidWidth { width: usize },
16 PixelLength { expected: usize, actual: usize },
17 PixelValue { index: usize, value: u8 },
18 PackedLength { expected: usize, actual: usize },
19 HeaderLength { actual: usize },
20 HeaderMagic,
21 HeaderChecksum,
22 BodyTooLarge { length: usize },
23 InvalidWriteLength { max_write: usize },
24 TooManyChunks { count: usize },
25 StatusLength { actual: usize },
26 StatusPrefix,
27 InvalidCassetteId { id: u8 },
28}
29
30impl fmt::Display for Error {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 Self::InvalidWidth { width } => {
34 write!(f, "raster width {width} is outside 1..={MAX_COLUMNS}")
35 }
36 Self::PixelLength { expected, actual } => {
37 write!(f, "expected {expected} pixels, got {actual}")
38 }
39 Self::PixelValue { index, value } => {
40 write!(f, "pixel {index} has non-binary value {value}")
41 }
42 Self::PackedLength { expected, actual } => {
43 write!(f, "expected {expected} packed bytes, got {actual}")
44 }
45 Self::HeaderLength { actual } => {
46 write!(f, "expected a 9-byte header, got {actual} bytes")
47 }
48 Self::HeaderMagic => f.write_str("invalid header preamble or magic"),
49 Self::HeaderChecksum => f.write_str("invalid header checksum"),
50 Self::BodyTooLarge { length } => {
51 write!(f, "body length {length} exceeds {MAX_BODY_LENGTH}")
52 }
53 Self::InvalidWriteLength { max_write } => {
54 write!(f, "usable write length {max_write} is outside 9..=512")
55 }
56 Self::TooManyChunks { count } => {
57 write!(f, "job requires {count} chunks; maximum is 255")
58 }
59 Self::StatusLength { actual } => {
60 write!(f, "expected a 3-byte status, got {actual} bytes")
61 }
62 Self::StatusPrefix => f.write_str("invalid status prefix"),
63 Self::InvalidCassetteId { id } => {
64 write!(f, "cassette id {id} is outside 1..=5")
65 }
66 }
67 }
68}
69
70impl error::Error for Error {}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct Raster {
75 width: usize,
76 pixels: Vec<u8>,
77}
78
79impl Raster {
80 pub fn new(width: usize, pixels: Vec<u8>) -> Result<Self, Error> {
81 validate_width(width)?;
82 let expected = width * HEAD_ROWS;
83 if pixels.len() != expected {
84 return Err(Error::PixelLength {
85 expected,
86 actual: pixels.len(),
87 });
88 }
89 if let Some((index, &value)) = pixels.iter().enumerate().find(|(_, value)| **value > 1) {
90 return Err(Error::PixelValue { index, value });
91 }
92 Ok(Self { width, pixels })
93 }
94
95 pub fn width(&self) -> usize {
96 self.width
97 }
98
99 pub fn pixels(&self) -> &[u8] {
100 &self.pixels
101 }
102
103 pub fn get(&self, x: usize, y: usize) -> bool {
105 x < self.width && y < HEAD_ROWS && self.pixels[y * self.width + x] == 1
106 }
107
108 pub fn pack_columns(&self) -> Vec<u8> {
109 let mut packed = vec![0; self.width * BYTES_PER_COLUMN];
110 for x in 0..self.width {
111 for y in 0..HEAD_ROWS {
112 if self.get(x, y) {
113 packed[x * 4 + 3 - y / 8] |= 1 << (7 - y % 8);
114 }
115 }
116 }
117 packed
118 }
119
120 pub fn from_packed_columns(width: usize, packed: &[u8]) -> Result<Self, Error> {
121 validate_width(width)?;
122 let expected = width * BYTES_PER_COLUMN;
123 if packed.len() != expected {
124 return Err(Error::PackedLength {
125 expected,
126 actual: packed.len(),
127 });
128 }
129 let mut pixels = vec![0; width * HEAD_ROWS];
130 for x in 0..width {
131 for y in 0..HEAD_ROWS {
132 pixels[y * width + x] =
133 u8::from(packed[x * 4 + 3 - y / 8] & (1 << (7 - y % 8)) != 0);
134 }
135 }
136 Self::new(width, pixels)
137 }
138}
139
140fn validate_width(width: usize) -> Result<(), Error> {
141 if !(1..=MAX_COLUMNS).contains(&width) {
142 return Err(Error::InvalidWidth { width });
143 }
144 Ok(())
145}
146
147#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
148pub enum Profile {
149 #[default]
151 Modern,
152 Legacy,
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct Job {
158 body: Vec<u8>,
159}
160
161impl Job {
162 pub fn new(raster: &Raster, profile: Profile) -> Result<Self, Error> {
163 let mut body = Vec::with_capacity(raster.width * BYTES_PER_COLUMN + 28);
164 body.extend_from_slice(&[0x1b, 0x73, 0x9a, 0x02, 0x00, 0x00]);
165 if profile == Profile::Modern {
166 body.extend_from_slice(&[0x1b, 0x23, 0x01]);
167 }
168 let mode = if profile == Profile::Modern {
169 0x81
170 } else {
171 0x01
172 };
173 body.extend_from_slice(&[0x1b, 0x44, mode, 0x02]);
174 body.extend_from_slice(&(raster.width as u32).to_le_bytes());
175 body.extend_from_slice(&(HEAD_ROWS as u32).to_le_bytes());
176 body.extend_from_slice(&raster.pack_columns());
177 match profile {
178 Profile::Modern => body.extend_from_slice(&[0x1b, 0x70, 0x30]),
179 Profile::Legacy => body.extend_from_slice(&[0x1b, 0x45]),
180 }
181 body.extend_from_slice(&[0x1b, 0x41, 0x1b, 0x51]);
182 Ok(Self { body })
183 }
184
185 pub fn body(&self) -> &[u8] {
186 &self.body
187 }
188
189 pub fn writes(&self, max_write: usize) -> Result<Vec<Vec<u8>>, Error> {
195 if !(9..=512).contains(&max_write) {
196 return Err(Error::InvalidWriteLength { max_write });
197 }
198 let ceiling = 500.min(max_write - 3);
199 let count = self.body.len().div_ceil(ceiling);
200 if count > 255 {
201 return Err(Error::TooManyChunks { count });
202 }
203 let mut header = vec![0xff, 0xf0, MAGIC[0], MAGIC[1]];
204 header.extend_from_slice(&(self.body.len() as u32).to_le_bytes());
205 header.push(checksum(&header));
206 let mut writes = Vec::with_capacity(count + 1);
207 writes.push(header);
208 for (i, bytes) in self.body.chunks(ceiling).enumerate() {
209 let mut chunk = Vec::with_capacity(bytes.len() + 3);
210 chunk.push(if i >= 27 { (i + 1) as u8 } else { i as u8 });
211 chunk.extend_from_slice(bytes);
212 if i + 1 == count {
213 chunk.extend_from_slice(&MAGIC);
214 }
215 writes.push(chunk);
216 }
217 Ok(writes)
218 }
219}
220
221fn checksum(bytes: &[u8]) -> u8 {
222 bytes.iter().fold(0, |sum, byte| sum.wrapping_add(*byte))
223}
224
225pub const CASSETTE_ID_12MM: u8 = 3;
230
231pub fn set_cassette_type_write(cassette_id: u8) -> Result<Vec<u8>, Error> {
238 if !(1..=5).contains(&cassette_id) {
239 return Err(Error::InvalidCassetteId { id: cassette_id });
240 }
241 let mut body = Vec::with_capacity(14);
242 body.extend_from_slice(&[0x1b, 0x73, 0x9a, 0x02, 0x00, 0x00]);
243 body.extend_from_slice(&[0x1b, 0x4d, cassette_id, 0x00, 0x00, 0x00]);
244 body.extend_from_slice(&[0x1b, 0x51]);
245 let mut write = vec![0xff, 0xf0, MAGIC[0], MAGIC[1]];
246 write.extend_from_slice(&(body.len() as u32).to_le_bytes());
247 write.push(checksum(&write));
248 write.extend_from_slice(&body);
249 Ok(write)
250}
251
252pub fn parse_header(bytes: &[u8]) -> Result<usize, Error> {
255 if bytes.len() != 9 {
256 return Err(Error::HeaderLength {
257 actual: bytes.len(),
258 });
259 }
260 if bytes[..4] != [0xff, 0xf0, MAGIC[0], MAGIC[1]] {
261 return Err(Error::HeaderMagic);
262 }
263 if checksum(&bytes[..8]) != bytes[8] {
264 return Err(Error::HeaderChecksum);
265 }
266 let length = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as usize;
267 if length > MAX_BODY_LENGTH {
268 return Err(Error::BodyTooLarge { length });
269 }
270 Ok(length)
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub enum Status {
275 Success,
276 Unconfirmed(u8),
277}
278
279impl Status {
280 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
282 match bytes {
283 [0x1b, 0x52, 0] => Ok(Self::Success),
284 [0x1b, 0x52, raw] => Ok(Self::Unconfirmed(*raw)),
285 [_, _, _] => Err(Error::StatusPrefix),
286 _ => Err(Error::StatusLength {
287 actual: bytes.len(),
288 }),
289 }
290 }
291}