1use std::cmp::max;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use ab_glyph::{Font, 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
29pub 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 pub fn new() -> Self {
47 Self {
48 canvas: ImageBuffer::new(0, 0),
49 font_manager: None,
50 }
51 }
52
53 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 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 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 match self.font_manager.as_ref() {
154 Some(fm) => fm.measure_text(font_char, height, width, text),
155 None => 0,
156 }
157 }
158}
159
160impl ZplForgeBackend for PngBackend {
161 fn setup_page(&mut self, width: f64, height: f64, _resolution: f32) {
162 const MAX_DIM: u32 = 8192;
164 let w = (width as u32).min(MAX_DIM);
165 let h = (height as u32).min(MAX_DIM);
166 self.canvas = ImageBuffer::from_pixel(w, h, Rgb([255, 255, 255]));
167 }
168
169 fn setup_font_manager(&mut self, font_manager: &FontManager) {
170 self.font_manager = Some(Arc::new(font_manager.clone()));
171 }
172
173 fn draw_text(
174 &mut self,
175 x: u32,
176 y: u32,
177 font: char,
178 height: Option<u32>,
179 width: Option<u32>,
180 orientation: char,
181 text: &str,
182 _reverse_print: bool,
183 color: Option<String>,
184 ) -> ZplResult<()> {
185 if text.is_empty() {
186 return Ok(());
187 }
188
189 let fm = self
190 .font_manager
191 .as_ref()
192 .ok_or_else(|| ZplError::FontError("Font manager not initialized".into()))?;
193 let (font_arc, layout) = fm
194 .text_layout(font, height, width)
195 .ok_or_else(|| ZplError::FontError(format!("Font not found: {}", font)))?;
196 let font_data = font_arc.clone();
197 let scale = layout.px;
198
199 let ascent = font_data.as_scaled(scale).ascent();
202 let y_offset = (layout.baseline - ascent).round() as i32;
203
204 let text_color = self.parse_hex_color(&color);
205
206 if !matches!(orientation, 'R' | 'I' | 'B') {
207 draw_text_mut(
208 &mut self.canvas,
209 text_color,
210 x as i32,
211 y as i32 + y_offset,
212 scale,
213 &font_data,
214 text,
215 );
216 return Ok(());
217 }
218
219 let text_w = self.get_text_width(text, font, height, width).max(1);
228 let font_h = (layout.cell_h.ceil() as u32).max(1);
229 let top_pad = (-y_offset).max(0) as u32;
230 let descent = font_data.as_scaled(scale).descent();
232 let ink_bottom = (layout.baseline - descent).ceil() as i32;
233 let bottom_pad = (ink_bottom - font_h as i32).max(0) as u32;
234 let mut tmp =
235 RgbaImage::from_pixel(text_w, font_h + top_pad + bottom_pad, Rgba([0, 0, 0, 0]));
236 let text_rgba = Rgba([text_color.0[0], text_color.0[1], text_color.0[2], 255]);
237 draw_text_mut(
238 &mut tmp,
239 text_rgba,
240 0,
241 y_offset + top_pad as i32,
242 scale,
243 &font_data,
244 text,
245 );
246
247 let (rotated, pad_x, pad_y) = match orientation {
251 'R' => (rotate90(&tmp), bottom_pad, 0),
253 'I' => (rotate180(&tmp), 0, bottom_pad),
255 _ => (rotate270(&tmp), top_pad, 0),
257 };
258
259 let (cw, ch) = self.canvas.dimensions();
260 for (sx, sy, p) in rotated.enumerate_pixels() {
261 if p.0[3] > 0 {
262 let dx = x as i64 + sx as i64 - pad_x as i64;
263 let dy = y as i64 + sy as i64 - pad_y as i64;
264 if (0..cw as i64).contains(&dx) && (0..ch as i64).contains(&dy) {
265 self.canvas[(dx as u32, dy as u32)] = Rgb([p.0[0], p.0[1], p.0[2]]);
266 }
267 }
268 }
269 Ok(())
270 }
271
272 fn draw_graphic_box(
273 &mut self,
274 x: u32,
275 y: u32,
276 width: u32,
277 height: u32,
278 thickness: u32,
279 color: char,
280 custom_color: Option<String>,
281 rounding: u32,
282 reverse_print: bool,
283 ) -> ZplResult<()> {
284 let w = max(width, 1);
285 let h = max(height, 1);
286 let t = thickness;
287 let r = (rounding as f64 * 8.0) as i32;
288
289 let (draw_color, clear_color) = if let Some(custom) = custom_color {
290 (self.parse_hex_color(&Some(custom)), Rgb([255, 255, 255]))
291 } else if color == 'B' {
292 (Rgb([0, 0, 0]), Rgb([255, 255, 255]))
293 } else {
294 (Rgb([255, 255, 255]), Rgb([0, 0, 0]))
295 };
296
297 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
298 let draw_rounded_fill =
299 |img: &mut RgbImage, px: i32, py: i32, pw: u32, ph: u32, pr: i32, pc: Rgb<u8>| {
300 if pw == 0 || ph == 0 {
301 return;
302 }
303 if pr <= 0 {
304 draw_filled_rect_mut(img, Rect::at(px, py).of_size(pw, ph), pc);
305 } else {
306 let pr = pr.max(0).min((pw / 2) as i32).min((ph / 2) as i32);
307 let inner_w = pw.saturating_sub(2 * pr as u32).max(1);
308 let inner_h = ph.saturating_sub(2 * pr as u32).max(1);
309 draw_filled_rect_mut(img, Rect::at(px + pr, py).of_size(inner_w, ph), pc);
310 draw_filled_rect_mut(img, Rect::at(px, py + pr).of_size(pw, inner_h), pc);
311 draw_filled_circle_mut(img, (px + pr, py + pr), pr, pc);
312 draw_filled_circle_mut(img, (px + pw as i32 - pr - 1, py + pr), pr, pc);
313 draw_filled_circle_mut(img, (px + pr, py + ph as i32 - pr - 1), pr, pc);
314 draw_filled_circle_mut(
315 img,
316 (px + pw as i32 - pr - 1, py + ph as i32 - pr - 1),
317 pr,
318 pc,
319 );
320 }
321 };
322
323 draw_rounded_fill(img, px, py, w, h, r, draw_color);
324 if t * 2 < w && t * 2 < h {
325 draw_rounded_fill(
326 img,
327 px + t as i32,
328 py + t as i32,
329 w - t * 2,
330 h - t * 2,
331 (r - t as i32).max(0),
332 clear_color,
333 );
334 }
335 };
336
337 self.draw_wrapper(x, y, w, h, reverse_print, draw_op)
338 }
339
340 fn draw_graphic_circle(
341 &mut self,
342 x: u32,
343 y: u32,
344 radius: u32,
345 thickness: u32,
346 _color: char,
347 custom_color: Option<String>,
348 reverse_print: bool,
349 ) -> ZplResult<()> {
350 let color = self.parse_hex_color(&custom_color);
351 let clear_color = Rgb([255, 255, 255]);
352
353 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
354 let center_x = px + radius as i32;
355 let center_y = py + radius as i32;
356 draw_filled_circle_mut(img, (center_x, center_y), radius as i32, color);
357
358 if radius > thickness {
359 draw_filled_circle_mut(
360 img,
361 (center_x, center_y),
362 (radius - thickness) as i32,
363 clear_color,
364 );
365 }
366 };
367
368 self.draw_wrapper(x, y, radius * 2, radius * 2, reverse_print, draw_op)
369 }
370
371 fn draw_graphic_ellipse(
372 &mut self,
373 x: u32,
374 y: u32,
375 width: u32,
376 height: u32,
377 thickness: u32,
378 _color: char,
379 custom_color: Option<String>,
380 reverse_print: bool,
381 ) -> ZplResult<()> {
382 let color = self.parse_hex_color(&custom_color);
383 let clear_color = Rgb([255, 255, 255]);
384
385 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
386 let rx = (width / 2) as i32;
387 let ry = (height / 2) as i32;
388 let center_x = px + rx;
389 let center_y = py + ry;
390 draw_filled_ellipse_mut(img, (center_x, center_y), rx, ry, color);
391
392 let t = thickness as i32;
393 if rx > t && ry > t {
394 draw_filled_ellipse_mut(img, (center_x, center_y), rx - t, ry - t, clear_color);
395 }
396 };
397
398 self.draw_wrapper(x, y, width, height, reverse_print, draw_op)
399 }
400
401 fn draw_graphic_field(
402 &mut self,
403 x: u32,
404 y: u32,
405 width: u32,
406 height: u32,
407 data: &[u8],
408 reverse_print: bool,
409 ) -> ZplResult<()> {
410 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
411 let row_bytes = width.div_ceil(8);
412 let (img_w, img_h) = (img.width() as i32, img.height() as i32);
413
414 for (row_idx, row_data) in data.chunks(row_bytes as usize).enumerate() {
415 let dy = py + row_idx as i32;
416 if dy < 0 || dy >= img_h || row_idx as u32 >= height {
417 continue;
418 }
419
420 for (byte_idx, &byte) in row_data.iter().enumerate() {
421 if byte == 0 {
422 continue;
423 }
424 let base_x = px + (byte_idx as i32 * 8);
425 for bit_idx in 0..8 {
426 let col_idx = byte_idx as u32 * 8 + bit_idx;
427 if col_idx >= width {
428 break;
429 }
430
431 if (byte & (0x80 >> bit_idx)) != 0 {
432 let dx = base_x + bit_idx as i32;
433 if dx >= 0 && dx < img_w {
434 img[(dx as u32, dy as u32)] = Rgb([0, 0, 0]);
435 }
436 }
437 }
438 }
439 }
440 };
441
442 self.draw_wrapper(x, y, width, height, reverse_print, draw_op)
443 }
444
445 fn draw_graphic_image_custom(
446 &mut self,
447 x: u32,
448 y: u32,
449 width: u32,
450 height: u32,
451 data: &str,
452 ) -> ZplResult<()> {
453 let image_data = general_purpose::STANDARD
454 .decode(data.trim())
455 .map_err(|e| ZplError::ImageError(format!("Failed to decode base64: {}", e)))?;
456
457 let img = image::load_from_memory(&image_data)
458 .map_err(|e| ZplError::ImageError(format!("Failed to load image: {}", e)))?
459 .to_rgb8();
460
461 let (orig_w, orig_h) = img.dimensions();
462 let (target_w, target_h) = match (width, height) {
463 (0, 0) => (orig_w, orig_h),
464 (w, 0) => {
465 let h = (orig_h as f32 * (w as f32 / orig_w as f32)).round() as u32;
466 (w, h)
467 }
468 (0, h) => {
469 let w = (orig_w as f32 * (h as f32 / orig_h as f32)).round() as u32;
470 (w, h)
471 }
472 (w, h) => (w, h),
473 };
474
475 let resized_img = if target_w != orig_w || target_h != orig_h {
476 image::imageops::resize(
477 &img,
478 target_w,
479 target_h,
480 image::imageops::FilterType::Lanczos3,
481 )
482 } else {
483 img
484 };
485
486 overlay(&mut self.canvas, &resized_img, x as i64, y as i64);
487 Ok(())
488 }
489
490 fn draw_code128(
491 &mut self,
492 x: u32,
493 y: u32,
494 orientation: char,
495 height: u32,
496 module_width: u32,
497 interpretation_line: char,
498 interpretation_line_above: char,
499 _check_digit: char,
500 _mode: char,
501 data: &str,
502 reverse_print: bool,
503 ) -> ZplResult<()> {
504 let (clean_data, hint_val) = if let Some(stripped) = data.strip_prefix(">:") {
505 (stripped, Some("B"))
506 } else if let Some(stripped) = data.strip_prefix(">;") {
507 (stripped, Some("C"))
508 } else if let Some(stripped) = data.strip_prefix(">9") {
509 (stripped, Some("A"))
510 } else {
511 (data, Some("B")) };
513
514 let hints = hint_val.map(|v| {
515 let mut h = HashMap::new();
516 h.insert(
517 EncodeHintType::FORCE_CODE_SET,
518 EncodeHintValue::ForceCodeSet(v.to_string()),
519 );
520 EncodeHints::from(h)
521 });
522
523 self.draw_1d_barcode(
524 x,
525 y,
526 orientation,
527 height,
528 module_width,
529 clean_data,
530 BarcodeFormat::CODE_128,
531 reverse_print,
532 interpretation_line,
533 interpretation_line_above,
534 hints,
535 hint_val.unwrap_or(""),
536 )
537 }
538
539 fn draw_qr_code(
540 &mut self,
541 x: u32,
542 y: u32,
543 orientation: char,
544 _model: u32,
545 magnification: u32,
546 error_correction: char,
547 _mask: u32,
548 data: &str,
549 reverse_print: bool,
550 ) -> ZplResult<()> {
551 let level = match error_correction {
552 'L' => "L",
553 'M' => "M",
554 'Q' => "Q",
555 'H' => "H",
556 _ => "M",
557 };
558
559 let mut hints = HashMap::new();
560 hints.insert(
561 EncodeHintType::ERROR_CORRECTION,
562 EncodeHintValue::ErrorCorrection(level.to_string()),
563 );
564 hints.insert(
565 EncodeHintType::MARGIN,
566 EncodeHintValue::Margin("0".to_owned()),
567 );
568 let hints: EncodeHints = hints.into();
569
570 let bit_matrix = barcode_cache::encode_cached(
571 BarcodeFormat::QR_CODE,
572 data,
573 &format!("ec:{}", level),
574 Some(&hints),
575 )?;
576
577 let mag = max(magnification, 1);
578 self.fill_matrix_cells(x, y, orientation, mag, mag, &bit_matrix, reverse_print);
579 Ok(())
580 }
581
582 fn draw_datamatrix(
583 &mut self,
584 x: u32,
585 y: u32,
586 orientation: char,
587 module_size: u32,
588 data: &str,
589 reverse_print: bool,
590 ) -> ZplResult<()> {
591 let bit_matrix = barcode_cache::encode_cached(BarcodeFormat::DATA_MATRIX, data, "", None)?;
592
593 let m = max(module_size, 1);
594 self.fill_matrix_cells(x, y, orientation, m, m, &bit_matrix, reverse_print);
595 Ok(())
596 }
597
598 fn draw_pdf417(
599 &mut self,
600 x: u32,
601 y: u32,
602 orientation: char,
603 row_height: u32,
604 module_width: u32,
605 security_level: u32,
606 data: &str,
607 reverse_print: bool,
608 ) -> ZplResult<()> {
609 let mut hints = HashMap::new();
610 hints.insert(
611 EncodeHintType::ERROR_CORRECTION,
612 EncodeHintValue::ErrorCorrection(security_level.min(8).to_string()),
613 );
614 hints.insert(
615 EncodeHintType::MARGIN,
616 EncodeHintValue::Margin("0".to_owned()),
617 );
618 let hints: EncodeHints = hints.into();
619
620 let bit_matrix = barcode_cache::encode_cached(
621 BarcodeFormat::PDF_417,
622 data,
623 &format!("ec:{}", security_level.min(8)),
624 Some(&hints),
625 )?;
626
627 let cw = max(module_width, 1);
628 let ch = max(row_height, 1);
629 self.fill_matrix_cells(x, y, orientation, cw, ch, &bit_matrix, reverse_print);
630 Ok(())
631 }
632
633 fn draw_code39(
634 &mut self,
635 x: u32,
636 y: u32,
637 orientation: char,
638 _check_digit: char,
639 height: u32,
640 module_width: u32,
641 interpretation_line: char,
642 interpretation_line_above: char,
643 data: &str,
644 reverse_print: bool,
645 ) -> ZplResult<()> {
646 self.draw_1d_barcode(
647 x,
648 y,
649 orientation,
650 height,
651 module_width,
652 data,
653 BarcodeFormat::CODE_39,
654 reverse_print,
655 interpretation_line,
656 interpretation_line_above,
657 None,
658 "",
659 )
660 }
661
662 fn draw_barcode_1d(
663 &mut self,
664 kind: Barcode1DKind,
665 x: u32,
666 y: u32,
667 orientation: char,
668 height: u32,
669 module_width: u32,
670 interpretation_line: char,
671 interpretation_line_above: char,
672 data: &str,
673 reverse_print: bool,
674 ) -> ZplResult<()> {
675 self.draw_1d_barcode(
676 x,
677 y,
678 orientation,
679 height,
680 module_width,
681 data,
682 barcode_1d_format(kind),
683 reverse_print,
684 interpretation_line,
685 interpretation_line_above,
686 None,
687 "",
688 )
689 }
690
691 fn draw_graphic_diagonal(
692 &mut self,
693 x: u32,
694 y: u32,
695 width: u32,
696 height: u32,
697 thickness: u32,
698 color: char,
699 custom_color: Option<String>,
700 diagonal_orientation: char,
701 reverse_print: bool,
702 ) -> ZplResult<()> {
703 let draw_color = if custom_color.is_some() {
704 self.parse_hex_color(&custom_color)
705 } else if color == 'W' {
706 Rgb([255, 255, 255])
707 } else {
708 Rgb([0, 0, 0])
709 };
710
711 let w = max(width, 1) as i32;
712 let h = max(height, 1) as i32;
713 let t = (max(thickness, 1) as i32).min(w);
714
715 let draw_op = move |img: &mut RgbImage, px: i32, py: i32| {
716 let pts = if diagonal_orientation == 'L' {
718 [
720 Point::new(px, py),
721 Point::new(px + t, py),
722 Point::new(px + w, py + h),
723 Point::new(px + w - t, py + h),
724 ]
725 } else {
726 [
728 Point::new(px, py + h),
729 Point::new(px + t, py + h),
730 Point::new(px + w, py),
731 Point::new(px + w - t, py),
732 ]
733 };
734 draw_polygon_mut(img, &pts, draw_color);
735 };
736
737 self.draw_wrapper(x, y, w as u32, h as u32, reverse_print, draw_op)
738 }
739
740 fn finalize(&mut self) -> ZplResult<Vec<u8>> {
741 let mut bytes = Vec::new();
742 let mut cursor = std::io::Cursor::new(&mut bytes);
743 self.canvas
744 .write_to(&mut cursor, image::ImageFormat::Png)
745 .map_err(|e| ZplError::BackendError(format!("Failed to write PNG: {}", e)))?;
746 Ok(bytes)
747 }
748}
749
750impl PngBackend {
751 #[allow(clippy::too_many_arguments)]
755 fn fill_matrix_cells(
756 &mut self,
757 x: u32,
758 y: u32,
759 orientation: char,
760 cell_w: u32,
761 cell_h: u32,
762 bit_matrix: &BitMatrix,
763 reverse_print: bool,
764 ) {
765 let bw = bit_matrix.getWidth();
766 let bh = bit_matrix.getHeight();
767 let full_w = bw * cell_w;
768 let full_h = bh * cell_h;
769
770 for gy in 0..bh {
771 for gx in 0..bw {
772 if !bit_matrix.get(gx, gy) {
773 continue;
774 }
775 let lx = (gx * cell_w) as i32;
776 let ly = (gy * cell_h) as i32;
777 let (w, h) = (cell_w, cell_h);
778 let rect = match orientation {
779 'R' => {
780 let nx = full_h as i32 - (ly + h as i32);
781 Rect::at(x as i32 + nx, y as i32 + lx).of_size(h, w)
782 }
783 'I' => {
784 let nx = full_w as i32 - (lx + w as i32);
785 let ny = full_h as i32 - (ly + h as i32);
786 Rect::at(x as i32 + nx, y as i32 + ny).of_size(w, h)
787 }
788 'B' => {
789 let ny = full_w as i32 - (lx + w as i32);
790 Rect::at(x as i32 + ly, y as i32 + ny).of_size(h, w)
791 }
792 _ => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
793 };
794 if reverse_print {
795 self.invert_rect(rect);
796 } else {
797 draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
798 }
799 }
800 }
801 }
802
803 #[allow(clippy::too_many_arguments)]
804 fn draw_1d_barcode(
805 &mut self,
806 x: u32,
807 y: u32,
808 orientation: char,
809 height: u32,
810 module_width: u32,
811 data: &str,
812 format: BarcodeFormat,
813 reverse_print: bool,
814 interpretation_line: char,
815 interpretation_line_above: char,
816 hints: Option<EncodeHints>,
817 hints_key: &str,
818 ) -> ZplResult<()> {
819 let bit_matrix = barcode_cache::encode_cached(format, data, hints_key, hints.as_ref())?;
820
821 let mw = max(module_width, 1);
822 let bh = height;
823 let bw = bit_matrix.getWidth() * mw;
824
825 let (full_w, full_h) = match orientation {
826 'N' | 'I' => (bw, bh),
827 'R' | 'B' => (bh, bw),
828 _ => (bw, bh),
829 };
830
831 let transform_rect = |lx: i32, ly: i32, w: u32, h: u32| -> Rect {
832 match orientation {
833 'N' => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
834 'R' => {
835 let new_x = bh as i32 - (ly + h as i32);
836 let new_y = lx;
837 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
838 }
839 'I' => {
840 let new_x = bw as i32 - (lx + w as i32);
841 let new_y = bh as i32 - (ly + h as i32);
842 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(w, h)
843 }
844 'B' => {
845 let new_x = ly;
846 let new_y = bw as i32 - (lx + w as i32);
847 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
848 }
849 _ => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
850 }
851 };
852
853 for gx in 0..bit_matrix.getWidth() {
854 if bit_matrix.get(gx, 0) {
855 let rect = transform_rect((gx * mw) as i32, 0, mw, bh);
856 if reverse_print {
857 self.invert_rect(rect);
858 } else {
859 draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
860 }
861 }
862 }
863
864 if interpretation_line == 'Y' {
865 let font_char = '0';
866 let (text_h, gap) = crate::engine::font::interpretation_metrics(module_width);
867 let text_y = if interpretation_line_above == 'Y' {
868 y.saturating_sub(text_h + gap)
869 } else {
870 y + full_h + gap
871 };
872
873 let text_width = self.get_text_width(data, font_char, Some(text_h), None);
874 let text_x = if full_w > text_width {
875 x + (full_w - text_width) / 2
876 } else {
877 x
878 };
879
880 self.draw_text(
881 text_x,
882 text_y,
883 font_char,
884 Some(text_h),
885 None,
886 'N',
887 data,
888 false,
889 None,
890 )?;
891 }
892
893 Ok(())
894 }
895}