1use std::collections::HashMap;
2use std::sync::Arc;
3
4use crate::{
5 FontManager, ZplError, ZplResult,
6 ast::parse_zpl,
7 engine::{backend, common, font, intr},
8};
9
10#[derive(Debug)]
15pub struct ZplEngine {
16 instructions: Vec<common::ZplInstruction>,
17 width: common::Unit,
18 height: common::Unit,
19 resolution: common::Resolution,
20 fonts: Option<Arc<font::FontManager>>,
21}
22
23impl ZplEngine {
24 pub fn new(
35 zpl: &str,
36 width: common::Unit,
37 height: common::Unit,
38 resolution: common::Resolution,
39 ) -> ZplResult<Self> {
40 let commands = parse_zpl(zpl)?;
41 if commands.is_empty() {
42 return Err(ZplError::EmptyInput);
43 }
44
45 let instructions = intr::ZplInstructionBuilder::new(commands);
46 let instructions = instructions.build()?;
47
48 Ok(Self {
49 instructions,
50 width,
51 height,
52 resolution,
53 fonts: None,
54 })
55 }
56
57 pub fn set_fonts(&mut self, fonts: Arc<font::FontManager>) {
61 self.fonts = Some(fonts);
62 }
63
64 pub fn render<B: backend::ZplForgeBackend>(
73 &self,
74 mut backend: B,
75 variables: &HashMap<String, String>,
76 ) -> ZplResult<Vec<u8>> {
77 fn replace_vars<'a>(
78 s: &'a str,
79 variables: &HashMap<String, String>,
80 ) -> std::borrow::Cow<'a, str> {
81 if variables.is_empty() || !s.contains("{{") {
82 return std::borrow::Cow::Borrowed(s);
83 }
84
85 let mut result = String::new();
86 let mut last_pos = 0;
87 let mut found = false;
88 let mut cursor = 0;
89
90 while let Some(start_offset) = s[cursor..].find("{{") {
91 let start = cursor + start_offset;
92 if let Some(end_offset) = s[start + 2..].find("}}") {
93 let end = start + 2 + end_offset;
94 let key = &s[start + 2..end];
95 if let Some(value) = variables.get(key) {
96 if !found {
97 result.reserve(s.len());
98 found = true;
99 }
100 result.push_str(&s[last_pos..start]);
101 result.push_str(value);
102 last_pos = end + 2;
103 cursor = last_pos;
104 continue;
105 }
106 }
107 cursor = start + 2;
108 }
109
110 if found {
111 result.push_str(&s[last_pos..]);
112 std::borrow::Cow::Owned(result)
113 } else {
114 std::borrow::Cow::Borrowed(s)
115 }
116 }
117
118 let w_dots = self.width.clone().to_dots(self.resolution);
119 let h_dots = self.height.clone().to_dots(self.resolution);
120 let font_manager = if let Some(fonts) = &self.fonts {
121 fonts.clone()
122 } else {
123 Arc::new(FontManager::default())
124 };
125
126 backend.setup_page(w_dots as f64, h_dots as f64, self.resolution.dpi());
127 backend.setup_font_manager(&font_manager);
128
129 for instruction in &self.instructions {
130 let condition = match instruction {
131 common::ZplInstruction::Text { condition, .. } => condition,
132 common::ZplInstruction::GraphicBox { condition, .. } => condition,
133 common::ZplInstruction::GraphicCircle { condition, .. } => condition,
134 common::ZplInstruction::GraphicEllipse { condition, .. } => condition,
135 common::ZplInstruction::GraphicField { condition, .. } => condition,
136 common::ZplInstruction::CustomImage { condition, .. } => condition,
137 common::ZplInstruction::Code128 { condition, .. } => condition,
138 common::ZplInstruction::QRCode { condition, .. } => condition,
139 common::ZplInstruction::Code39 { condition, .. } => condition,
140 };
141
142 if let Some((var, expected)) = condition
143 && variables.get(var) != Some(expected)
144 {
145 continue;
146 }
147
148 match instruction {
149 common::ZplInstruction::Text {
150 condition: _,
151 x,
152 y,
153 font,
154 height,
155 width,
156 text,
157 reverse_print,
158 color,
159 } => {
160 backend.draw_text(
161 *x,
162 *y,
163 *font,
164 *height,
165 *width,
166 &replace_vars(text, variables),
167 *reverse_print,
168 color.clone(),
169 )?;
170 }
171 common::ZplInstruction::GraphicBox {
172 condition: _,
173 x,
174 y,
175 width,
176 height,
177 thickness,
178 color,
179 custom_color,
180 rounding,
181 reverse_print,
182 } => {
183 backend.draw_graphic_box(
184 *x,
185 *y,
186 *width,
187 *height,
188 *thickness,
189 *color,
190 custom_color.clone(),
191 *rounding,
192 *reverse_print,
193 )?;
194 }
195 common::ZplInstruction::GraphicCircle {
196 condition: _,
197 x,
198 y,
199 radius,
200 thickness,
201 color,
202 custom_color,
203 reverse_print,
204 } => {
205 backend.draw_graphic_circle(
206 *x,
207 *y,
208 *radius,
209 *thickness,
210 *color,
211 custom_color.clone(),
212 *reverse_print,
213 )?;
214 }
215 common::ZplInstruction::GraphicEllipse {
216 condition: _,
217 x,
218 y,
219 width,
220 height,
221 thickness,
222 color,
223 custom_color,
224 reverse_print,
225 } => {
226 backend.draw_graphic_ellipse(
227 *x,
228 *y,
229 *width,
230 *height,
231 *thickness,
232 *color,
233 custom_color.clone(),
234 *reverse_print,
235 )?;
236 }
237 common::ZplInstruction::GraphicField {
238 condition: _,
239 x,
240 y,
241 width,
242 height,
243 data,
244 reverse_print,
245 } => {
246 backend.draw_graphic_field(*x, *y, *width, *height, data, *reverse_print)?;
247 }
248 common::ZplInstruction::Code128 {
249 condition: _,
250 x,
251 y,
252 orientation,
253 height,
254 module_width,
255 interpretation_line,
256 interpretation_line_above,
257 check_digit,
258 mode,
259 data,
260 reverse_print,
261 } => {
262 backend.draw_code128(
263 *x,
264 *y,
265 *orientation,
266 *height,
267 *module_width,
268 *interpretation_line,
269 *interpretation_line_above,
270 *check_digit,
271 *mode,
272 &replace_vars(data, variables),
273 *reverse_print,
274 )?;
275 }
276 common::ZplInstruction::QRCode {
277 condition: _,
278 x,
279 y,
280 orientation,
281 model,
282 magnification,
283 error_correction,
284 mask,
285 data,
286 reverse_print,
287 } => {
288 backend.draw_qr_code(
289 *x,
290 *y,
291 *orientation,
292 *model,
293 *magnification,
294 *error_correction,
295 *mask,
296 &replace_vars(data, variables),
297 *reverse_print,
298 )?;
299 }
300 common::ZplInstruction::Code39 {
301 condition: _,
302 x,
303 y,
304 orientation,
305 check_digit,
306 height,
307 module_width,
308 interpretation_line,
309 interpretation_line_above,
310 data,
311 reverse_print,
312 } => {
313 backend.draw_code39(
314 *x,
315 *y,
316 *orientation,
317 *check_digit,
318 *height,
319 *module_width,
320 *interpretation_line,
321 *interpretation_line_above,
322 &replace_vars(data, variables),
323 *reverse_print,
324 )?;
325 }
326 common::ZplInstruction::CustomImage {
327 condition: _,
328 x,
329 y,
330 width,
331 height,
332 data,
333 } => {
334 backend.draw_graphic_image_custom(*x, *y, *width, *height, data)?;
335 }
336 }
337 }
338
339 let result = backend.finalize()?;
340
341 Ok(result)
342 }
343}