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}