use anyhow::{anyhow, Ok, Result};
use log::debug;
use std::{fmt::Display, io::Write};
use strum_macros::Display;
#[derive(Debug, Clone)]
pub enum Size {
Imperial(f32),
Metric(f32),
Dots(u32),
}
impl Size {
fn to_dots_raw(&self, resolution: u32) -> u32 {
match self {
Self::Imperial(x) => (*x * resolution as f32) as u32,
Self::Metric(x) => (*x / 25.4 * resolution as f32) as u32,
Self::Dots(x) => *x,
}
}
}
impl Display for Size {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Imperial(x) => write!(f, "{x}"),
Self::Metric(x) => write!(f, "{x} mm"),
Self::Dots(x) => write!(f, "{x} dot"),
}
}
}
#[derive(Debug, Display)]
pub enum Country {
#[strum(serialize = "1")]
Usa = 1,
#[strum(serialize = "2")]
CanadianFrench = 2,
#[strum(serialize = "3")]
SpanishLatinAmerica = 3,
#[strum(serialize = "31")]
Dutch = 31,
#[strum(serialize = "32")]
Belgian = 32,
#[strum(serialize = "33")]
French = 33,
#[strum(serialize = "34")]
Spanish = 34,
#[strum(serialize = "36")]
Hungarian = 36,
#[strum(serialize = "38")]
Yugoslavian = 38,
#[strum(serialize = "39")]
Italian = 39,
#[strum(serialize = "41")]
Switzerland = 41,
#[strum(serialize = "42")]
Slovak = 42,
#[strum(serialize = "44")]
UnitedKingdom = 44,
#[strum(serialize = "45")]
Danish = 45,
#[strum(serialize = "46")]
Swedish = 46,
#[strum(serialize = "47")]
Norwegian = 47,
#[strum(serialize = "48")]
Polish = 48,
#[strum(serialize = "49")]
German = 49,
#[strum(serialize = "55")]
Brazil = 55,
#[strum(serialize = "61")]
English = 61,
#[strum(serialize = "351")]
Portuguese = 351,
#[strum(serialize = "358")]
Finnish = 358,
}
#[derive(Debug, Display)]
pub enum Codepage7Bit {
#[strum(serialize = "USA")]
Usa,
#[strum(serialize = "BRI")]
British,
#[strum(serialize = "GER")]
German,
#[strum(serialize = "FRE")]
French,
#[strum(serialize = "DAN")]
Danish,
#[strum(serialize = "ITA")]
Italian,
#[strum(serialize = "SPA")]
Spanish,
#[strum(serialize = "SWE")]
Swedish,
#[strum(serialize = "SWI")]
Swiss,
}
#[derive(Debug, Display)]
pub enum Codepage8Bit {
#[strum(serialize = "437")]
UnitedStates,
#[strum(serialize = "737")]
Greek,
#[strum(serialize = "850")]
Multilingual,
#[strum(serialize = "851")]
Greek1,
#[strum(serialize = "852")]
Slavic,
#[strum(serialize = "855")]
Cyrillic,
#[strum(serialize = "857")]
Turkish,
#[strum(serialize = "860")]
Portuguese,
#[strum(serialize = "861")]
Icelandic,
#[strum(serialize = "862")]
Hebrew,
#[strum(serialize = "863")]
CanadianFrench,
#[strum(serialize = "864")]
Arabic,
#[strum(serialize = "865")]
Nordic,
#[strum(serialize = "866")]
Russian,
#[strum(serialize = "869")]
Greek2,
}
#[derive(Debug, Display)]
pub enum CodepageWindows {
#[strum(serialize = "1250")]
CentralEurope,
#[strum(serialize = "1251")]
Cyrillic,
#[strum(serialize = "1252")]
Latin1,
#[strum(serialize = "1253")]
Greek,
#[strum(serialize = "1254")]
Turkish,
#[strum(serialize = "1255")]
Hebrew,
#[strum(serialize = "1256")]
Arabic,
#[strum(serialize = "1257")]
Baltic,
#[strum(serialize = "1258")]
Vietnam,
#[strum(serialize = "932")]
Japanese,
#[strum(serialize = "936")]
ChineseSiplified,
#[strum(serialize = "949")]
Korean,
#[strum(serialize = "950")]
ChineseTraditional,
#[strum(serialize = "UTF-8")]
Utf8,
}
#[derive(Debug, Display)]
pub enum CodepageIso {
#[strum(serialize = "8859-1")]
Latin1,
#[strum(serialize = "8859-2")]
Latin2,
#[strum(serialize = "8859-3")]
Latin3,
#[strum(serialize = "8859-4")]
Baltic,
#[strum(serialize = "8859-5")]
Cyrillic,
#[strum(serialize = "8859-6")]
Arabic,
#[strum(serialize = "8859-7")]
Greek,
#[strum(serialize = "8859-8")]
Hebrew,
#[strum(serialize = "8859-9")]
Turkish,
#[strum(serialize = "8859-10")]
Latin6,
#[strum(serialize = "8859-15")]
Latin9,
}
#[derive(Debug, Display)]
pub enum Codepage {
Codepage7Bit(Codepage7Bit),
Codepage8Bit(Codepage8Bit),
CodepageWindows(CodepageWindows),
CodepageIso(CodepageIso),
}
#[derive(Debug)]
pub struct Tape {
pub width: Size,
pub height: Option<Size>,
pub gap: Size,
pub gap_offset: Option<Size>,
}
#[derive(Debug, Display)]
pub enum Selftest {
#[strum(serialize = "")]
All,
#[strum(serialize = "PATTERN")]
Pattern,
#[strum(serialize = "ETHERNET")]
Ethernet,
#[strum(serialize = "WLAN")]
Wlan,
#[strum(serialize = "RS232")]
Rs232,
#[strum(serialize = "SYSTEM")]
System,
#[strum(serialize = "Z")]
Z,
#[strum(serialize = "BT")]
Bt,
}
#[derive(Debug, Display)]
pub enum Barcode {
#[strum(serialize = "128")]
Barcode128,
#[strum(serialize = "128M")]
Barcode128M,
#[strum(serialize = "EAN128")]
BarcodeEan128,
#[strum(serialize = "EAN128M")]
BarcodeEan128M,
#[strum(serialize = "25")]
Barcode25,
#[strum(serialize = "25C")]
Barcode25C,
#[strum(serialize = "25S")]
Barcode25S,
#[strum(serialize = "25I")]
Barcode25I,
#[strum(serialize = "39")]
Barcode39,
#[strum(serialize = "39C")]
Barcode39C,
#[strum(serialize = "93")]
Barcode93,
#[strum(serialize = "EAN13")]
BarcodeEan13,
#[strum(serialize = "EAN13+2")]
BarcodeEan13Plus2,
#[strum(serialize = "EAN13+5")]
BarcodeEan13Plus5,
#[strum(serialize = "EAN8")]
BarcodeEan8,
#[strum(serialize = "EAN8+2")]
BarcodeEan8Plus2,
#[strum(serialize = "EAN8+5")]
BarcodeEan8Plus5,
#[strum(serialize = "CODA")]
BarcodeCoda,
#[strum(serialize = "POST")]
BarcodePost,
#[strum(serialize = "UPCA")]
BarcodeUpca,
#[strum(serialize = "UPCA+2")]
BarcodeUpcaPlus2,
#[strum(serialize = "UPCA+5")]
BarcodeUpaPlus5,
#[strum(serialize = "UPCE")]
BarcodeUpce,
#[strum(serialize = "UPCE+2")]
BarcodeUpcePlus2,
#[strum(serialize = "UPCE+5")]
BarcodeUpePlus5,
#[strum(serialize = "MSI")]
BarcodeMsi,
#[strum(serialize = "MSIC")]
BarcodeMsic,
#[strum(serialize = "PLESSEY")]
BarcodePlessey,
#[strum(serialize = "CPOST")]
BarcodeCpost,
#[strum(serialize = "ITF14")]
BarcodeItf14,
#[strum(serialize = "EAN14")]
BarcodeEan14,
#[strum(serialize = "11")]
Barcode11,
#[strum(serialize = "TELEPEN")]
BarcodeTelepen,
#[strum(serialize = "TELEPENN")]
BarcodeTelepenN,
#[strum(serialize = "PLANET")]
BarcodePlanet,
#[strum(serialize = "CODE49")]
BarcodeCode49,
#[strum(serialize = "DPI")]
BarcodeDpi,
#[strum(serialize = "DPL")]
BarcodeDpl,
#[strum(serialize = "LOGMARS")]
BarcodeLogmars,
}
#[derive(Debug, Display)]
pub enum RssType {
#[strum(serialize = "RSS14")]
Rss14,
#[strum(serialize = "RSS14T")]
Rss14T,
#[strum(serialize = "RSS14S")]
Rss14S,
#[strum(serialize = "RSS14SO")]
Rss14So,
#[strum(serialize = "RSSLIM")]
RssLim,
#[strum(serialize = "RSSEXP")]
RssExp,
#[strum(serialize = "UPCA")]
UpcA,
#[strum(serialize = "UPCE")]
UpcE,
#[strum(serialize = "EAN13")]
Ean13,
#[strum(serialize = "EAN8")]
Ean8,
#[strum(serialize = "UCC128CCA")]
Ucc128Cca,
#[strum(serialize = "UCC128CCC")]
Ucc128Ccc,
}
#[derive(Debug, Display)]
pub enum Font {
#[strum(serialize = "0")]
FontMonotye,
#[strum(serialize = "1")]
Font8x12,
#[strum(serialize = "2")]
Font12x20,
#[strum(serialize = "3")]
Font16x24,
#[strum(serialize = "4")]
Font24x32,
#[strum(serialize = "5")]
Font32x48,
#[strum(serialize = "6")]
Font14x19,
#[strum(serialize = "7")]
Font21x27,
#[strum(serialize = "8")]
Font14x25,
#[strum(serialize = "ROMAN.TTF")]
FontRoman,
#[strum(serialize = "1.EFT")]
FontEpl1,
#[strum(serialize = "2.EFT")]
FontEpl2,
#[strum(serialize = "3.RFT")]
FontEpl3,
#[strum(serialize = "4.EFT")]
FontEpl4,
#[strum(serialize = "5.EFT")]
FontEpl5,
#[strum(serialize = "A.FNT")]
FontZplA,
#[strum(serialize = "B.FNT")]
FontZplB,
#[strum(serialize = "D.FNT")]
FontZplD,
#[strum(serialize = "E8.FNT")]
FontZplE8,
#[strum(serialize = "F.FNT")]
FontZplF,
#[strum(serialize = "G.FNT")]
FontZplG,
#[strum(serialize = "H8.FNT")]
FontZplH8,
#[strum(serialize = "GS.FNT")]
FontZplGs,
}
#[derive(Debug, Display)]
pub enum HumanReadable {
#[strum(serialize = "0")]
NotReadable = 0,
#[strum(serialize = "1")]
ReadableAlignsToLeft = 1,
#[strum(serialize = "2")]
ReadableAlignsToCenter = 2,
#[strum(serialize = "3")]
ReadableAlignsToRight = 3,
}
#[derive(Debug, Display)]
pub enum Rotation {
#[strum(serialize = "0")]
NoRotation = 0,
#[strum(serialize = "90")]
Rotation90 = 90,
#[strum(serialize = "180")]
Rotation180 = 180,
#[strum(serialize = "270")]
Rotation270 = 270,
}
#[derive(Debug, Display)]
pub enum Alignment {
#[strum(serialize = "0")]
Default = 0,
#[strum(serialize = "1")]
Left = 1,
#[strum(serialize = "2")]
Center = 2,
#[strum(serialize = "3")]
Right = 3,
}
#[derive(Debug, Display)]
pub enum NarrowWide {
#[strum(serialize = "1,1")]
N1W1,
#[strum(serialize = "1,2")]
N1W2,
#[strum(serialize = "1,3")]
N1W3,
#[strum(serialize = "2,5")]
N2W5,
#[strum(serialize = "3,7")]
N3W7,
}
#[derive(Debug, Display)]
pub enum BitmapMode {
#[strum(serialize = "0")]
Overwrite = 0,
#[strum(serialize = "1")]
Or = 1,
#[strum(serialize = "2")]
Xor = 2,
}
#[derive(Debug, Display)]
pub enum QrCodeJustification {
#[strum(serialize = "J1")]
UpperLeft,
#[strum(serialize = "J2")]
UpperCenter,
#[strum(serialize = "J3")]
UpperRight,
#[strum(serialize = "J4")]
CenterLeft,
#[strum(serialize = "J5")]
Center,
#[strum(serialize = "J6")]
CenterRight,
#[strum(serialize = "J7")]
BottomLeft,
#[strum(serialize = "J8")]
BottomCenter,
#[strum(serialize = "J9")]
BottomRight,
}
pub struct Printer {
file: std::fs::File,
resolution: u32,
}
impl Printer {
pub fn with_resolution(path: &str, tape: Tape, dpi: u32) -> Result<Self> {
let file = std::fs::File::options().read(true).write(true).open(path)?;
let mut printer = Self {
file,
resolution: dpi,
};
printer
.size(tape.width, tape.height)?
.gap(tape.gap, tape.gap_offset)?
.cls()?;
Ok(printer)
}
fn size(&mut self, width: Size, height: Option<Size>) -> Result<&mut Self> {
let cmd = match height {
Some(height) => format!("SIZE {width},{height}\r\n"),
None => format!("SIZE {width}\r\n"),
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
fn gap(&mut self, gap: Size, gap_offset: Option<Size>) -> Result<&mut Self> {
let cmd = match gap_offset {
Some(offset) => format!("GAP {gap},{offset}\r\n"),
None => format!("GAP {gap}\r\n"),
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn gap_detect(&mut self, calib: Option<(Size, Size)>) -> Result<&mut Self> {
let cmd = match calib {
Some((x, y)) => &format!(
"GAPDETECT {},{}\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution)
),
None => "GAPDETECT\r\n",
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn bline_detect(&mut self, calib: Option<(Size, Size)>) -> Result<&mut Self> {
let cmd = match calib {
Some((x, y)) => &format!(
"BLINEDETECT {},{}\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution)
),
None => "BLINEDETECT\r\n",
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn auto_detect(&mut self, calib: Option<(Size, Size)>) -> Result<&mut Self> {
let cmd = match calib {
Some((x, y)) => &format!(
"AUTODETECT {},{}\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution)
),
None => "AUTODETECT\r\n",
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn bline(&mut self, black_line_height: Size, extra_feeding_len: Size) -> Result<&mut Self> {
let cmd = format!("BLINE {black_line_height},{extra_feeding_len}\r\n");
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn offset(&mut self, offset: Size) -> Result<&mut Self> {
let cmd = format!("OFFSET {offset}\r\n");
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn speed(&mut self, speed: &str) -> Result<&mut Self> {
let cmd = format!("SPEED {speed}\r\n");
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn density(&mut self, density: u8) -> Result<&mut Self> {
let cmd = match density {
1..=15 => format!("DENSITY {density}\r\n"),
_ => return Err(anyhow!("Density should be in range 0..15")),
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn direction(
&mut self,
reversed_direction: bool,
mirrored_image: bool,
) -> Result<&mut Self> {
let cmd = format!(
"DIRECTION {},{}\r\n",
reversed_direction as u8, mirrored_image as u8
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn reference(&mut self, x: Size, y: Size) -> Result<&mut Self> {
let cmd = format!(
"REFERENCE {},{}\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution)
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn shift(&mut self, x: Option<Size>, y: Size) -> Result<&mut Self> {
let cmd = match x {
Some(x) => format!(
"SHIFT {},{}\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution)
),
None => format!("SHIFT {}\r\n", y.to_dots_raw(self.resolution)),
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn country(&mut self, country: Country) -> Result<&mut Self> {
let cmd = format!("COUNTRY {:03}\r\n", country as u16);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn codepage(&mut self, codepage: Codepage) -> Result<&mut Self> {
let cmd = format!("CODEPAGE {codepage}\r\n");
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn cls(&mut self) -> Result<&mut Self> {
let cmd = "CLS\r\n";
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn feed(&mut self, feed: Size) -> Result<&mut Self> {
let feed_dot = feed.to_dots_raw(self.resolution);
let cmd = match feed_dot {
0..=9999 => format!("FEED {feed_dot}\r\n"),
_ => {
return Err(anyhow!(
"feed length must be in range 0..9999 in dots, got {:?}",
feed_dot
))
}
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn backup(&mut self, feed: Size) -> Result<&mut Self> {
let feed_dot = feed.to_dots_raw(self.resolution);
let cmd = match feed_dot {
0..=9999 => format!("BACKUP {feed_dot}\r\n"),
_ => {
return Err(anyhow!(
"backup length must be in range 0..9999, got {:?}",
feed_dot
))
}
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn backfeed(&mut self, feed: Size) -> Result<&mut Self> {
let feed_dot = feed.to_dots_raw(self.resolution);
let cmd = match feed_dot {
0..=9999 => format!("BACKFEED {feed_dot}\r\n"),
_ => {
return Err(anyhow!(
"backfeed length must be in range 0..9999, got {:?}",
feed_dot
))
}
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn formfeed(&mut self) -> Result<&mut Self> {
let cmd = "FORMFEED\r\n";
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn home(&mut self) -> Result<&mut Self> {
let cmd = "HOME\r\n";
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn print(&mut self, sets: u32, copies: Option<u32>) -> Result<&mut Self> {
let cmd = match sets {
1..=999999999 => {
if let Some(copies) = copies {
match copies {
1..=999999999 => format!("PRINT {sets},{copies}\r\n"),
_ => {
return Err(anyhow!(
"Copies qty must be in range 1..999999999, got {:?}",
copies
))
}
}
} else {
format!("PRINT {sets}\r\n")
}
}
_ => {
return Err(anyhow!(
"Sets qty must be in range 1..999999999, got {:?}",
sets
))
}
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn sound(&mut self, level: u8, interval: u16) -> Result<&mut Self> {
let cmd = match (level, interval) {
(0..=9, 1..=4095) => format!("SOUND {level},{interval}\r\n"),
_ => return Err(anyhow!("wrong sound parameters")),
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn cut(&mut self) -> Result<&mut Self> {
let cmd = "CUT\r\n";
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn limit_feed(
&mut self,
n: Size,
minpaper_maxgap: Option<(Size, Size)>,
) -> Result<&mut Self> {
let cmd = match minpaper_maxgap {
Some((x, y)) => format!("LIMITFEED {n},{x},{y}\r\n"),
None => format!("LIMITFEED {n}\r\n"),
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn selftest(&mut self, test_kind: Selftest) -> Result<&mut Self> {
let cmd = format!("SELFTEST {test_kind}\r\n");
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn eoj(&mut self) -> Result<&mut Self> {
let cmd = "EOJ\r\n";
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn delay(&mut self, delay: std::time::Duration) -> Result<&mut Self> {
let cmd = format!("DELAY {}\r\n", delay.as_millis());
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn display(&self) {
unimplemented!()
}
pub fn initial_printer(&mut self) -> Result<&mut Self> {
let cmd = "INITIALPRINTER\r\n";
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn menu(&self) {
unimplemented!()
}
pub fn bar(
&mut self,
x_upper_left: Size,
y_upper_left: Size,
width: Size,
height: Size,
) -> Result<&mut Self> {
let cmd = format!(
"BAR {},{},{},{}\r\n",
x_upper_left.to_dots_raw(self.resolution),
y_upper_left.to_dots_raw(self.resolution),
width.to_dots_raw(self.resolution),
height.to_dots_raw(self.resolution)
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn barcode(
&mut self,
x: Size,
y: Size,
code_type: Barcode,
height: Size,
human_readable: HumanReadable,
rotate: Rotation,
narrow_wide: NarrowWide,
alignment: Option<Alignment>,
content: &str,
) -> Result<&mut Self> {
let cmd = if let Some(alignment) = alignment {
format!(
"BARCODE {},{},\"{}\",{},{},{},{},{}, \"{}\"\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
code_type,
height.to_dots_raw(self.resolution),
human_readable,
rotate,
narrow_wide,
alignment,
content
)
} else {
format!(
"BARCODE {},{},\"{}\",{},{},{},{}, \"{}\"\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
code_type,
height.to_dots_raw(self.resolution),
human_readable,
rotate,
narrow_wide,
content
)
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn tlc39(
&mut self,
x: Size,
y: Size,
rotate: Rotation,
height: Option<Size>,
narrow: Option<Size>,
wide: Option<Size>,
cellwidth: Option<Size>,
cellheight: Option<Size>,
eci_number: &str,
serial_number: &str,
additional_data: &str,
) -> Result<&mut Self> {
let x = x.to_dots_raw(self.resolution);
let y = y.to_dots_raw(self.resolution);
let height = height
.unwrap_or(Size::Dots(40))
.to_dots_raw(self.resolution);
let narrow = narrow.unwrap_or(Size::Dots(2)).to_dots_raw(self.resolution);
let wide = wide.unwrap_or(Size::Dots(4)).to_dots_raw(self.resolution);
let cellwidth = cellwidth
.unwrap_or(Size::Dots(2))
.to_dots_raw(self.resolution);
let cellheight = cellheight
.unwrap_or(Size::Dots(4))
.to_dots_raw(self.resolution);
let cmd = format!(
"TLC39 {},{},{},{},{},{},{},{}, \"{},{},{}\"\r\n",
x,
y,
rotate,
height,
narrow,
wide,
cellwidth,
cellheight,
eci_number,
serial_number,
additional_data
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn bitmap(
&mut self,
x: Size,
y: Size,
width_bytes: u16,
height_dots: u16,
mode: BitmapMode,
bitmap_data: Vec<u8>,
) -> Result<&mut Self> {
let crlf = vec![b'\r', b'\n'];
let mut cmd = format!(
"BITMAP {},{},{},{},{},",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
width_bytes,
height_dots,
mode
)
.as_bytes()
.to_vec();
cmd.extend(bitmap_data);
cmd.extend(crlf);
self.file.write_all(&cmd)?;
Ok(self)
}
pub fn rectangle(
&mut self,
x_start: Size,
y_start: Size,
x_end: Size,
y_end: Size,
thickness: Size,
radius: Option<Size>,
) -> Result<&mut Self> {
let cmd = format!(
"BOX {},{},{},{},{},{}\r\n",
x_start.to_dots_raw(self.resolution),
y_start.to_dots_raw(self.resolution),
x_end.to_dots_raw(self.resolution),
y_end.to_dots_raw(self.resolution),
thickness.to_dots_raw(self.resolution),
radius.unwrap_or(Size::Dots(0)).to_dots_raw(self.resolution)
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn circle(
&mut self,
x_start: Size,
y_start: Size,
diameter: Size,
thickness: Size,
) -> Result<&mut Self> {
let cmd = format!(
"CIRCLE {},{},{},{}\r\n",
x_start.to_dots_raw(self.resolution),
y_start.to_dots_raw(self.resolution),
diameter.to_dots_raw(self.resolution),
thickness.to_dots_raw(self.resolution)
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn ellipse(
&mut self,
x_upper_left: Size,
y_upper_left: Size,
width: Size,
height: Size,
thickness: Size,
) -> Result<&mut Self> {
let cmd = format!(
"ELLIPSE {},{},{},{},{}\r\n",
x_upper_left.to_dots_raw(self.resolution),
y_upper_left.to_dots_raw(self.resolution),
width.to_dots_raw(self.resolution),
height.to_dots_raw(self.resolution),
thickness.to_dots_raw(self.resolution)
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn codablock(
&mut self,
x: Size,
y: Size,
rotate: Rotation,
row_height: Option<Size>,
module_width: Option<Size>,
content: &str,
) -> Result<&mut Self> {
let row_height = row_height
.unwrap_or(Size::Dots(8))
.to_dots_raw(self.resolution);
let module_width = module_width
.unwrap_or(Size::Dots(8))
.to_dots_raw(self.resolution);
let cmd = format!(
"CODABLOCK {},{},{},{},{},\"{}\"\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
rotate,
row_height,
module_width,
content
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn data_matrix(
&mut self,
x: Size,
y: Size,
exp_width: Size,
exp_height: Size,
escape_symbol: Option<char>,
module_size: Option<Size>,
rotate: Option<Rotation>,
rectangular: Option<bool>,
row_size: Option<u8>,
col_size: Option<u8>,
content: &str,
) -> Result<&mut Self> {
let mut cmd = format!(
"DMATRIX {},{},{},{},",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
exp_width.to_dots_raw(self.resolution),
exp_height.to_dots_raw(self.resolution)
);
if let Some(c) = escape_symbol {
let c = c as u8;
if (0..=127).contains(&c) {
cmd.push_str(&format!("c{},", c));
} else {
return Err(anyhow!("Wrong ASCII symbol"));
}
}
if let Some(x) = module_size {
cmd.push_str(&format!("x{},", x.to_dots_raw(self.resolution)));
}
if let Some(r) = rotate {
cmd.push_str(&format!("r{},", r));
}
if let Some(a) = rectangular {
let a = if a { 1 } else { 0 };
cmd.push_str(&format!("a{},", a));
}
if let Some(row) = row_size {
if (10..=144).contains(&row) {
cmd.push_str(&format!("{row},"));
} else {
return Err(anyhow!("Row size doesnt match limit [10;144]"));
}
}
if let Some(col) = col_size {
if (10..=144).contains(&col) {
cmd.push_str(&format!("{col},"));
} else {
return Err(anyhow!("Column size doesnt match limit [10;144]"));
}
}
cmd.push_str(&format!(" \"{}\"\r\n", content));
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn erase(&mut self, x: Size, y: Size, width: Size, height: Size) -> Result<&mut Self> {
let cmd = format!(
"ERASE {},{},{},{}\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
width.to_dots_raw(self.resolution),
height.to_dots_raw(self.resolution)
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn pdf417(
&mut self,
x_start: Size,
y_start: Size,
width: Size,
height: Size,
rotate: Rotation,
content: &str,
) -> Result<&mut Self> {
let cmd = format!(
"PDF417 {},{},{},{},{},\"{}\"\r\n",
x_start.to_dots_raw(self.resolution),
y_start.to_dots_raw(self.resolution),
width.to_dots_raw(self.resolution),
height.to_dots_raw(self.resolution),
rotate,
content
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn aztec(
&mut self,
x_start: Size,
y_start: Size,
rotate: Rotation,
size: u8,
ecp: u16,
flg: bool,
menu: bool,
multi: u8,
reversed: bool,
content: &str,
) -> Result<&mut Self> {
if !(1..=20).contains(&size) {
return Err(anyhow!("Wrong size settings. min: 1, max: 20"));
}
if ecp > 300 {
return Err(anyhow!("Wrong error control parameter. Max: 300"));
}
if !(1..=26).contains(&multi) {
return Err(anyhow!("Wrong number of symbols. min: 1, max: 26"));
}
let cmd = format!(
"AZTEC {},{},{},{},{},{},{},{},{},{},{}\r\n",
x_start.to_dots_raw(self.resolution),
y_start.to_dots_raw(self.resolution),
rotate,
size,
ecp,
flg as u8,
menu as u8,
multi,
reversed as u8,
content.as_bytes().len(),
content
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn mpdf417(
&mut self,
x_start: Size,
y_start: Size,
rotate: Rotation,
module_width: Option<Size>,
module_height: Option<Size>,
col_num: Option<usize>,
content: &str,
) -> Result<&mut Self> {
let col_num = match col_num {
Some(x) => match x {
1..=4 => x,
_ => 0,
},
_ => 0,
};
let module_width = module_width
.unwrap_or(Size::Dots(1))
.to_dots_raw(self.resolution);
let module_height = module_height
.unwrap_or(Size::Dots(10))
.to_dots_raw(self.resolution);
let cmd = format!(
"MPDF417 {},{},{},{},{},{}, \"{}\"\r\n",
x_start.to_dots_raw(self.resolution),
y_start.to_dots_raw(self.resolution),
rotate,
module_width,
module_height,
col_num,
content,
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn qrcode(
&mut self,
x_upper_left: Size,
y_upper_left: Size,
ecc_level: u8,
cellwidth_dot: u8,
rotate: Rotation,
justification: Option<QrCodeJustification>,
content: &str,
) -> Result<&mut Self> {
let ecc_level = match ecc_level {
0..=6 => 'L',
7..=14 => 'M',
15..=24 => 'Q',
_ => 'H',
};
if !(1..=10).contains(&cellwidth_dot) {
return Err(anyhow!("Wrong cellwidth value. min: 1, max: 10"));
}
let cmd = match justification {
Some(justification) => format!(
"QRCODE {},{},{},{},A,{},{},\"{}\"\r\n",
x_upper_left.to_dots_raw(self.resolution),
y_upper_left.to_dots_raw(self.resolution),
ecc_level,
cellwidth_dot,
rotate,
justification,
content
),
None => format!(
"QRCODE {},{},{},{},A,{},\"{}\"\r\n",
x_upper_left.to_dots_raw(self.resolution),
y_upper_left.to_dots_raw(self.resolution),
ecc_level,
cellwidth_dot,
rotate,
content
),
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn rss(
&mut self,
x_upper_left: Size,
y_upper_left: Size,
rss_type: RssType,
rotate: Rotation,
module_width: Size,
separator_height: usize,
seg_width: Option<usize>,
lin_height: Option<usize>,
content: &str,
) -> Result<&mut Self> {
let pix_mult = module_width.to_dots_raw(self.resolution);
if !(1..=10).contains(&pix_mult) {
return Err(anyhow!("Wrong module resolution"));
}
if separator_height != 1 && separator_height != 2 {
return Err(anyhow!("Wrong separator height"));
}
let cmd = match rss_type {
RssType::RssExp => match seg_width {
Some(seg_width) => {
if !(2..=22).contains(&seg_width) {
return Err(anyhow!("Wrong segment width. 2 to 22 accepted"));
}
format!(
"RSS {},{}, \"{}\",{},{},{},{}, \"{}\"\r\n",
x_upper_left.to_dots_raw(self.resolution),
y_upper_left.to_dots_raw(self.resolution),
rss_type,
rotate,
pix_mult,
separator_height,
seg_width,
content
)
}
None => return Err(anyhow!("Missed segment width")),
},
RssType::Ucc128Cca | RssType::Ucc128Ccc => match lin_height {
Some(lin_height) => {
if !(1..=500).contains(&lin_height) {
return Err(anyhow!("Wrong line height. 1 to 500 accepted"));
}
format!(
"RSS {},{}, \"{}\",{},{},{},{}, \"{}\"\r\n",
x_upper_left.to_dots_raw(self.resolution),
y_upper_left.to_dots_raw(self.resolution),
rss_type,
rotate,
pix_mult,
separator_height,
lin_height,
content
)
}
None => return Err(anyhow!("UCC/EAN-128 height missed")),
},
_ => {
format!(
"RSS {},{}, \"{}\",{},{},{}, \"{}\"\r\n",
x_upper_left.to_dots_raw(self.resolution),
y_upper_left.to_dots_raw(self.resolution),
rss_type,
rotate,
pix_mult,
separator_height,
content
)
}
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn reverse(
&mut self,
x_start: Size,
y_start: Size,
width: Size,
height: Size,
) -> Result<&mut Self> {
let cmd = format!(
"REVERSE {},{},{},{}\r\n",
x_start.to_dots_raw(self.resolution),
y_start.to_dots_raw(self.resolution),
width.to_dots_raw(self.resolution),
height.to_dots_raw(self.resolution)
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn diagonal(
&mut self,
x_start: Size,
y_start: Size,
x_end: Size,
y_end: Size,
thickness: Size,
) -> Result<&mut Self> {
let cmd = format!(
"DIAGONAL {},{},{},{},{}\r\n",
x_start.to_dots_raw(self.resolution),
y_start.to_dots_raw(self.resolution),
x_end.to_dots_raw(self.resolution),
y_end.to_dots_raw(self.resolution),
thickness.to_dots_raw(self.resolution)
);
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn text(
&mut self,
x: Size,
y: Size,
font: Font,
rotate: Rotation,
multiply_x: u8,
multiply_y: u8,
alignment: Option<Alignment>,
content: &str,
) -> Result<&mut Self> {
if !(1..=10).contains(&multiply_x) || !(1..=10).contains(&multiply_y) {
return Err(anyhow!("Wrong multiplication. Should be in range 1-10"));
}
let cmd = match alignment {
Some(alignment) => format!(
"TEXT {},{},\"{}\",{},{},{},{}, \"{}\"\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
font,
rotate,
multiply_x,
multiply_y,
alignment,
content
),
None => format!(
"TEXT {},{},\"{}\",{},{},{}, \"{}\"\r\n",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
font,
rotate,
multiply_x,
multiply_y,
content
),
};
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
pub fn block(
&mut self,
x: Size,
y: Size,
width: Size,
height: Size,
font: Font,
rotate: Rotation,
multiply_x: u8,
multiply_y: u8,
space: Option<Size>,
alignment: Option<Alignment>,
fit: Option<bool>,
content: &str,
) -> Result<&mut Self> {
if !(1..=10).contains(&multiply_x) || !(1..=10).contains(&multiply_y) {
return Err(anyhow!("Wrong multiplication. Should be in range 1-10"));
}
if content.len() > 4096 {
return Err(anyhow!("Overflow. Max content length 4096"));
}
let mut cmd = format!(
"TEXT {},{},{},{},\"{}\",{},{},{},",
x.to_dots_raw(self.resolution),
y.to_dots_raw(self.resolution),
width.to_dots_raw(self.resolution),
height.to_dots_raw(self.resolution),
font,
rotate,
multiply_x,
multiply_y,
);
if let Some(space) = space {
cmd.push_str(&format!("{},", space.to_dots_raw(self.resolution)));
}
if let Some(alignment) = alignment {
cmd.push_str(&format!("{},", alignment));
}
if let Some(fit) = fit {
cmd.push_str(&format!("{},", fit as u8));
}
cmd.push_str(&format!("\"{}\"\r\n", content));
debug!("{cmd}");
self.file.write_all(cmd.as_bytes())?;
Ok(self)
}
}