zpl_forge/forge/
pdf.rs

1use crate::engine::{FontManager, ZplForgeBackend};
2use crate::forge::png::PngBackend;
3use crate::{ZplError, ZplResult};
4use printpdf::*;
5
6/// A rendering backend that produces PDF documents.
7///
8/// This backend acts as a wrapper around [`PngBackend`]. It renders the ZPL
9/// commands into a high-resolution PNG image first, then embeds that image
10/// into a PDF document of the corresponding physical size.
11pub struct PdfBackend {
12    png_backend: PngBackend,
13    width_dots: f64,
14    height_dots: f64,
15    resolution: f32,
16}
17
18impl Default for PdfBackend {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl PdfBackend {
25    /// Creates a new `PdfBackend` instance.
26    pub fn new() -> Self {
27        Self {
28            png_backend: PngBackend::new(),
29            width_dots: 0.0,
30            height_dots: 0.0,
31            resolution: 0.0,
32        }
33    }
34}
35
36impl ZplForgeBackend for PdfBackend {
37    fn setup_page(&mut self, width: f64, height: f64, resolution: f32) {
38        self.width_dots = width;
39        self.height_dots = height;
40        self.resolution = resolution;
41        self.png_backend.setup_page(width, height, resolution);
42    }
43
44    fn setup_font_manager(&mut self, font_manager: &FontManager) {
45        self.png_backend.setup_font_manager(font_manager);
46    }
47
48    fn draw_text(
49        &mut self,
50        x: u32,
51        y: u32,
52        font: char,
53        height: Option<u32>,
54        width: Option<u32>,
55        text: String,
56        reverse_print: bool,
57        color: Option<String>,
58    ) -> ZplResult<()> {
59        self.png_backend
60            .draw_text(x, y, font, height, width, text, reverse_print, color)
61    }
62
63    fn draw_graphic_box(
64        &mut self,
65        x: u32,
66        y: u32,
67        width: u32,
68        height: u32,
69        thickness: u32,
70        color: char,
71        custom_color: Option<String>,
72        rounding: u32,
73        reverse_print: bool,
74    ) -> ZplResult<()> {
75        self.png_backend.draw_graphic_box(
76            x,
77            y,
78            width,
79            height,
80            thickness,
81            color,
82            custom_color,
83            rounding,
84            reverse_print,
85        )
86    }
87
88    fn draw_graphic_circle(
89        &mut self,
90        x: u32,
91        y: u32,
92        radius: u32,
93        thickness: u32,
94        color: char,
95        custom_color: Option<String>,
96        reverse_print: bool,
97    ) -> ZplResult<()> {
98        self.png_backend.draw_graphic_circle(
99            x,
100            y,
101            radius,
102            thickness,
103            color,
104            custom_color,
105            reverse_print,
106        )
107    }
108
109    fn draw_graphic_ellipse(
110        &mut self,
111        x: u32,
112        y: u32,
113        width: u32,
114        height: u32,
115        thickness: u32,
116        color: char,
117        custom_color: Option<String>,
118        reverse_print: bool,
119    ) -> ZplResult<()> {
120        self.png_backend.draw_graphic_ellipse(
121            x,
122            y,
123            width,
124            height,
125            thickness,
126            color,
127            custom_color,
128            reverse_print,
129        )
130    }
131
132    fn draw_graphic_field(
133        &mut self,
134        x: u32,
135        y: u32,
136        width: u32,
137        height: u32,
138        data: Vec<u8>,
139        reverse_print: bool,
140    ) -> ZplResult<()> {
141        self.png_backend
142            .draw_graphic_field(x, y, width, height, data, reverse_print)
143    }
144
145    fn draw_graphic_image_custom(
146        &mut self,
147        x: u32,
148        y: u32,
149        width: u32,
150        height: u32,
151        data: String,
152    ) -> ZplResult<()> {
153        self.png_backend
154            .draw_graphic_image_custom(x, y, width, height, data)
155    }
156
157    fn draw_code128(
158        &mut self,
159        x: u32,
160        y: u32,
161        orientation: char,
162        height: u32,
163        module_width: u32,
164        interpretation_line: char,
165        interpretation_line_above: char,
166        check_digit: char,
167        mode: char,
168        data: String,
169        reverse_print: bool,
170    ) -> ZplResult<()> {
171        self.png_backend.draw_code128(
172            x,
173            y,
174            orientation,
175            height,
176            module_width,
177            interpretation_line,
178            interpretation_line_above,
179            check_digit,
180            mode,
181            data,
182            reverse_print,
183        )
184    }
185
186    fn draw_qr_code(
187        &mut self,
188        x: u32,
189        y: u32,
190        orientation: char,
191        model: u32,
192        magnification: u32,
193        error_correction: char,
194        mask: u32,
195        data: String,
196        reverse_print: bool,
197    ) -> ZplResult<()> {
198        self.png_backend.draw_qr_code(
199            x,
200            y,
201            orientation,
202            model,
203            magnification,
204            error_correction,
205            mask,
206            data,
207            reverse_print,
208        )
209    }
210
211    fn draw_code39(
212        &mut self,
213        x: u32,
214        y: u32,
215        orientation: char,
216        check_digit: char,
217        height: u32,
218        module_width: u32,
219        interpretation_line: char,
220        interpretation_line_above: char,
221        data: String,
222        reverse_print: bool,
223    ) -> ZplResult<()> {
224        self.png_backend.draw_code39(
225            x,
226            y,
227            orientation,
228            check_digit,
229            height,
230            module_width,
231            interpretation_line,
232            interpretation_line_above,
233            data,
234            reverse_print,
235        )
236    }
237
238    fn finalize(&mut self) -> ZplResult<Vec<u8>> {
239        let png_data = self.png_backend.finalize()?;
240
241        let dpi = if self.resolution == 0.0 {
242            203.2
243        } else {
244            self.resolution as f64
245        };
246        let width_pt = (self.width_dots / dpi) * 72.0;
247        let height_pt = (self.height_dots / dpi) * 72.0;
248
249        let mut doc = PdfDocument::new("Label");
250
251        // printpdf 0.8 requires collecting warnings manually
252        let mut warnings = Vec::new();
253        let image = RawImage::decode_from_bytes(&png_data, &mut warnings)
254            .map_err(|e| ZplError::BackendError(format!("Failed to decode image: {}", e)))?;
255
256        let image_id = doc.add_image(&image);
257
258        let transform = XObjectTransform {
259            translate_x: Some(Pt(0.0)),
260            translate_y: Some(Pt(0.0)),
261            rotate: None,
262            scale_x: None,
263            scale_y: None,
264            dpi: Some(dpi as f32),
265        };
266
267        let op = Op::UseXobject {
268            id: image_id,
269            transform,
270        };
271
272        let page = PdfPage::new(
273            Mm::from(Pt(width_pt as f32)),
274            Mm::from(Pt(height_pt as f32)),
275            vec![op],
276        );
277
278        doc.pages.push(page);
279
280        let save_options = PdfSaveOptions::default();
281        let pdf_bytes = doc.save(&save_options, &mut warnings);
282
283        Ok(pdf_bytes)
284    }
285}