Skip to main content

zpl_forge/forge/
png.rs

1//! PNG rendering backend for ZPL label output.
2//!
3//! This module provides [`PngBackend`], which rasterizes ZPL commands into
4//! RGB PNG images using the `image` and `imageproc` crates.
5
6use std::cmp::max;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use ab_glyph::{Font, PxScale, ScaleFont};
11use base64::{Engine as _, engine::general_purpose};
12use image::{
13    ImageBuffer, Rgb, RgbImage, Rgba, RgbaImage,
14    imageops::{overlay, rotate90, rotate180, rotate270},
15};
16use imageproc::drawing::{
17    draw_filled_circle_mut, draw_filled_ellipse_mut, draw_filled_rect_mut, draw_polygon_mut,
18    draw_text_mut,
19};
20use imageproc::point::Point;
21use imageproc::rect::Rect;
22use rxing::common::BitMatrix;
23use rxing::{BarcodeFormat, EncodeHintType, EncodeHintValue, EncodeHints};
24
25use super::{barcode_1d_format, barcode_cache};
26use crate::engine::{Barcode1DKind, FontManager, ZplForgeBackend};
27use crate::{ZplError, ZplResult};
28
29/// A rendering backend that produces PNG images.
30///
31/// This backend uses the `image` and `imageproc` crates to draw ZPL instructions
32/// onto an RGB canvas.
33pub struct PngBackend {
34    canvas: RgbImage,
35    font_manager: Option<Arc<FontManager>>,
36}
37
38impl Default for PngBackend {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl PngBackend {
45    /// Creates a new `PngBackend` instance with an empty canvas.
46    pub fn new() -> Self {
47        Self {
48            canvas: ImageBuffer::new(0, 0),
49            font_manager: None,
50        }
51    }
52
53    /// Performs an XOR overlay of a source image onto the canvas at (x, y).
54    fn xor_overlay(&mut self, src: &RgbImage, x: i64, y: i64) {
55        let (sw, sh) = src.dimensions();
56        let (cw, ch) = self.canvas.dimensions();
57
58        for sy in 0..sh {
59            let dy = y + sy as i64;
60            if dy < 0 || dy >= ch as i64 {
61                continue;
62            }
63
64            for sx in 0..sw {
65                let dx = x + sx as i64;
66                if dx < 0 || dx >= cw as i64 {
67                    continue;
68                }
69
70                let src_pixel = src[(sx, sy)];
71                if src_pixel.0 != [255, 255, 255] {
72                    let dest_pixel = &mut self.canvas[(dx as u32, dy as u32)];
73                    dest_pixel.0[0] ^= 255;
74                    dest_pixel.0[1] ^= 255;
75                    dest_pixel.0[2] ^= 255;
76                }
77            }
78        }
79    }
80
81    /// Inverts the colors within a specified rectangular area.
82    fn invert_rect(&mut self, rect: Rect) {
83        let (cw, ch) = self.canvas.dimensions();
84        let x_start = rect.left().max(0) as u32;
85        let y_start = rect.top().max(0) as u32;
86        let x_end = (rect.right() as u32).min(cw);
87        let y_end = (rect.bottom() as u32).min(ch);
88
89        for py in y_start..y_end {
90            for px in x_start..x_end {
91                let pixel = &mut self.canvas[(px, py)];
92                pixel.0[0] ^= 255;
93                pixel.0[1] ^= 255;
94                pixel.0[2] ^= 255;
95            }
96        }
97    }
98
99    /// Helper to execute a drawing operation.
100    fn draw_wrapper<F>(
101        &mut self,
102        x: u32,
103        y: u32,
104        width: u32,
105        height: u32,
106        reverse_print: bool,
107        draw_op: F,
108    ) -> ZplResult<()>
109    where
110        F: FnOnce(&mut RgbImage, i32, i32),
111    {
112        if reverse_print {
113            let mut temp_buf = ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255]));
114            draw_op(&mut temp_buf, 0, 0);
115            self.xor_overlay(&temp_buf, x as i64, y as i64);
116        } else {
117            draw_op(&mut self.canvas, x as i32, y as i32);
118        }
119        Ok(())
120    }
121
122    fn parse_hex_color(&self, color: &Option<String>) -> Rgb<u8> {
123        if let Some(hex) = color {
124            let hex = hex.trim_start_matches('#');
125            if hex.len() == 6 {
126                if let (Ok(r), Ok(g), Ok(b)) = (
127                    u8::from_str_radix(&hex[0..2], 16),
128                    u8::from_str_radix(&hex[2..4], 16),
129                    u8::from_str_radix(&hex[4..6], 16),
130                ) {
131                    return Rgb([r, g, b]);
132                }
133            } else if hex.len() == 3
134                && let (Ok(r), Ok(g), Ok(b)) = (
135                    u8::from_str_radix(&hex[0..1], 16),
136                    u8::from_str_radix(&hex[1..2], 16),
137                    u8::from_str_radix(&hex[2..3], 16),
138                )
139            {
140                return Rgb([r * 17, g * 17, b * 17]);
141            }
142        }
143        Rgb([0, 0, 0])
144    }
145
146    fn get_text_width(
147        &self,
148        text: &str,
149        font_char: char,
150        height: Option<u32>,
151        width: Option<u32>,
152    ) -> u32 {
153        let font = match self.font_manager.as_ref() {
154            Some(fm) => match fm.get_font(&font_char.to_string()) {
155                Some(f) => f,
156                None => match fm.get_font("0") {
157                    Some(f) => f,
158                    None => return 0,
159                },
160            },
161            None => return 0,
162        };
163
164        let scale_y = height.unwrap_or(9) as f32;
165        let scale_x = width.unwrap_or(scale_y as u32) as f32;
166        let scale = PxScale {
167            x: scale_x,
168            y: scale_y,
169        };
170
171        let scaled_font = font.as_scaled(scale);
172        let mut width = 0.0;
173        let mut last_glyph_id = None;
174
175        for c in text.chars() {
176            let glyph_id = font.glyph_id(c);
177            if let Some(last) = last_glyph_id {
178                width += scaled_font.kern(last, glyph_id);
179            }
180            width += scaled_font.h_advance(glyph_id);
181            last_glyph_id = Some(glyph_id);
182        }
183
184        width.ceil() as u32
185    }
186}
187
188impl ZplForgeBackend for PngBackend {
189    fn setup_page(&mut self, width: f64, height: f64, _resolution: f32) {
190        // Safety limit to avoid OOM: 8192x8192 is enough for most labels
191        const MAX_DIM: u32 = 8192;
192        let w = (width as u32).min(MAX_DIM);
193        let h = (height as u32).min(MAX_DIM);
194        self.canvas = ImageBuffer::from_pixel(w, h, Rgb([255, 255, 255]));
195    }
196
197    fn setup_font_manager(&mut self, font_manager: &FontManager) {
198        self.font_manager = Some(Arc::new(font_manager.clone()));
199    }
200
201    fn draw_text(
202        &mut self,
203        x: u32,
204        y: u32,
205        font: char,
206        height: Option<u32>,
207        width: Option<u32>,
208        orientation: char,
209        text: &str,
210        _reverse_print: bool,
211        color: Option<String>,
212    ) -> ZplResult<()> {
213        if text.is_empty() {
214            return Ok(());
215        }
216
217        let font_data = match self.font_manager.as_ref() {
218            Some(fm) => match fm.get_font(&font.to_string()) {
219                Some(f) => f.clone(),
220                None => match fm.get_font("0") {
221                    Some(f) => f.clone(),
222                    None => return Err(ZplError::FontError(format!("Font not found: {}", font))),
223                },
224            },
225            None => return Err(ZplError::FontError("Font manager not initialized".into())),
226        };
227
228        let scale_y = height.unwrap_or(9) as f32;
229        let scale_x = width.unwrap_or(scale_y as u32) as f32;
230        let scale = PxScale {
231            x: scale_x,
232            y: scale_y,
233        };
234
235        let text_color = self.parse_hex_color(&color);
236
237        if !matches!(orientation, 'R' | 'I' | 'B') {
238            draw_text_mut(
239                &mut self.canvas,
240                text_color,
241                x as i32,
242                y as i32,
243                scale,
244                &font_data,
245                text,
246            );
247            return Ok(());
248        }
249
250        // Rotated text: render on a temporary transparent surface, rotate it, and blit
251        // non-transparent pixels so the background stays transparent.
252        let text_w = self.get_text_width(text, font, height, width).max(1);
253        let font_h = (scale_y as u32).max(1);
254        let mut tmp = RgbaImage::from_pixel(text_w, font_h, Rgba([0, 0, 0, 0]));
255        let text_rgba = Rgba([text_color.0[0], text_color.0[1], text_color.0[2], 255]);
256        draw_text_mut(&mut tmp, text_rgba, 0, 0, scale, &font_data, text);
257
258        let rotated = match orientation {
259            'R' => rotate90(&tmp),
260            'I' => rotate180(&tmp),
261            _ => rotate270(&tmp),
262        };
263
264        let (cw, ch) = self.canvas.dimensions();
265        for (sx, sy, p) in rotated.enumerate_pixels() {
266            if p.0[3] > 0 {
267                let dx = x.saturating_add(sx);
268                let dy = y.saturating_add(sy);
269                if dx < cw && dy < ch {
270                    self.canvas[(dx, dy)] = Rgb([p.0[0], p.0[1], p.0[2]]);
271                }
272            }
273        }
274        Ok(())
275    }
276
277    fn draw_graphic_box(
278        &mut self,
279        x: u32,
280        y: u32,
281        width: u32,
282        height: u32,
283        thickness: u32,
284        color: char,
285        custom_color: Option<String>,
286        rounding: u32,
287        reverse_print: bool,
288    ) -> ZplResult<()> {
289        let w = max(width, 1);
290        let h = max(height, 1);
291        let t = thickness;
292        let r = (rounding as f64 * 8.0) as i32;
293
294        let (draw_color, clear_color) = if let Some(custom) = custom_color {
295            (self.parse_hex_color(&Some(custom)), Rgb([255, 255, 255]))
296        } else if color == 'B' {
297            (Rgb([0, 0, 0]), Rgb([255, 255, 255]))
298        } else {
299            (Rgb([255, 255, 255]), Rgb([0, 0, 0]))
300        };
301
302        let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
303            let draw_rounded_fill =
304                |img: &mut RgbImage, px: i32, py: i32, pw: u32, ph: u32, pr: i32, pc: Rgb<u8>| {
305                    if pw == 0 || ph == 0 {
306                        return;
307                    }
308                    if pr <= 0 {
309                        draw_filled_rect_mut(img, Rect::at(px, py).of_size(pw, ph), pc);
310                    } else {
311                        let pr = pr.max(0).min((pw / 2) as i32).min((ph / 2) as i32);
312                        let inner_w = pw.saturating_sub(2 * pr as u32).max(1);
313                        let inner_h = ph.saturating_sub(2 * pr as u32).max(1);
314                        draw_filled_rect_mut(img, Rect::at(px + pr, py).of_size(inner_w, ph), pc);
315                        draw_filled_rect_mut(img, Rect::at(px, py + pr).of_size(pw, inner_h), pc);
316                        draw_filled_circle_mut(img, (px + pr, py + pr), pr, pc);
317                        draw_filled_circle_mut(img, (px + pw as i32 - pr - 1, py + pr), pr, pc);
318                        draw_filled_circle_mut(img, (px + pr, py + ph as i32 - pr - 1), pr, pc);
319                        draw_filled_circle_mut(
320                            img,
321                            (px + pw as i32 - pr - 1, py + ph as i32 - pr - 1),
322                            pr,
323                            pc,
324                        );
325                    }
326                };
327
328            draw_rounded_fill(img, px, py, w, h, r, draw_color);
329            if t * 2 < w && t * 2 < h {
330                draw_rounded_fill(
331                    img,
332                    px + t as i32,
333                    py + t as i32,
334                    w - t * 2,
335                    h - t * 2,
336                    (r - t as i32).max(0),
337                    clear_color,
338                );
339            }
340        };
341
342        self.draw_wrapper(x, y, w, h, reverse_print, draw_op)
343    }
344
345    fn draw_graphic_circle(
346        &mut self,
347        x: u32,
348        y: u32,
349        radius: u32,
350        thickness: u32,
351        _color: char,
352        custom_color: Option<String>,
353        reverse_print: bool,
354    ) -> ZplResult<()> {
355        let color = self.parse_hex_color(&custom_color);
356        let clear_color = Rgb([255, 255, 255]);
357
358        let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
359            let center_x = px + radius as i32;
360            let center_y = py + radius as i32;
361            draw_filled_circle_mut(img, (center_x, center_y), radius as i32, color);
362
363            if radius > thickness {
364                draw_filled_circle_mut(
365                    img,
366                    (center_x, center_y),
367                    (radius - thickness) as i32,
368                    clear_color,
369                );
370            }
371        };
372
373        self.draw_wrapper(x, y, radius * 2, radius * 2, reverse_print, draw_op)
374    }
375
376    fn draw_graphic_ellipse(
377        &mut self,
378        x: u32,
379        y: u32,
380        width: u32,
381        height: u32,
382        thickness: u32,
383        _color: char,
384        custom_color: Option<String>,
385        reverse_print: bool,
386    ) -> ZplResult<()> {
387        let color = self.parse_hex_color(&custom_color);
388        let clear_color = Rgb([255, 255, 255]);
389
390        let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
391            let rx = (width / 2) as i32;
392            let ry = (height / 2) as i32;
393            let center_x = px + rx;
394            let center_y = py + ry;
395            draw_filled_ellipse_mut(img, (center_x, center_y), rx, ry, color);
396
397            let t = thickness as i32;
398            if rx > t && ry > t {
399                draw_filled_ellipse_mut(img, (center_x, center_y), rx - t, ry - t, clear_color);
400            }
401        };
402
403        self.draw_wrapper(x, y, width, height, reverse_print, draw_op)
404    }
405
406    fn draw_graphic_field(
407        &mut self,
408        x: u32,
409        y: u32,
410        width: u32,
411        height: u32,
412        data: &[u8],
413        reverse_print: bool,
414    ) -> ZplResult<()> {
415        let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
416            let row_bytes = width.div_ceil(8);
417            let (img_w, img_h) = (img.width() as i32, img.height() as i32);
418
419            for (row_idx, row_data) in data.chunks(row_bytes as usize).enumerate() {
420                let dy = py + row_idx as i32;
421                if dy < 0 || dy >= img_h || row_idx as u32 >= height {
422                    continue;
423                }
424
425                for (byte_idx, &byte) in row_data.iter().enumerate() {
426                    if byte == 0 {
427                        continue;
428                    }
429                    let base_x = px + (byte_idx as i32 * 8);
430                    for bit_idx in 0..8 {
431                        let col_idx = byte_idx as u32 * 8 + bit_idx;
432                        if col_idx >= width {
433                            break;
434                        }
435
436                        if (byte & (0x80 >> bit_idx)) != 0 {
437                            let dx = base_x + bit_idx as i32;
438                            if dx >= 0 && dx < img_w {
439                                img[(dx as u32, dy as u32)] = Rgb([0, 0, 0]);
440                            }
441                        }
442                    }
443                }
444            }
445        };
446
447        self.draw_wrapper(x, y, width, height, reverse_print, draw_op)
448    }
449
450    fn draw_graphic_image_custom(
451        &mut self,
452        x: u32,
453        y: u32,
454        width: u32,
455        height: u32,
456        data: &str,
457    ) -> ZplResult<()> {
458        let image_data = general_purpose::STANDARD
459            .decode(data.trim())
460            .map_err(|e| ZplError::ImageError(format!("Failed to decode base64: {}", e)))?;
461
462        let img = image::load_from_memory(&image_data)
463            .map_err(|e| ZplError::ImageError(format!("Failed to load image: {}", e)))?
464            .to_rgb8();
465
466        let (orig_w, orig_h) = img.dimensions();
467        let (target_w, target_h) = match (width, height) {
468            (0, 0) => (orig_w, orig_h),
469            (w, 0) => {
470                let h = (orig_h as f32 * (w as f32 / orig_w as f32)).round() as u32;
471                (w, h)
472            }
473            (0, h) => {
474                let w = (orig_w as f32 * (h as f32 / orig_h as f32)).round() as u32;
475                (w, h)
476            }
477            (w, h) => (w, h),
478        };
479
480        let resized_img = if target_w != orig_w || target_h != orig_h {
481            image::imageops::resize(
482                &img,
483                target_w,
484                target_h,
485                image::imageops::FilterType::Lanczos3,
486            )
487        } else {
488            img
489        };
490
491        overlay(&mut self.canvas, &resized_img, x as i64, y as i64);
492        Ok(())
493    }
494
495    fn draw_code128(
496        &mut self,
497        x: u32,
498        y: u32,
499        orientation: char,
500        height: u32,
501        module_width: u32,
502        interpretation_line: char,
503        interpretation_line_above: char,
504        _check_digit: char,
505        _mode: char,
506        data: &str,
507        reverse_print: bool,
508    ) -> ZplResult<()> {
509        let (clean_data, hint_val) = if let Some(stripped) = data.strip_prefix(">:") {
510            (stripped, Some("B"))
511        } else if let Some(stripped) = data.strip_prefix(">;") {
512            (stripped, Some("C"))
513        } else if let Some(stripped) = data.strip_prefix(">9") {
514            (stripped, Some("A"))
515        } else {
516            (data, Some("B")) // Standard default is Code Set B
517        };
518
519        let hints = hint_val.map(|v| {
520            let mut h = HashMap::new();
521            h.insert(
522                EncodeHintType::FORCE_CODE_SET,
523                EncodeHintValue::ForceCodeSet(v.to_string()),
524            );
525            EncodeHints::from(h)
526        });
527
528        self.draw_1d_barcode(
529            x,
530            y,
531            orientation,
532            height,
533            module_width,
534            clean_data,
535            BarcodeFormat::CODE_128,
536            reverse_print,
537            interpretation_line,
538            interpretation_line_above,
539            hints,
540            hint_val.unwrap_or(""),
541        )
542    }
543
544    fn draw_qr_code(
545        &mut self,
546        x: u32,
547        y: u32,
548        orientation: char,
549        _model: u32,
550        magnification: u32,
551        error_correction: char,
552        _mask: u32,
553        data: &str,
554        reverse_print: bool,
555    ) -> ZplResult<()> {
556        let level = match error_correction {
557            'L' => "L",
558            'M' => "M",
559            'Q' => "Q",
560            'H' => "H",
561            _ => "M",
562        };
563
564        let mut hints = HashMap::new();
565        hints.insert(
566            EncodeHintType::ERROR_CORRECTION,
567            EncodeHintValue::ErrorCorrection(level.to_string()),
568        );
569        hints.insert(
570            EncodeHintType::MARGIN,
571            EncodeHintValue::Margin("0".to_owned()),
572        );
573        let hints: EncodeHints = hints.into();
574
575        let bit_matrix = barcode_cache::encode_cached(
576            BarcodeFormat::QR_CODE,
577            data,
578            &format!("ec:{}", level),
579            Some(&hints),
580        )?;
581
582        let mag = max(magnification, 1);
583        self.fill_matrix_cells(x, y, orientation, mag, mag, &bit_matrix, reverse_print);
584        Ok(())
585    }
586
587    fn draw_datamatrix(
588        &mut self,
589        x: u32,
590        y: u32,
591        orientation: char,
592        module_size: u32,
593        data: &str,
594        reverse_print: bool,
595    ) -> ZplResult<()> {
596        let bit_matrix = barcode_cache::encode_cached(BarcodeFormat::DATA_MATRIX, data, "", None)?;
597
598        let m = max(module_size, 1);
599        self.fill_matrix_cells(x, y, orientation, m, m, &bit_matrix, reverse_print);
600        Ok(())
601    }
602
603    fn draw_pdf417(
604        &mut self,
605        x: u32,
606        y: u32,
607        orientation: char,
608        row_height: u32,
609        module_width: u32,
610        security_level: u32,
611        data: &str,
612        reverse_print: bool,
613    ) -> ZplResult<()> {
614        let mut hints = HashMap::new();
615        hints.insert(
616            EncodeHintType::ERROR_CORRECTION,
617            EncodeHintValue::ErrorCorrection(security_level.min(8).to_string()),
618        );
619        hints.insert(
620            EncodeHintType::MARGIN,
621            EncodeHintValue::Margin("0".to_owned()),
622        );
623        let hints: EncodeHints = hints.into();
624
625        let bit_matrix = barcode_cache::encode_cached(
626            BarcodeFormat::PDF_417,
627            data,
628            &format!("ec:{}", security_level.min(8)),
629            Some(&hints),
630        )?;
631
632        let cw = max(module_width, 1);
633        let ch = max(row_height, 1);
634        self.fill_matrix_cells(x, y, orientation, cw, ch, &bit_matrix, reverse_print);
635        Ok(())
636    }
637
638    fn draw_code39(
639        &mut self,
640        x: u32,
641        y: u32,
642        orientation: char,
643        _check_digit: char,
644        height: u32,
645        module_width: u32,
646        interpretation_line: char,
647        interpretation_line_above: char,
648        data: &str,
649        reverse_print: bool,
650    ) -> ZplResult<()> {
651        self.draw_1d_barcode(
652            x,
653            y,
654            orientation,
655            height,
656            module_width,
657            data,
658            BarcodeFormat::CODE_39,
659            reverse_print,
660            interpretation_line,
661            interpretation_line_above,
662            None,
663            "",
664        )
665    }
666
667    fn draw_barcode_1d(
668        &mut self,
669        kind: Barcode1DKind,
670        x: u32,
671        y: u32,
672        orientation: char,
673        height: u32,
674        module_width: u32,
675        interpretation_line: char,
676        interpretation_line_above: char,
677        data: &str,
678        reverse_print: bool,
679    ) -> ZplResult<()> {
680        self.draw_1d_barcode(
681            x,
682            y,
683            orientation,
684            height,
685            module_width,
686            data,
687            barcode_1d_format(kind),
688            reverse_print,
689            interpretation_line,
690            interpretation_line_above,
691            None,
692            "",
693        )
694    }
695
696    fn draw_graphic_diagonal(
697        &mut self,
698        x: u32,
699        y: u32,
700        width: u32,
701        height: u32,
702        thickness: u32,
703        color: char,
704        custom_color: Option<String>,
705        diagonal_orientation: char,
706        reverse_print: bool,
707    ) -> ZplResult<()> {
708        let draw_color = if custom_color.is_some() {
709            self.parse_hex_color(&custom_color)
710        } else if color == 'W' {
711            Rgb([255, 255, 255])
712        } else {
713            Rgb([0, 0, 0])
714        };
715
716        let w = max(width, 1) as i32;
717        let h = max(height, 1) as i32;
718        let t = (max(thickness, 1) as i32).min(w);
719
720        let draw_op = move |img: &mut RgbImage, px: i32, py: i32| {
721            // Filled parallelogram with horizontal thickness `t`.
722            let pts = if diagonal_orientation == 'L' {
723                // '\' top-left → bottom-right
724                [
725                    Point::new(px, py),
726                    Point::new(px + t, py),
727                    Point::new(px + w, py + h),
728                    Point::new(px + w - t, py + h),
729                ]
730            } else {
731                // '/' bottom-left → top-right
732                [
733                    Point::new(px, py + h),
734                    Point::new(px + t, py + h),
735                    Point::new(px + w, py),
736                    Point::new(px + w - t, py),
737                ]
738            };
739            draw_polygon_mut(img, &pts, draw_color);
740        };
741
742        self.draw_wrapper(x, y, w as u32, h as u32, reverse_print, draw_op)
743    }
744
745    fn finalize(&mut self) -> ZplResult<Vec<u8>> {
746        let mut bytes = Vec::new();
747        let mut cursor = std::io::Cursor::new(&mut bytes);
748        self.canvas
749            .write_to(&mut cursor, image::ImageFormat::Png)
750            .map_err(|e| ZplError::BackendError(format!("Failed to write PNG: {}", e)))?;
751        Ok(bytes)
752    }
753}
754
755impl PngBackend {
756    /// Paints every set cell of a 2-D bit matrix as a filled rectangle,
757    /// scaling each cell to `cell_w` × `cell_h` dots and applying the
758    /// requested orientation.
759    #[allow(clippy::too_many_arguments)]
760    fn fill_matrix_cells(
761        &mut self,
762        x: u32,
763        y: u32,
764        orientation: char,
765        cell_w: u32,
766        cell_h: u32,
767        bit_matrix: &BitMatrix,
768        reverse_print: bool,
769    ) {
770        let bw = bit_matrix.getWidth();
771        let bh = bit_matrix.getHeight();
772        let full_w = bw * cell_w;
773        let full_h = bh * cell_h;
774
775        for gy in 0..bh {
776            for gx in 0..bw {
777                if !bit_matrix.get(gx, gy) {
778                    continue;
779                }
780                let lx = (gx * cell_w) as i32;
781                let ly = (gy * cell_h) as i32;
782                let (w, h) = (cell_w, cell_h);
783                let rect = match orientation {
784                    'R' => {
785                        let nx = full_h as i32 - (ly + h as i32);
786                        Rect::at(x as i32 + nx, y as i32 + lx).of_size(h, w)
787                    }
788                    'I' => {
789                        let nx = full_w as i32 - (lx + w as i32);
790                        let ny = full_h as i32 - (ly + h as i32);
791                        Rect::at(x as i32 + nx, y as i32 + ny).of_size(w, h)
792                    }
793                    'B' => {
794                        let ny = full_w as i32 - (lx + w as i32);
795                        Rect::at(x as i32 + ly, y as i32 + ny).of_size(h, w)
796                    }
797                    _ => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
798                };
799                if reverse_print {
800                    self.invert_rect(rect);
801                } else {
802                    draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
803                }
804            }
805        }
806    }
807
808    #[allow(clippy::too_many_arguments)]
809    fn draw_1d_barcode(
810        &mut self,
811        x: u32,
812        y: u32,
813        orientation: char,
814        height: u32,
815        module_width: u32,
816        data: &str,
817        format: BarcodeFormat,
818        reverse_print: bool,
819        interpretation_line: char,
820        interpretation_line_above: char,
821        hints: Option<EncodeHints>,
822        hints_key: &str,
823    ) -> ZplResult<()> {
824        let bit_matrix = barcode_cache::encode_cached(format, data, hints_key, hints.as_ref())?;
825
826        let mw = max(module_width, 1);
827        let bh = height;
828        let bw = bit_matrix.getWidth() * mw;
829
830        let (full_w, full_h) = match orientation {
831            'N' | 'I' => (bw, bh),
832            'R' | 'B' => (bh, bw),
833            _ => (bw, bh),
834        };
835
836        let transform_rect = |lx: i32, ly: i32, w: u32, h: u32| -> Rect {
837            match orientation {
838                'N' => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
839                'R' => {
840                    let new_x = bh as i32 - (ly + h as i32);
841                    let new_y = lx;
842                    Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
843                }
844                'I' => {
845                    let new_x = bw as i32 - (lx + w as i32);
846                    let new_y = bh as i32 - (ly + h as i32);
847                    Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(w, h)
848                }
849                'B' => {
850                    let new_x = ly;
851                    let new_y = bw as i32 - (lx + w as i32);
852                    Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
853                }
854                _ => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
855            }
856        };
857
858        for gx in 0..bit_matrix.getWidth() {
859            if bit_matrix.get(gx, 0) {
860                let rect = transform_rect((gx * mw) as i32, 0, mw, bh);
861                if reverse_print {
862                    self.invert_rect(rect);
863                } else {
864                    draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
865                }
866            }
867        }
868
869        if interpretation_line == 'Y' {
870            let font_char = '0';
871            let text_h = 18;
872            let text_y = if interpretation_line_above == 'Y' {
873                y.saturating_sub(text_h)
874            } else {
875                y + full_h
876            } + 6;
877
878            let text_width = self.get_text_width(data, font_char, Some(text_h), None);
879            let text_x = if full_w > text_width {
880                x + (full_w - text_width) / 2
881            } else {
882                x
883            };
884
885            self.draw_text(
886                text_x,
887                text_y,
888                font_char,
889                Some(text_h),
890                None,
891                'N',
892                data,
893                false,
894                None,
895            )?;
896        }
897
898        Ok(())
899    }
900}