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::datamatrix::encoder::SymbolShapeHint;
24use rxing::{BarcodeFormat, EncodeHintType, EncodeHintValue, EncodeHints};
25
26use super::{barcode_1d_format, barcode_cache, symbology};
27use crate::engine::{Barcode1DKind, FontManager, ZplForgeBackend};
28use crate::{ZplError, ZplResult};
29
30const PDF417_TEXT_COMPACTION: u32 = 1;
32
33pub struct PngBackend {
38 canvas: RgbImage,
39 font_manager: Option<Arc<FontManager>>,
40}
41
42impl Default for PngBackend {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl PngBackend {
49 pub fn new() -> Self {
51 Self {
52 canvas: ImageBuffer::new(0, 0),
53 font_manager: None,
54 }
55 }
56
57 fn xor_overlay(&mut self, src: &RgbImage, x: i64, y: i64) {
59 let (sw, sh) = src.dimensions();
60 let (cw, ch) = self.canvas.dimensions();
61
62 for sy in 0..sh {
63 let dy = y + sy as i64;
64 if dy < 0 || dy >= ch as i64 {
65 continue;
66 }
67
68 for sx in 0..sw {
69 let dx = x + sx as i64;
70 if dx < 0 || dx >= cw as i64 {
71 continue;
72 }
73
74 let src_pixel = src[(sx, sy)];
75 if src_pixel.0 != [255, 255, 255] {
76 let dest_pixel = &mut self.canvas[(dx as u32, dy as u32)];
77 dest_pixel.0[0] ^= 255;
78 dest_pixel.0[1] ^= 255;
79 dest_pixel.0[2] ^= 255;
80 }
81 }
82 }
83 }
84
85 fn invert_rect(&mut self, rect: Rect) {
87 let (cw, ch) = self.canvas.dimensions();
88 let x_start = rect.left().max(0) as u32;
89 let y_start = rect.top().max(0) as u32;
90 let x_end = (rect.right() as u32).min(cw);
91 let y_end = (rect.bottom() as u32).min(ch);
92
93 for py in y_start..y_end {
94 for px in x_start..x_end {
95 let pixel = &mut self.canvas[(px, py)];
96 pixel.0[0] ^= 255;
97 pixel.0[1] ^= 255;
98 pixel.0[2] ^= 255;
99 }
100 }
101 }
102
103 fn draw_wrapper<F>(
105 &mut self,
106 x: u32,
107 y: u32,
108 width: u32,
109 height: u32,
110 reverse_print: bool,
111 draw_op: F,
112 ) -> ZplResult<()>
113 where
114 F: FnOnce(&mut RgbImage, i32, i32),
115 {
116 if reverse_print {
117 let mut temp_buf = ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255]));
118 draw_op(&mut temp_buf, 0, 0);
119 self.xor_overlay(&temp_buf, x as i64, y as i64);
120 } else {
121 draw_op(&mut self.canvas, x as i32, y as i32);
122 }
123 Ok(())
124 }
125
126 fn parse_hex_color(&self, color: &Option<String>) -> Rgb<u8> {
127 if let Some(hex) = color {
128 let hex = hex.trim_start_matches('#');
129 if hex.len() == 6 {
130 if let (Ok(r), Ok(g), Ok(b)) = (
131 u8::from_str_radix(&hex[0..2], 16),
132 u8::from_str_radix(&hex[2..4], 16),
133 u8::from_str_radix(&hex[4..6], 16),
134 ) {
135 return Rgb([r, g, b]);
136 }
137 } else if hex.len() == 3
138 && let (Ok(r), Ok(g), Ok(b)) = (
139 u8::from_str_radix(&hex[0..1], 16),
140 u8::from_str_radix(&hex[1..2], 16),
141 u8::from_str_radix(&hex[2..3], 16),
142 )
143 {
144 return Rgb([r * 17, g * 17, b * 17]);
145 }
146 }
147 Rgb([0, 0, 0])
148 }
149
150 fn get_text_width(
151 &self,
152 text: &str,
153 font_char: char,
154 height: Option<u32>,
155 width: Option<u32>,
156 ) -> u32 {
157 match self.font_manager.as_ref() {
158 Some(fm) => fm.measure_text(font_char, height, width, text),
159 None => 0,
160 }
161 }
162}
163
164impl ZplForgeBackend for PngBackend {
165 fn setup_page(&mut self, width: f64, height: f64, _resolution: f32) {
166 const MAX_DIM: u32 = 8192;
168 let w = (width as u32).min(MAX_DIM);
169 let h = (height as u32).min(MAX_DIM);
170 self.canvas = ImageBuffer::from_pixel(w, h, Rgb([255, 255, 255]));
171 }
172
173 fn setup_font_manager(&mut self, font_manager: &FontManager) {
174 self.font_manager = Some(Arc::new(font_manager.clone()));
175 }
176
177 fn draw_text(
178 &mut self,
179 x: u32,
180 y: u32,
181 font: char,
182 height: Option<u32>,
183 width: Option<u32>,
184 orientation: char,
185 text: &str,
186 _reverse_print: bool,
187 color: Option<String>,
188 ) -> ZplResult<()> {
189 if text.is_empty() {
190 return Ok(());
191 }
192
193 let fm = self
194 .font_manager
195 .as_ref()
196 .ok_or_else(|| ZplError::FontError("Font manager not initialized".into()))?;
197 let (font_arc, layout) = fm
198 .text_layout(font, height, width)
199 .ok_or_else(|| ZplError::FontError(format!("Font not found: {}", font)))?;
200 let font_data = font_arc.clone();
201 let scale = layout.px;
202
203 let ascent = font_data.as_scaled(scale).ascent();
206 let y_offset = (layout.baseline - ascent).round() as i32;
207
208 let text_color = self.parse_hex_color(&color);
209
210 if !matches!(orientation, 'R' | 'I' | 'B') {
211 draw_text_mut(
212 &mut self.canvas,
213 text_color,
214 x as i32,
215 y as i32 + y_offset,
216 scale,
217 &font_data,
218 text,
219 );
220 return Ok(());
221 }
222
223 let text_w = self.get_text_width(text, font, height, width).max(1);
232 let font_h = (layout.cell_h.ceil() as u32).max(1);
233 let top_pad = (-y_offset).max(0) as u32;
234 let descent = font_data.as_scaled(scale).descent();
236 let ink_bottom = (layout.baseline - descent).ceil() as i32;
237 let bottom_pad = (ink_bottom - font_h as i32).max(0) as u32;
238 let mut tmp =
239 RgbaImage::from_pixel(text_w, font_h + top_pad + bottom_pad, Rgba([0, 0, 0, 0]));
240 let text_rgba = Rgba([text_color.0[0], text_color.0[1], text_color.0[2], 255]);
241 draw_text_mut(
242 &mut tmp,
243 text_rgba,
244 0,
245 y_offset + top_pad as i32,
246 scale,
247 &font_data,
248 text,
249 );
250
251 let (rotated, pad_x, pad_y) = match orientation {
255 'R' => (rotate90(&tmp), bottom_pad, 0),
257 'I' => (rotate180(&tmp), 0, bottom_pad),
259 _ => (rotate270(&tmp), top_pad, 0),
261 };
262
263 let (cw, ch) = self.canvas.dimensions();
264 for (sx, sy, p) in rotated.enumerate_pixels() {
265 if p.0[3] > 0 {
266 let dx = x as i64 + sx as i64 - pad_x as i64;
267 let dy = y as i64 + sy as i64 - pad_y as i64;
268 if (0..cw as i64).contains(&dx) && (0..ch as i64).contains(&dy) {
269 self.canvas[(dx as u32, dy as u32)] = Rgb([p.0[0], p.0[1], p.0[2]]);
270 }
271 }
272 }
273 Ok(())
274 }
275
276 fn draw_graphic_box(
277 &mut self,
278 x: u32,
279 y: u32,
280 width: u32,
281 height: u32,
282 thickness: u32,
283 color: char,
284 custom_color: Option<String>,
285 rounding: u32,
286 reverse_print: bool,
287 ) -> ZplResult<()> {
288 let w = max(width, 1);
289 let h = max(height, 1);
290 let t = thickness;
291 let r = (rounding as f64 * 8.0) as i32;
292
293 let (draw_color, clear_color) = if let Some(custom) = custom_color {
294 (self.parse_hex_color(&Some(custom)), Rgb([255, 255, 255]))
295 } else if color == 'B' {
296 (Rgb([0, 0, 0]), Rgb([255, 255, 255]))
297 } else {
298 (Rgb([255, 255, 255]), Rgb([0, 0, 0]))
299 };
300
301 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
302 let draw_rounded_fill =
303 |img: &mut RgbImage, px: i32, py: i32, pw: u32, ph: u32, pr: i32, pc: Rgb<u8>| {
304 if pw == 0 || ph == 0 {
305 return;
306 }
307 if pr <= 0 {
308 draw_filled_rect_mut(img, Rect::at(px, py).of_size(pw, ph), pc);
309 } else {
310 let pr = pr.max(0).min((pw / 2) as i32).min((ph / 2) as i32);
311 let inner_w = pw.saturating_sub(2 * pr as u32).max(1);
312 let inner_h = ph.saturating_sub(2 * pr as u32).max(1);
313 draw_filled_rect_mut(img, Rect::at(px + pr, py).of_size(inner_w, ph), pc);
314 draw_filled_rect_mut(img, Rect::at(px, py + pr).of_size(pw, inner_h), pc);
315 draw_filled_circle_mut(img, (px + pr, py + pr), pr, pc);
316 draw_filled_circle_mut(img, (px + pw as i32 - pr - 1, py + pr), pr, pc);
317 draw_filled_circle_mut(img, (px + pr, py + ph as i32 - pr - 1), pr, pc);
318 draw_filled_circle_mut(
319 img,
320 (px + pw as i32 - pr - 1, py + ph as i32 - pr - 1),
321 pr,
322 pc,
323 );
324 }
325 };
326
327 draw_rounded_fill(img, px, py, w, h, r, draw_color);
328 if t * 2 < w && t * 2 < h {
329 draw_rounded_fill(
330 img,
331 px + t as i32,
332 py + t as i32,
333 w - t * 2,
334 h - t * 2,
335 (r - t as i32).max(0),
336 clear_color,
337 );
338 }
339 };
340
341 self.draw_wrapper(x, y, w, h, reverse_print, draw_op)
342 }
343
344 fn draw_graphic_circle(
345 &mut self,
346 x: u32,
347 y: u32,
348 radius: u32,
349 thickness: u32,
350 _color: char,
351 custom_color: Option<String>,
352 reverse_print: bool,
353 ) -> ZplResult<()> {
354 let color = self.parse_hex_color(&custom_color);
355 let clear_color = Rgb([255, 255, 255]);
356
357 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
358 let center_x = px + radius as i32;
359 let center_y = py + radius as i32;
360 draw_filled_circle_mut(img, (center_x, center_y), radius as i32, color);
361
362 if radius > thickness {
363 draw_filled_circle_mut(
364 img,
365 (center_x, center_y),
366 (radius - thickness) as i32,
367 clear_color,
368 );
369 }
370 };
371
372 self.draw_wrapper(x, y, radius * 2, radius * 2, reverse_print, draw_op)
373 }
374
375 fn draw_graphic_ellipse(
376 &mut self,
377 x: u32,
378 y: u32,
379 width: u32,
380 height: u32,
381 thickness: u32,
382 _color: char,
383 custom_color: Option<String>,
384 reverse_print: bool,
385 ) -> ZplResult<()> {
386 let color = self.parse_hex_color(&custom_color);
387 let clear_color = Rgb([255, 255, 255]);
388
389 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
390 let rx = (width / 2) as i32;
391 let ry = (height / 2) as i32;
392 let center_x = px + rx;
393 let center_y = py + ry;
394 draw_filled_ellipse_mut(img, (center_x, center_y), rx, ry, color);
395
396 let t = thickness as i32;
397 if rx > t && ry > t {
398 draw_filled_ellipse_mut(img, (center_x, center_y), rx - t, ry - t, clear_color);
399 }
400 };
401
402 self.draw_wrapper(x, y, width, height, reverse_print, draw_op)
403 }
404
405 fn draw_graphic_field(
406 &mut self,
407 x: u32,
408 y: u32,
409 width: u32,
410 height: u32,
411 data: &[u8],
412 reverse_print: bool,
413 ) -> ZplResult<()> {
414 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
415 let row_bytes = width.div_ceil(8);
416 let (img_w, img_h) = (img.width() as i32, img.height() as i32);
417
418 for (row_idx, row_data) in data.chunks(row_bytes as usize).enumerate() {
419 let dy = py + row_idx as i32;
420 if dy < 0 || dy >= img_h || row_idx as u32 >= height {
421 continue;
422 }
423
424 for (byte_idx, &byte) in row_data.iter().enumerate() {
425 if byte == 0 {
426 continue;
427 }
428 let base_x = px + (byte_idx as i32 * 8);
429 for bit_idx in 0..8 {
430 let col_idx = byte_idx as u32 * 8 + bit_idx;
431 if col_idx >= width {
432 break;
433 }
434
435 if (byte & (0x80 >> bit_idx)) != 0 {
436 let dx = base_x + bit_idx as i32;
437 if dx >= 0 && dx < img_w {
438 img[(dx as u32, dy as u32)] = Rgb([0, 0, 0]);
439 }
440 }
441 }
442 }
443 }
444 };
445
446 self.draw_wrapper(x, y, width, height, reverse_print, draw_op)
447 }
448
449 fn draw_graphic_image_custom(
450 &mut self,
451 x: u32,
452 y: u32,
453 width: u32,
454 height: u32,
455 data: &str,
456 ) -> ZplResult<()> {
457 let image_data = general_purpose::STANDARD
458 .decode(data.trim())
459 .map_err(|e| ZplError::ImageError(format!("Failed to decode base64: {}", e)))?;
460
461 let img = image::load_from_memory(&image_data)
462 .map_err(|e| ZplError::ImageError(format!("Failed to load image: {}", e)))?
463 .to_rgb8();
464
465 let (orig_w, orig_h) = img.dimensions();
466 let (target_w, target_h) = match (width, height) {
467 (0, 0) => (orig_w, orig_h),
468 (w, 0) => {
469 let h = (orig_h as f32 * (w as f32 / orig_w as f32)).round() as u32;
470 (w, h)
471 }
472 (0, h) => {
473 let w = (orig_w as f32 * (h as f32 / orig_h as f32)).round() as u32;
474 (w, h)
475 }
476 (w, h) => (w, h),
477 };
478
479 let resized_img = if target_w != orig_w || target_h != orig_h {
480 image::imageops::resize(
481 &img,
482 target_w,
483 target_h,
484 image::imageops::FilterType::Lanczos3,
485 )
486 } else {
487 img
488 };
489
490 overlay(&mut self.canvas, &resized_img, x as i64, y as i64);
491 Ok(())
492 }
493
494 fn draw_code128(
495 &mut self,
496 x: u32,
497 y: u32,
498 orientation: char,
499 height: u32,
500 module_width: u32,
501 interpretation_line: char,
502 interpretation_line_above: char,
503 _check_digit: char,
504 _mode: char,
505 data: &str,
506 reverse_print: bool,
507 ) -> ZplResult<()> {
508 let (clean_data, code_set) = super::code128_code_set(data);
509
510 let mut h = HashMap::new();
511 h.insert(
512 EncodeHintType::FORCE_CODE_SET,
513 EncodeHintValue::ForceCodeSet(code_set.to_string()),
514 );
515 let hints = Some(EncodeHints::from(h));
516
517 self.draw_1d_barcode(
518 x,
519 y,
520 orientation,
521 height,
522 module_width,
523 0.0,
525 clean_data,
526 BarcodeFormat::CODE_128,
527 reverse_print,
528 interpretation_line,
529 interpretation_line_above,
530 hints,
531 code_set,
532 )
533 }
534
535 fn draw_qr_code(
536 &mut self,
537 x: u32,
538 y: u32,
539 orientation: char,
540 _model: u32,
541 magnification: u32,
542 error_correction: char,
543 _mask: u32,
544 data: &str,
545 reverse_print: bool,
546 ) -> ZplResult<()> {
547 let (ec, payload) = super::qr_field_data(data, error_correction);
548 let level = match ec {
549 'L' => "L",
550 'Q' => "Q",
551 'H' => "H",
552 _ => "M",
553 };
554
555 let mut hints = HashMap::new();
556 hints.insert(
557 EncodeHintType::ERROR_CORRECTION,
558 EncodeHintValue::ErrorCorrection(level.to_string()),
559 );
560 let hints: EncodeHints = hints.into();
561
562 let bit_matrix = barcode_cache::encode_cached(
563 BarcodeFormat::QR_CODE,
564 payload,
565 &format!("ec:{}", level),
566 Some(&hints),
567 )?;
568
569 let mag = max(magnification, 1);
570 self.fill_matrix_cells(
571 x,
572 y + super::QR_ORIGIN_Y_OFFSET,
573 orientation,
574 mag,
575 mag,
576 &bit_matrix,
577 reverse_print,
578 );
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 hints = EncodeHints {
595 DataMatrixShape: Some(SymbolShapeHint::FORCE_SQUARE),
596 ..Default::default()
597 };
598
599 let bit_matrix =
600 barcode_cache::encode_cached(BarcodeFormat::DATA_MATRIX, data, "sq", Some(&hints))?;
601
602 let m = max(module_size, 1);
603 self.fill_matrix_cells(x, y, orientation, m, m, &bit_matrix, reverse_print);
604 Ok(())
605 }
606
607 fn draw_pdf417(
608 &mut self,
609 x: u32,
610 y: u32,
611 orientation: char,
612 row_height: u32,
613 module_width: u32,
614 security_level: u32,
615 data: &str,
616 reverse_print: bool,
617 ) -> ZplResult<()> {
618 let ec = security_level.min(8);
619 let hints = EncodeHints {
620 ErrorCorrection: Some(ec.to_string()),
621 Pdf417Compaction: Some(PDF417_TEXT_COMPACTION.to_string()),
623 Pdf417Dimensions: Some(super::pdf417_dimensions(None, None)),
624 ..Default::default()
625 };
626
627 let bit_matrix = barcode_cache::encode_cached(
628 BarcodeFormat::PDF_417,
629 data,
630 &format!("ec:{ec}:c1:t"),
631 Some(&hints),
632 )?;
633 let bit_matrix = super::pdf417_descale(&bit_matrix)?;
634
635 let cw = max(module_width, 1);
636 let ch = max(row_height, 1);
637 self.fill_matrix_cells(x, y, orientation, cw, ch, &bit_matrix, reverse_print);
638 Ok(())
639 }
640
641 fn draw_micropdf417(
642 &mut self,
643 x: u32,
644 y: u32,
645 orientation: char,
646 height: u32,
647 _mode: u32,
648 data: &str,
649 reverse_print: bool,
650 ) -> ZplResult<()> {
651 let bit_matrix = barcode_cache::encode_cached(BarcodeFormat::PDF_417, data, "", None)?;
652 let ch = max(height, 1);
653 self.fill_matrix_cells(x, y, orientation, 1, ch, &bit_matrix, reverse_print);
654 Ok(())
655 }
656
657 fn draw_aztec_code(
658 &mut self,
659 x: u32,
660 y: u32,
661 orientation: char,
662 magnification: u32,
663 data: &str,
664 reverse_print: bool,
665 ) -> ZplResult<()> {
666 let bit_matrix = barcode_cache::encode_cached(BarcodeFormat::AZTEC, data, "", None)?;
667 let m = max(magnification, 1);
668 self.fill_matrix_cells(x, y, orientation, m, m, &bit_matrix, reverse_print);
669 Ok(())
670 }
671
672 fn draw_code39(
673 &mut self,
674 x: u32,
675 y: u32,
676 orientation: char,
677 _check_digit: char,
678 height: u32,
679 module_width: u32,
680 ratio: f32,
681 interpretation_line: char,
682 interpretation_line_above: char,
683 data: &str,
684 reverse_print: bool,
685 ) -> ZplResult<()> {
686 self.draw_1d_barcode(
687 x,
688 y,
689 orientation,
690 height,
691 module_width,
692 ratio,
693 data,
694 BarcodeFormat::CODE_39,
695 reverse_print,
696 interpretation_line,
697 interpretation_line_above,
698 None,
699 "",
700 )
701 }
702
703 fn draw_barcode_1d(
704 &mut self,
705 kind: Barcode1DKind,
706 x: u32,
707 y: u32,
708 orientation: char,
709 height: u32,
710 module_width: u32,
711 ratio: f32,
712 check_digit: char,
713 interpretation_line: char,
714 interpretation_line_above: char,
715 data: &str,
716 reverse_print: bool,
717 ) -> ZplResult<()> {
718 match kind {
720 Barcode1DKind::Msi => {
721 let check = symbology::MsiCheck::from_zpl(check_digit);
722 let elements = symbology::msi_elements(data, check, module_width, ratio);
723 let text = symbology::msi_text(data, check);
724 return self.draw_elements(
725 x,
726 y,
727 orientation,
728 height,
729 module_width,
730 &elements,
731 reverse_print,
732 interpretation_line,
733 interpretation_line_above,
734 &text,
735 );
736 }
737 Barcode1DKind::Postnet => {
738 return self.draw_postnet(
739 x,
740 y,
741 orientation,
742 height,
743 module_width,
744 data,
745 reverse_print,
746 interpretation_line,
747 interpretation_line_above,
748 );
749 }
750 _ => {}
751 }
752
753 self.draw_1d_barcode(
754 x,
755 y,
756 orientation,
757 height,
758 module_width,
759 ratio,
760 data,
761 barcode_1d_format(kind),
762 reverse_print,
763 interpretation_line,
764 interpretation_line_above,
765 None,
766 "",
767 )
768 }
769
770 fn draw_graphic_diagonal(
771 &mut self,
772 x: u32,
773 y: u32,
774 width: u32,
775 height: u32,
776 thickness: u32,
777 color: char,
778 custom_color: Option<String>,
779 diagonal_orientation: char,
780 reverse_print: bool,
781 ) -> ZplResult<()> {
782 let draw_color = if custom_color.is_some() {
783 self.parse_hex_color(&custom_color)
784 } else if color == 'W' {
785 Rgb([255, 255, 255])
786 } else {
787 Rgb([0, 0, 0])
788 };
789
790 let w = max(width, 1) as i32;
791 let h = max(height, 1) as i32;
792 let t = (max(thickness, 1) as i32).min(w);
793
794 let draw_op = move |img: &mut RgbImage, px: i32, py: i32| {
795 let pts = if diagonal_orientation == 'L' {
797 [
799 Point::new(px, py),
800 Point::new(px + t, py),
801 Point::new(px + w, py + h),
802 Point::new(px + w - t, py + h),
803 ]
804 } else {
805 [
807 Point::new(px, py + h),
808 Point::new(px + t, py + h),
809 Point::new(px + w, py),
810 Point::new(px + w - t, py),
811 ]
812 };
813 draw_polygon_mut(img, &pts, draw_color);
814 };
815
816 self.draw_wrapper(x, y, w as u32, h as u32, reverse_print, draw_op)
817 }
818
819 fn finalize(&mut self) -> ZplResult<Vec<u8>> {
820 let mut bytes = Vec::new();
821 let mut cursor = std::io::Cursor::new(&mut bytes);
822 self.canvas
823 .write_to(&mut cursor, image::ImageFormat::Png)
824 .map_err(|e| ZplError::BackendError(format!("Failed to write PNG: {}", e)))?;
825 Ok(bytes)
826 }
827}
828
829impl PngBackend {
830 #[allow(clippy::too_many_arguments)]
834 fn fill_matrix_cells(
835 &mut self,
836 x: u32,
837 y: u32,
838 orientation: char,
839 cell_w: u32,
840 cell_h: u32,
841 bit_matrix: &BitMatrix,
842 reverse_print: bool,
843 ) {
844 let bw = bit_matrix.getWidth();
845 let bh = bit_matrix.getHeight();
846 let full_w = bw * cell_w;
847 let full_h = bh * cell_h;
848
849 for gy in 0..bh {
850 for gx in 0..bw {
851 if !bit_matrix.get(gx, gy) {
852 continue;
853 }
854 let lx = (gx * cell_w) as i32;
855 let ly = (gy * cell_h) as i32;
856 let (w, h) = (cell_w, cell_h);
857 let rect = match orientation {
858 'R' => {
859 let nx = full_h as i32 - (ly + h as i32);
860 Rect::at(x as i32 + nx, y as i32 + lx).of_size(h, w)
861 }
862 'I' => {
863 let nx = full_w as i32 - (lx + w as i32);
864 let ny = full_h as i32 - (ly + h as i32);
865 Rect::at(x as i32 + nx, y as i32 + ny).of_size(w, h)
866 }
867 'B' => {
868 let ny = full_w as i32 - (lx + w as i32);
869 Rect::at(x as i32 + ly, y as i32 + ny).of_size(h, w)
870 }
871 _ => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
872 };
873 if reverse_print {
874 self.invert_rect(rect);
875 } else {
876 draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
877 }
878 }
879 }
880 }
881
882 #[allow(clippy::too_many_arguments)]
883 fn draw_1d_barcode(
884 &mut self,
885 x: u32,
886 y: u32,
887 orientation: char,
888 height: u32,
889 module_width: u32,
890 ratio: f32,
891 data: &str,
892 format: BarcodeFormat,
893 reverse_print: bool,
894 interpretation_line: char,
895 interpretation_line_above: char,
896 hints: Option<EncodeHints>,
897 hints_key: &str,
898 ) -> ZplResult<()> {
899 let numeric_data;
900 let data = match format {
901 BarcodeFormat::EAN_13
902 | BarcodeFormat::EAN_8
903 | BarcodeFormat::UPC_A
904 | BarcodeFormat::UPC_E
905 | BarcodeFormat::ITF => {
906 numeric_data = data
907 .chars()
908 .filter(|c| c.is_ascii_digit())
909 .collect::<String>();
910 &numeric_data
911 }
912 _ => data,
913 };
914
915 let padded_data;
916 let data = if format == BarcodeFormat::ITF && data.len() % 2 != 0 {
917 padded_data = format!("0{}", data);
918 &padded_data
919 } else {
920 data
921 };
922
923 let bit_matrix = barcode_cache::encode_cached(format, data, hints_key, hints.as_ref())?;
924
925 let mw = max(module_width, 1);
926 let elements = symbology::matrix_elements(
929 &bit_matrix,
930 mw,
931 symbology::is_two_width(format).then_some(ratio),
932 );
933
934 self.draw_elements(
935 x,
936 y,
937 orientation,
938 height,
939 module_width,
940 &elements,
941 reverse_print,
942 interpretation_line,
943 interpretation_line_above,
944 data,
945 )
946 }
947
948 #[allow(clippy::too_many_arguments)]
954 fn draw_elements(
955 &mut self,
956 x: u32,
957 y: u32,
958 orientation: char,
959 height: u32,
960 module_width: u32,
961 elements: &[symbology::Element],
962 reverse_print: bool,
963 interpretation_line: char,
964 interpretation_line_above: char,
965 data: &str,
966 ) -> ZplResult<()> {
967 let bh = height;
968 let bw: u32 = elements.iter().map(|e| e.width).sum();
969
970 let (full_w, full_h) = match orientation {
971 'N' | 'I' => (bw, bh),
972 'R' | 'B' => (bh, bw),
973 _ => (bw, bh),
974 };
975
976 let transform_rect = |lx: i32, ly: i32, w: u32, h: u32| -> Rect {
977 match orientation {
978 'N' => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
979 'R' => {
980 let new_x = bh as i32 - (ly + h as i32);
981 let new_y = lx;
982 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
983 }
984 'I' => {
985 let new_x = bw as i32 - (lx + w as i32);
986 let new_y = bh as i32 - (ly + h as i32);
987 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(w, h)
988 }
989 'B' => {
990 let new_x = ly;
991 let new_y = bw as i32 - (lx + w as i32);
992 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
993 }
994 _ => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
995 }
996 };
997
998 let mut cursor = 0u32;
999 for element in elements {
1000 if element.bar {
1001 let rect = transform_rect(cursor as i32, 0, element.width, bh);
1002 if reverse_print {
1003 self.invert_rect(rect);
1004 } else {
1005 draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
1006 }
1007 }
1008 cursor += element.width;
1009 }
1010
1011 if interpretation_line == 'Y' {
1012 let (font_char, text_h, text_w, gap) =
1013 crate::engine::font::interpretation_metrics(module_width);
1014 let text_y = if interpretation_line_above == 'Y' {
1015 y.saturating_sub(text_h + gap)
1016 } else {
1017 y + full_h + gap
1018 };
1019
1020 let text_width = self.get_text_width(data, font_char, Some(text_h), Some(text_w));
1021 let text_x = if full_w > text_width {
1022 x + (full_w - text_width) / 2
1023 } else {
1024 x
1025 };
1026
1027 self.draw_text(
1028 text_x,
1029 text_y,
1030 font_char,
1031 Some(text_h),
1032 Some(text_w),
1033 'N',
1034 data,
1035 false,
1036 None,
1037 )?;
1038 }
1039
1040 Ok(())
1041 }
1042
1043 #[allow(clippy::too_many_arguments)]
1050 fn draw_postnet(
1051 &mut self,
1052 x: u32,
1053 y: u32,
1054 orientation: char,
1055 height: u32,
1056 module_width: u32,
1057 data: &str,
1058 reverse_print: bool,
1059 interpretation_line: char,
1060 interpretation_line_above: char,
1061 ) -> ZplResult<()> {
1062 let bars = symbology::postnet_bars(data);
1063 let (bar_w, gap) = symbology::postnet_pitch(module_width);
1064 let pitch = bar_w + gap;
1065 let short_h = ((height as f32) * symbology::POSTNET_SHORT_RATIO).round() as u32;
1066 let full_w = bars.len() as u32 * bar_w + bars.len().saturating_sub(1) as u32 * gap;
1067
1068 for (i, bar) in bars.iter().enumerate() {
1069 let h = match bar {
1070 symbology::BarHeight::Full => height,
1071 symbology::BarHeight::Half => short_h,
1072 };
1073 let top = height.saturating_sub(h);
1075 let lx = i as u32 * pitch;
1076 let rect = match orientation {
1077 'R' => Rect::at(x as i32 + top as i32, y as i32 + lx as i32).of_size(h, bar_w),
1078 'I' => Rect::at(
1079 x as i32 + (full_w - lx - bar_w) as i32,
1080 y as i32 + (height - h - top) as i32,
1081 )
1082 .of_size(bar_w, h),
1083 'B' => Rect::at(
1084 x as i32 + (height - h - top) as i32,
1085 y as i32 + (full_w - lx - bar_w) as i32,
1086 )
1087 .of_size(h, bar_w),
1088 _ => Rect::at(x as i32 + lx as i32, y as i32 + top as i32).of_size(bar_w, h),
1089 };
1090 if reverse_print {
1091 self.invert_rect(rect);
1092 } else {
1093 draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
1094 }
1095 }
1096
1097 if interpretation_line == 'Y' {
1098 let (font_char, text_h, text_w, gap) =
1099 crate::engine::font::interpretation_metrics(module_width);
1100 let text_y = if interpretation_line_above == 'Y' {
1101 y.saturating_sub(text_h + gap)
1102 } else {
1103 y + height + gap
1104 };
1105 let digits: String = data.chars().filter(|c| c.is_ascii_digit()).collect();
1106 let text_width = self.get_text_width(&digits, font_char, Some(text_h), Some(text_w));
1107 let text_x = if full_w > text_width {
1108 x + (full_w - text_width) / 2
1109 } else {
1110 x
1111 };
1112 self.draw_text(
1113 text_x,
1114 text_y,
1115 font_char,
1116 Some(text_h),
1117 Some(text_w),
1118 'N',
1119 &digits,
1120 false,
1121 None,
1122 )?;
1123 }
1124
1125 Ok(())
1126 }
1127}