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