1use crate::command::{
2 CharMagnification, Charset, CodeTable, Command, Font, Justification, UnderlineThickness,
3};
4use crate::config::PrinterConfig;
5use crate::error::{Error, Result};
6use crate::instruction::EscposImage;
7use crate::split_words::split_words;
8use codepage_437::{IntoCp437, CP437_CONTROL};
9use std::io;
10
11pub trait PrinterDevice {
12 fn write_all(&mut self, buf: &[u8]) -> io::Result<()>;
13}
14
15impl<T> PrinterDevice for T
16where
17 T: io::Write,
18{
19 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
20 self.write_all(buf)
21 }
22}
23
24#[derive(Clone, Debug)]
25pub struct PrinterState {
26 pub(crate) char_spacing: u8,
27 pub(crate) line_spacing: Option<u8>,
28 pub(crate) font: Font,
29 pub(crate) left_offset: usize,
30 pub(crate) split_words: bool,
31 pub(crate) left_margin: u16,
32 pub(crate) justification: Justification,
33 pub(crate) char_magnification: CharMagnification,
34}
35
36#[derive(Clone, Debug)]
37pub struct Printer<D> {
38 pub(crate) device: D,
39 pub(crate) config: PrinterConfig,
40 pub(crate) state: PrinterState,
41}
42
43impl<D> Printer<D> {
44 pub fn builder() -> PrinterConfig {
45 PrinterConfig::default()
46 }
47
48 fn reduce_spacing_param(spacing: usize) -> Result<u8> {
49 if spacing > u8::MAX as usize {
50 Err(Error::InvalidSpacingParam)
51 } else {
52 Ok(spacing as u8)
53 }
54 }
55
56 pub fn new(device: D, config: PrinterConfig) -> Result<Self> {
57 let state = PrinterState {
58 char_spacing: Self::reduce_spacing_param(config.char_spacing)?,
59 line_spacing: None,
60 font: Font::default(),
61 left_offset: 0,
62 split_words: true,
63 left_margin: 0,
64 justification: Justification::default(),
65 char_magnification: CharMagnification::default(),
66 };
67 Ok(Printer {
68 device,
69 config,
70 state,
71 })
72 }
73
74 pub(crate) fn calc_char_size(&self) -> usize {
75 (self.config.font_widths.get(&self.state.font) + self.state.char_spacing as usize)
76 * self.state.char_magnification.width() as usize
77 }
78
79 pub(crate) fn printable_width(&self) -> usize {
80 self.config.width - (self.state.left_margin as usize).min(self.config.width)
81 }
82}
83
84impl PrinterConfig {
85 pub fn build<D>(&self, device: D) -> Result<Printer<D>> {
86 Printer::new(device, self.clone())
87 }
88}
89
90macro_rules! cmd_fn {
91 ($name:ident, $cmd:ident) => {
92 #[inline]
93 pub fn $name(&mut self) -> Result<&mut Self> {
94 self.command(&Command::$cmd)
95 }
96 };
97 ($name:ident, $cmd:ident, $param:ident, $param_ty:ty) => {
98 #[inline]
99 pub fn $name(&mut self, $param: $param_ty) -> Result<&mut Self> {
100 self.command(&Command::$cmd($param))
101 }
102 };
103}
104
105impl<D> Printer<D>
106where
107 D: PrinterDevice,
108{
109 cmd_fn!(cut, Cut);
110 cmd_fn!(init, Init);
111 cmd_fn!(print_mode_default, PrintModeDefault);
112 cmd_fn!(charset, Charset, charset, Charset);
113 cmd_fn!(code_table, CodeTable, code_table, CodeTable);
114 cmd_fn!(font, Font, font, Font);
115 cmd_fn!(underline, Underline, thickness, UnderlineThickness);
116 cmd_fn!(bold, Bold, enabled, bool);
117 cmd_fn!(double_strike, DoubleStrike, enabled, bool);
118 cmd_fn!(white_black_reverse, WhiteBlackReverse, enabled, bool);
119 cmd_fn!(char_size, CharSize, magnification, CharMagnification);
120 cmd_fn!(split_words, SplitWords, enabled, bool);
121 cmd_fn!(left_margin, LeftMargin, margin, u16);
122 cmd_fn!(justification, Justification, justification, Justification);
123
124 pub fn reset(&mut self) -> Result<&mut Self> {
125 self.state.split_words = true;
126 let og_char_spacing = self.config.char_spacing;
127 self.init()?
128 .print_mode_default()?
129 .white_black_reverse(false)?
130 .double_strike(false)?
131 .char_spacing(og_char_spacing)?
132 .line_spacing(None)?
133 .left_margin(0)?
134 .justification(Justification::default())
135 }
136
137 pub fn print(&mut self, text: impl ToString) -> Result<&mut Self> {
138 let mut content = text.to_string().into_cp437(&CP437_CONTROL)?;
139
140 let new_offset = if self.state.split_words {
141 split_words(
142 &mut content,
143 self.state.left_offset,
144 self.printable_width(),
145 self.calc_char_size(),
146 )
147 } else {
148 (self.state.left_offset + content.len() * self.calc_char_size())
149 % self.printable_width()
150 };
151
152 unsafe {
153 self.raw(content)?;
154 }
155 self.state.left_offset = new_offset;
156 Ok(self)
157 }
158
159 pub fn println(&mut self, text: impl ToString) -> Result<&mut Self> {
160 self.print(text.to_string() + "\n")
161 }
162
163 pub fn feed_lines(&mut self, lines: usize) -> Result<&mut Self> {
164 for _ in 0..lines / u8::MAX as usize {
165 self.command(&Command::FeedLines(u8::MAX))?;
166 }
167 self.command(&Command::FeedLines(lines as u8))
168 }
169
170 pub fn feed_paper(&mut self, units: usize) -> Result<&mut Self> {
171 for _ in 0..units / u8::MAX as usize {
172 self.command(&Command::FeedPaper(u8::MAX))?;
173 }
174 self.command(&Command::FeedPaper(units as u8))
175 }
176
177 pub fn char_spacing(&mut self, char_spacing: usize) -> Result<&mut Self> {
178 self.command(&Command::CharSpacing(Self::reduce_spacing_param(
179 char_spacing,
180 )?))
181 }
182
183 pub fn line_spacing(&mut self, line_spacing: Option<usize>) -> Result<&mut Self> {
184 let cmd = if let Some(line_spacing) = line_spacing {
185 Command::LineSpacing(Self::reduce_spacing_param(line_spacing)?)
186 } else {
187 Command::DefaultLineSpacing
188 };
189 self.command(&cmd)
190 }
191
192 pub fn command(&mut self, cmd: &Command) -> Result<&mut Self> {
193 unsafe {
194 self.raw(&cmd.as_bytes())?;
195 }
196 match cmd {
197 Command::LineSpacing(units) => self.state.line_spacing = Some(*units),
198 Command::DefaultLineSpacing => self.state.line_spacing = None,
199 Command::CharSpacing(units) => self.state.char_spacing = *units,
200 Command::CharSize(magnification) => self.state.char_magnification = *magnification,
201 Command::Font(font) => self.state.font = *font,
202 Command::SplitWords(split) => self.state.split_words = *split,
203 Command::LeftMargin(margin) => self.state.left_margin = *margin,
204 Command::Justification(justification) => self.state.justification = *justification,
205 Command::FeedPaper(_) | Command::FeedLines(_) => {
206 self.state.left_offset = 0;
207 }
208 Command::Init => {
209 self.state.char_magnification = CharMagnification::default();
210 self.state.font = Font::default();
211 }
212 _ => {} }
214 Ok(self)
215 }
216
217 pub fn image(&mut self, image: &EscposImage) -> Result<&mut Self> {
218 unsafe {
219 self.raw(image.as_bytes(
220 self.printable_width(),
221 self.state.justification,
222 self.state.line_spacing,
223 ))?;
224 }
225 self.state.left_offset = 0;
226 Ok(self)
227 }
228
229 pub unsafe fn raw(&mut self, data: impl AsRef<[u8]>) -> Result<&mut Self> {
230 self.device.write_all(data.as_ref())?;
231 Ok(self)
232 }
233}