1extern crate serde;
2extern crate codepage_437;
3extern crate image;
4extern crate qrcode;
5
6pub use self::print_data::{PrintData, PrintDataBuilder};
7pub use self::justification::{Justification};
8pub use self::escpos_image::EscposImage;
9
10mod print_data;
11mod justification;
12mod escpos_image;
13
14use qrcode::QrCode;
15use codepage_437::{IntoCp437, CP437_CONTROL};
16use crate::{
17 Error, PrinterProfile,
18 command::{Command, Font}
19};
20use serde::{Serialize, Deserialize};
21use std::collections::HashSet;
22
23#[derive(Serialize, Deserialize, Clone, Debug)]
29#[serde(tag = "kind")]
30pub enum Instruction {
31 Compound {
33 instructions: Vec<Instruction>
34 },
35 Command {
37 command: Command
38 },
39 VSpace {
41 lines: u8
42 },
43 Text {
45 content: String,
47 markdown: bool,
49 font: Font,
51 justification: Justification,
53 replacements: Option<HashSet<String>>
55 },
56 DuoTable {
58 name: String,
60 header: (String, String),
62 font: Font
64 },
65 TrioTable {
67 name: String,
68 header: (String, String, String)
69 },
70 QuadTable {
72 name: String,
73 header: (String, String, String)
74 },
75 Image {
77 image: EscposImage
79 },
80 QRCode {
82 name: String
84 },
85 Cut
87}
88
89impl std::ops::Add<Instruction> for Instruction {
91 type Output = Instruction;
92 fn add(self, rhs: Instruction) -> Self::Output {
93 match self {
94 Instruction::Compound{mut instructions} => {
95 match rhs {
96 Instruction::Compound{instructions: mut rhs_instructions} => {
97 instructions.append(&mut rhs_instructions);
98 },
99 rhs => {
100 instructions.push(rhs);
101 }
102 }
103 Instruction::Compound{instructions}
104 },
105 lhs => {
106 let mut instructions = vec![lhs];
107 match rhs {
108 Instruction::Compound{instructions: mut rhs_instructions} => {
109 instructions.append(&mut rhs_instructions);
110 },
111 rhs => {
112 instructions.push(rhs);
113 }
114 }
115 Instruction::Compound{instructions}
116 }
117 }
118 }
119}
120
121impl std::iter::FromIterator<Instruction> for Option<Instruction> {
123 fn from_iter<I: IntoIterator<Item=Instruction>>(iter: I) -> Self {
124 let mut r = None;
125
126 for elem in iter {
127 if let Some(rd) = &mut r {
128 *rd += elem;
129 } else {
130 r = Some(elem);
131 }
132 }
133
134 r
135 }
136}
137
138impl std::ops::AddAssign for Instruction {
140 fn add_assign(&mut self, other: Self) {
141 if !self.is_compound() {
143 *self = Instruction::Compound{instructions: vec![self.clone()]};
145 }
146
147 match self {
148 Instruction::Compound{instructions} => {
149 match other {
150 Instruction::Compound{instructions: mut other_instructions} => {
151 instructions.append(&mut other_instructions);
152 },
153 other => {
154 instructions.push(other);
155 }
156 }
157 },
158 _ => panic!("Impossible error")
159 }
160 }
161}
162
163impl Instruction {
164 pub fn is_compound(&self) -> bool {
166 matches!(self, Instruction::Compound{..})
167 }
168
169 pub fn is_text(&self) -> bool {
171 matches!(self, Instruction::Text{..})
172 }
173
174 pub fn text<A: Into<String>>(content: A, font: Font, justification: Justification, replacements: Option<HashSet<String>>) -> Instruction {
178 Instruction::Text {
179 content: content.into(),
180 markdown: false,
181 font,
182 justification,
183 replacements
184 }
185 }
186
187 pub fn markdown(content: String, font: Font, justification: Justification, replacements: Option<HashSet<String>>) -> Instruction {
193 Instruction::Text {
194 content,
195 markdown: true,
196 font,
197 justification,
198 replacements
199 }
200 }
201
202 pub fn image(image: EscposImage) -> Result<Instruction, Error> {
206 Ok(Instruction::Image {
207 image
208 })
209 }
210
211 pub fn qr_code(content: String) -> Result<Instruction, Error> {
213 let code = QrCode::new(content.as_bytes()).unwrap();
214 let img = code.render::<image::Rgba<u8>>().build();
216
217 let escpos_image = EscposImage::new(
218 image::DynamicImage::ImageRgba8(img),128,
220 Justification::Center
221 )?;
222
223 Instruction::image(escpos_image)
224 }
225
226 pub fn dynamic_qr_code<A: Into<String>>(name: A) -> Instruction {
228 Instruction::QRCode{name: name.into()}
229 }
230
231 pub fn command(command: Command) -> Instruction {
233 Instruction::Command {
234 command
235 }
236 }
237
238 pub fn duo_table<A: Into<String>, B: Into<String>, C: Into<String>>(name: A, header: (B, C), font: Font) -> Instruction {
240 Instruction::DuoTable {
241 name: name.into(),
242 header: (header.0.into(), header.1.into()),
243 font
244 }
245 }
246
247 pub fn trio_table<A: Into<String>, B: Into<String>, C: Into<String>, D: Into<String>>(name: A, header: (B, C, D)) -> Instruction {
249 Instruction::TrioTable {
250 name: name.into(),
251 header: (header.0.into(), header.1.into(), header.2.into())
252 }
253 }
254
255 pub fn quad_table<A: Into<String>, B: Into<String>, C: Into<String>, D: Into<String>>(name: A, header: (B, C, D)) -> Instruction {
259 Instruction::QuadTable {
260 name: name.into(),
261 header: (header.0.into(), header.1.into(), header.2.into())
262 }
263 }
264
265 pub fn cut() -> Instruction {
267 Instruction::Cut
268 }
269
270 pub fn vspace(lines: u8) -> Instruction {
272 Instruction::VSpace{lines}
273 }
274
275 pub(crate) fn to_vec(&self, printer_profile: &PrinterProfile, print_data: Option<&PrintData>) -> Result<Vec<u8>, Error> {
279 let mut target = Vec::new();
280 match self {
281 Instruction::Compound{instructions} => {
282 for instruction in instructions {
283 target.append(&mut instruction.to_vec(printer_profile, print_data)?);
284 }
285 },
286 Instruction::Cut => {
287 target.extend_from_slice(&Command::Cut.as_bytes());
288 },
289 Instruction::Command{command} => {
290 target.append(&mut command.as_bytes());
291 }
292 Instruction::VSpace{lines} => {
293 target.append(&mut vec![b'\n'; *lines as usize])
294 },
295 Instruction::Image{image} => {
296 target.extend_from_slice(&image.feed(printer_profile.width));
297 },
298 Instruction::QRCode{name} => {
299 let print_data = print_data.ok_or(Error::NoPrintData)?;
300 if let Some(qr_contents) = &print_data.qr_contents {
301 if let Some(qr_content) = qr_contents.get(name) {
302 target.extend_from_slice(&Instruction::qr_code(qr_content.clone())?.to_vec(printer_profile, Some(print_data))?)
303 } else {
304 return Err(Error::NoQrContent(name.clone()))
305 }
306 } else {
307 return Err(Error::NoQrContents)
308 }
309 },
310 Instruction::Text{content, markdown, font, justification, replacements: self_replacements} => {
312 target.append(&mut Command::SelectFont{font: font.clone()}.as_bytes());
314
315 let width = match printer_profile.columns_per_font.get(&font) {
317 Some(w) => *w,
318 None => return Err(Error::NoWidth)
319 };
320
321 let mut replaced_string = content.clone();
322 if let Some(self_replacements) = &self_replacements {
324 if !self_replacements.is_empty() {
325 let print_data = print_data.ok_or(Error::NoPrintData)?;
326
327 for key in self_replacements.iter() {
328 if let Some(replacement) = print_data.replacements.get(key) {
329 replaced_string = replaced_string.as_str().replace(key, replacement);
330 } else {
331 return Err(Error::NoReplacementFound(key.clone()))
332 }
333 }
334 }
335 }
336
337 let demarkdown_string = if *markdown {
339 let mut _tmp = String::new();
341 panic!("Not implemented the markdown thingy, is too hard!");
342 } else {
343 replaced_string
344 };
345
346 let mut result = Command::Reset.as_bytes();
348 let mut line = String::new();
350 let tokens = demarkdown_string.split_whitespace();
351 let mut width_count = 0;
352
353 for token in tokens {
354 if width_count + token.len() + 1 > (width as usize) {
355 width_count = token.len();
357 let mut tmp = match justification {
359 Justification::Left => format!("{}\n", line),
360 Justification::Right => format!("{:>1$}\n", line, width as usize),
361 Justification::Center => format!("{:^1$}\n", line, width as usize)
362 }.into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?;
363 result.append(&mut tmp);
364
365 line = token.to_string();
367 } else {
368 width_count += token.len();
369 if !line.is_empty() {
370 width_count += 1;
371 line += " ";
372 }
373 line += token;
374 }
375 }
376
377 if !line.is_empty() {
379 let mut tmp = match justification {
380 Justification::Left => format!("{}\n", line),
381 Justification::Right => format!("{:>1$}\n", line, width as usize),
382 Justification::Center => format!("{:^1$}\n", line, width as usize)
383 }.into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?;
384 result.append(&mut tmp);
385 }
386
387 target.append(&mut result);
388 },
389 Instruction::DuoTable{name, header, font} => {
390 let width = match printer_profile.columns_per_font.get(&font) {
392 Some(w) => *w,
393 None => return Err(Error::NoWidth)
394 };
395 target.extend_from_slice(&format!("{}{:>2$}\n", header.0, header.1, (width as usize) - header.0.len()).into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?);
397
398 target.append(&mut vec![b'-'; width as usize]);
400 target.push(b'\n');
401
402 let print_data = print_data.ok_or(Error::NoPrintData)?;
404
405 if let Some(tables) = &print_data.duo_tables {
406 if let Some(table) = tables.get(name) {
407 for row in table {
408 target.extend_from_slice(&format!("{}{:>2$}\n", row.0, row.1, (width as usize) - row.0.len()).into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?)
409 }
410 } else {
411 return Err(Error::NoTableFound(name.clone()))
412 }
413 } else {
414 return Err(Error::NoTables)
415 }
416 },
417 Instruction::TrioTable{name, header} => {
418 let print_data = print_data.ok_or(Error::NoPrintData)?;
420
421 let mut max_left: usize = header.0.len();
422 let mut max_middle: usize = header.1.len();
423 let mut max_right: usize = header.2.len();
424
425 if let Some(tables) = &print_data.trio_tables {
426 if let Some(table) = tables.get(name) {
427 for row in table {
428 if row.0.len() > max_left {
429 max_left = row.0.len();
430 }
431 if row.1.len() > max_middle {
432 max_middle = row.1.len();
433 }
434 if row.2.len() > max_right {
435 max_right = row.2.len();
436 }
437 }
438 } else {
439 return Err(Error::NoTableFound(name.clone()))
440 }
441 } else {
442 return Err(Error::NoTables)
443 }
444
445 let width = match printer_profile.columns_per_font.get(&Font::FontA) {
447 Some(w) => *w,
448 None => return Err(Error::NoWidth)
449 } as usize;
450
451 let (max_left, max_right) = if max_left + max_middle + max_right + 2 <= width {
452 (max_left, max_right)
454 } else if max_middle + max_right + 2 <= width && width - max_middle - max_right - 2 > 2 {
455 (width - max_middle - max_right - 2, max_right)
457 } else {
458 let third = width / 3;
460 if width % 3 == 0 {
461 (third, third)
462 } else if width % 3 == 1 {
463 (third, third)
464 } else {
465 (third, third)
466 }
467 };
468
469 target.extend_from_slice(
471 &trio_row(header.clone(), width, max_left, max_right)
472 .into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?);
473
474 target.append(&mut vec![b'-'; width]);
476 target.push(b'\n');
477
478 if let Some(tables) = &print_data.trio_tables {
480 if let Some(table) = tables.get(name) {
481 for row in table {
482 target.extend_from_slice(
483 &trio_row(row.clone(), width, max_left, max_right)
484 .into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?);
485 }
486 } else {
487 return Err(Error::NoTableFound(name.clone()))
488 }
489 } else {
490 return Err(Error::NoTables)
491 }
492 },
493 Instruction::QuadTable{name, header} => {
494 let print_data = print_data.ok_or(Error::NoPrintData)?;
496
497 let mut max_left: usize = header.0.len();
498 let mut max_middle: usize = header.1.len();
499 let mut max_right: usize = header.2.len();
500 if let Some(tables) = &print_data.quad_tables {
501 if let Some(table) = tables.get(name) {
502 for row in table {
503 if row.1.len() > max_left {
504 max_left = row.1.len();
505 }
506 if row.2.len() > max_middle {
507 max_middle = row.2.len();
508 }
509 if row.3.len() > max_right {
510 max_right = row.3.len();
511 }
512 }
513 } else {
514 return Err(Error::NoTableFound(name.clone()))
515 }
516 } else {
517 return Err(Error::NoTables)
518 }
519
520 let width = match printer_profile.columns_per_font.get(&Font::FontA) {
522 Some(w) => *w,
523 None => return Err(Error::NoWidth)
524 } as usize;
525
526 let (max_left, max_right) = if max_left + max_middle + max_right + 2 <= width {
527 (max_left, max_right)
529 } else if max_middle + max_right + 2 <= width && width - max_middle - max_right - 2 > 2 {
530 (width - max_middle - max_right - 2, max_right)
532 } else {
533 let third = width / 3;
535 if width % 3 == 0 {
537 (third, third)
538 } else if width % 3 == 1 {
539 (third, third)
540 } else {
541 (third, third)
542 }
543 };
544
545 target.extend_from_slice(
547 &trio_row((header.0.clone(), header.1.clone(), header.2.clone()), width, max_left, max_right)
548 .into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?);
549
550 target.append(&mut vec![b'-'; width]);
552 target.push(b'\n');
553
554 if let Some(tables) = &print_data.quad_tables {
556 if let Some(table) = tables.get(name) {
557 for row in table {
558 target.extend_from_slice(&Command::SelectFont{font: Font::FontB}.as_bytes());
560 target.extend_from_slice(&format!("{}\n", row.0).into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?);
561 target.extend_from_slice(&Command::SelectFont{font: Font::FontA}.as_bytes());
562 target.extend_from_slice(
564 &trio_row((row.1.clone(), row.2.clone(), row.3.clone()), width, max_left, max_right)
565 .into_cp437(&CP437_CONTROL).map_err(|_| Error::Encoding)?);
566 }
567 } else {
568 return Err(Error::NoTableFound(name.clone()))
569 }
570 } else {
571 return Err(Error::NoTables)
572 }
573 }
574 }
575 Ok(target)
576 }
577}
578
579fn trio_row(mut row: (String, String, String), width: usize, max_left: usize, max_right: usize) -> String {
581 if row.0.len() > max_left {
582 row.0.replace_range((max_left-2).., "..");
583 }
584 if row.1.len() > width - max_left - max_right - 2 {
585 row.1.replace_range((width - max_left - max_right - 2).., "..");
586 }
587 if row.2.len() > max_left {
588 row.2.replace_range((max_right-2).., "..");
589 }
590 row.0.truncate(max_left);
591 row.2.truncate(max_right);
592 row.1.truncate(width - max_left - max_right - 2);
593
594 format!("{:<3$}{:^4$}{:>5$}\n",
595 row.0, row.1, row.2, max_left, width - max_left - max_right, max_right )
598}