zpl_forge/engine/engine.rs
1use std::collections::HashMap;
2use std::sync::Arc;
3
4use crate::{
5 ast::parse_zpl,
6 engine::{backend, common, font, intr},
7 FontManager, ZplError, ZplResult,
8};
9
10/// The main entry point for processing and rendering ZPL labels.
11///
12/// `ZplEngine` holds the parsed instructions, label dimensions, and configuration
13/// required to render a label using a specific backend.
14#[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 /// Creates a new `ZplEngine` instance by parsing a ZPL string.
25 ///
26 /// # Arguments
27 /// * `zpl` - The raw ZPL string to parse.
28 /// * `width` - The physical width of the label.
29 /// * `height` - The physical height of the label.
30 /// * `resolution` - The printing resolution (DPI).
31 ///
32 /// # Errors
33 /// Returns an error if the ZPL is invalid or if the instruction building fails.
34 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 /// Sets the font manager to be used during rendering.
58 ///
59 /// If no font manager is provided, a default one will be used.
60 pub fn set_fonts(&mut self, fonts: Arc<font::FontManager>) {
61 self.fonts = Some(fonts);
62 }
63
64 /// Renders the parsed instructions using the provided backend.
65 ///
66 /// # Arguments
67 /// * `backend` - An implementation of `ZplForgeBackend` (e.g., PNG, PDF).
68 /// * `variables` - A map of template variables to replace in text fields (format: `{{key}}`).
69 ///
70 /// # Errors
71 /// Returns an error if rendering fails at the backend level.
72 pub fn render<B: backend::ZplForgeBackend>(
73 &self,
74 mut backend: B,
75 variables: &HashMap<String, String>,
76 ) -> ZplResult<Vec<u8>> {
77 let replace_vars = |s: &str| -> String {
78 let mut result = s.to_string();
79 for (k, v) in variables {
80 result = result.replace(&format!("{{{{{}}}}}", k), v);
81 }
82 result
83 };
84
85 let w_dots = self.width.clone().to_dots(self.resolution);
86 let h_dots = self.height.clone().to_dots(self.resolution);
87 let font_manager = if let Some(fonts) = &self.fonts {
88 fonts.clone()
89 } else {
90 Arc::new(FontManager::default())
91 };
92
93 backend.setup_page(w_dots as f64, h_dots as f64, self.resolution.dpi());
94 backend.setup_font_manager(&font_manager);
95
96 for instruction in &self.instructions {
97 match instruction {
98 common::ZplInstruction::Text {
99 x,
100 y,
101 font,
102 height,
103 width,
104 text,
105 reverse_print,
106 color,
107 } => {
108 backend.draw_text(
109 *x,
110 *y,
111 *font,
112 *height,
113 *width,
114 replace_vars(text),
115 *reverse_print,
116 color.clone(),
117 )?;
118 }
119 common::ZplInstruction::GraphicBox {
120 x,
121 y,
122 width,
123 height,
124 thickness,
125 color,
126 custom_color,
127 rounding,
128 reverse_print,
129 } => {
130 backend.draw_graphic_box(
131 *x,
132 *y,
133 *width,
134 *height,
135 *thickness,
136 *color,
137 custom_color.clone(),
138 *rounding,
139 *reverse_print,
140 )?;
141 }
142 common::ZplInstruction::GraphicCircle {
143 x,
144 y,
145 radius,
146 thickness,
147 color,
148 custom_color,
149 reverse_print,
150 } => {
151 backend.draw_graphic_circle(
152 *x,
153 *y,
154 *radius,
155 *thickness,
156 *color,
157 custom_color.clone(),
158 *reverse_print,
159 )?;
160 }
161 common::ZplInstruction::GraphicEllipse {
162 x,
163 y,
164 width,
165 height,
166 thickness,
167 color,
168 custom_color,
169 reverse_print,
170 } => {
171 backend.draw_graphic_ellipse(
172 *x,
173 *y,
174 *width,
175 *height,
176 *thickness,
177 *color,
178 custom_color.clone(),
179 *reverse_print,
180 )?;
181 }
182 common::ZplInstruction::GraphicField {
183 x,
184 y,
185 width,
186 height,
187 data,
188 reverse_print,
189 } => {
190 backend.draw_graphic_field(
191 *x,
192 *y,
193 *width,
194 *height,
195 data.clone(),
196 *reverse_print,
197 )?;
198 }
199 common::ZplInstruction::Code128 {
200 x,
201 y,
202 orientation,
203 height,
204 module_width,
205 interpretation_line,
206 interpretation_line_above,
207 check_digit,
208 mode,
209 data,
210 reverse_print,
211 } => {
212 backend.draw_code128(
213 *x,
214 *y,
215 *orientation,
216 *height,
217 *module_width,
218 *interpretation_line,
219 *interpretation_line_above,
220 *check_digit,
221 *mode,
222 replace_vars(data),
223 *reverse_print,
224 )?;
225 }
226 common::ZplInstruction::QRCode {
227 x,
228 y,
229 orientation,
230 model,
231 magnification,
232 error_correction,
233 mask,
234 data,
235 reverse_print,
236 } => {
237 backend.draw_qr_code(
238 *x,
239 *y,
240 *orientation,
241 *model,
242 *magnification,
243 *error_correction,
244 *mask,
245 replace_vars(data),
246 *reverse_print,
247 )?;
248 }
249 common::ZplInstruction::Code39 {
250 x,
251 y,
252 orientation,
253 check_digit,
254 height,
255 module_width,
256 interpretation_line,
257 interpretation_line_above,
258 data,
259 reverse_print,
260 } => {
261 backend.draw_code39(
262 *x,
263 *y,
264 *orientation,
265 *check_digit,
266 *height,
267 *module_width,
268 *interpretation_line,
269 *interpretation_line_above,
270 replace_vars(data),
271 *reverse_print,
272 )?;
273 }
274 common::ZplInstruction::CustomImage {
275 x,
276 y,
277 width,
278 height,
279 data,
280 } => {
281 backend.draw_graphic_image_custom(*x, *y, *width, *height, data.clone())?;
282 }
283 }
284 }
285
286 let result = backend.finalize()?;
287
288 Ok(result)
289 }
290}