1use std::cmp::max;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use ab_glyph::{Font, PxScale, ScaleFont};
6use base64::{engine::general_purpose, Engine as _};
7use image::{imageops::overlay, ImageBuffer, Rgb, RgbImage};
8use imageproc::drawing::{
9 draw_filled_circle_mut, draw_filled_ellipse_mut, draw_filled_rect_mut, draw_text_mut,
10};
11use imageproc::rect::Rect;
12use rxing::{
13 BarcodeFormat, EncodeHintType, EncodeHintValue, EncodeHints, MultiFormatWriter, Writer,
14};
15
16use crate::engine::{FontManager, ZplForgeBackend};
17use crate::{ZplError, ZplResult};
18
19pub struct PngBackend {
24 canvas: RgbImage,
25 font_manager: Option<Arc<FontManager>>,
26}
27
28impl Default for PngBackend {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl PngBackend {
35 pub fn new() -> Self {
37 Self {
38 canvas: ImageBuffer::new(0, 0),
39 font_manager: None,
40 }
41 }
42
43 fn xor_overlay(&mut self, src: &RgbImage, x: i64, y: i64) {
45 let (sw, sh) = src.dimensions();
46 let (cw, ch) = self.canvas.dimensions();
47
48 for sy in 0..sh {
49 let dy = y + sy as i64;
50 if dy < 0 || dy >= ch as i64 {
51 continue;
52 }
53
54 for sx in 0..sw {
55 let dx = x + sx as i64;
56 if dx < 0 || dx >= cw as i64 {
57 continue;
58 }
59
60 let src_pixel = src[(sx, sy)];
61 if src_pixel.0 != [255, 255, 255] {
62 let dest_pixel = &mut self.canvas[(dx as u32, dy as u32)];
63 dest_pixel.0[0] ^= 255;
64 dest_pixel.0[1] ^= 255;
65 dest_pixel.0[2] ^= 255;
66 }
67 }
68 }
69 }
70
71 fn invert_rect(&mut self, rect: Rect) {
73 let (cw, ch) = self.canvas.dimensions();
74 let x_start = rect.left().max(0) as u32;
75 let y_start = rect.top().max(0) as u32;
76 let x_end = (rect.right() as u32).min(cw);
77 let y_end = (rect.bottom() as u32).min(ch);
78
79 for py in y_start..y_end {
80 for px in x_start..x_end {
81 let pixel = &mut self.canvas[(px, py)];
82 pixel.0[0] ^= 255;
83 pixel.0[1] ^= 255;
84 pixel.0[2] ^= 255;
85 }
86 }
87 }
88
89 fn draw_wrapper<F>(
91 &mut self,
92 x: u32,
93 y: u32,
94 width: u32,
95 height: u32,
96 reverse_print: bool,
97 draw_op: F,
98 ) -> ZplResult<()>
99 where
100 F: FnOnce(&mut RgbImage, i32, i32),
101 {
102 if reverse_print {
103 let mut temp_buf = ImageBuffer::from_pixel(width, height, Rgb([255, 255, 255]));
104 draw_op(&mut temp_buf, 0, 0);
105 self.xor_overlay(&temp_buf, x as i64, y as i64);
106 } else {
107 draw_op(&mut self.canvas, x as i32, y as i32);
108 }
109 Ok(())
110 }
111
112 fn parse_hex_color(&self, color: &Option<String>) -> Rgb<u8> {
113 if let Some(hex) = color {
114 let hex = hex.trim_start_matches('#');
115 if hex.len() == 6 {
116 if let (Ok(r), Ok(g), Ok(b)) = (
117 u8::from_str_radix(&hex[0..2], 16),
118 u8::from_str_radix(&hex[2..4], 16),
119 u8::from_str_radix(&hex[4..6], 16),
120 ) {
121 return Rgb([r, g, b]);
122 }
123 } else if hex.len() == 3 {
124 if let (Ok(r), Ok(g), Ok(b)) = (
125 u8::from_str_radix(&hex[0..1], 16),
126 u8::from_str_radix(&hex[1..2], 16),
127 u8::from_str_radix(&hex[2..3], 16),
128 ) {
129 return Rgb([r * 17, g * 17, b * 17]);
130 }
131 }
132 }
133 Rgb([0, 0, 0])
134 }
135
136 fn get_text_width(
137 &self,
138 text: &str,
139 font_char: char,
140 height: Option<u32>,
141 width: Option<u32>,
142 ) -> u32 {
143 let font = match self.font_manager.as_ref() {
144 Some(fm) => match fm.get_font(&font_char.to_string()) {
145 Some(f) => f,
146 None => match fm.get_font("0") {
147 Some(f) => f,
148 None => return 0,
149 },
150 },
151 None => return 0,
152 };
153
154 let scale_y = height.unwrap_or(9) as f32;
155 let scale_x = width.unwrap_or(scale_y as u32) as f32;
156 let scale = PxScale {
157 x: scale_x,
158 y: scale_y,
159 };
160
161 let scaled_font = font.as_scaled(scale);
162 let mut width = 0.0;
163 let mut last_glyph_id = None;
164
165 for c in text.chars() {
166 let glyph_id = font.glyph_id(c);
167 if let Some(last) = last_glyph_id {
168 width += scaled_font.kern(last, glyph_id);
169 }
170 width += scaled_font.h_advance(glyph_id);
171 last_glyph_id = Some(glyph_id);
172 }
173
174 width.ceil() as u32
175 }
176}
177
178impl ZplForgeBackend for PngBackend {
179 fn setup_page(&mut self, width: f64, height: f64, _resolution: f32) {
180 const MAX_DIM: u32 = 8192;
182 let w = (width as u32).min(MAX_DIM);
183 let h = (height as u32).min(MAX_DIM);
184 self.canvas = ImageBuffer::from_pixel(w, h, Rgb([255, 255, 255]));
185 }
186
187 fn setup_font_manager(&mut self, font_manager: &FontManager) {
188 self.font_manager = Some(Arc::new(font_manager.clone()));
189 }
190
191 fn draw_text(
192 &mut self,
193 x: u32,
194 y: u32,
195 font: char,
196 height: Option<u32>,
197 width: Option<u32>,
198 text: String,
199 _reverse_print: bool,
200 color: Option<String>,
201 ) -> ZplResult<()> {
202 if text.is_empty() {
203 return Ok(());
204 }
205
206 let font_data = match self.font_manager.as_ref() {
207 Some(fm) => match fm.get_font(&font.to_string()) {
208 Some(f) => f,
209 None => match fm.get_font("0") {
210 Some(f) => f,
211 None => return Err(ZplError::FontError(format!("Font not found: {}", font))),
212 },
213 },
214 None => return Err(ZplError::FontError("Font manager not initialized".into())),
215 };
216
217 let scale_y = height.unwrap_or(9) as f32;
218 let scale_x = width.unwrap_or(scale_y as u32) as f32;
219 let scale = PxScale {
220 x: scale_x,
221 y: scale_y,
222 };
223
224 let text_color = self.parse_hex_color(&color);
225
226 draw_text_mut(
227 &mut self.canvas,
228 text_color,
229 x as i32,
230 y as i32,
231 scale,
232 font_data,
233 &text,
234 );
235 Ok(())
236 }
237
238 fn draw_graphic_box(
239 &mut self,
240 x: u32,
241 y: u32,
242 width: u32,
243 height: u32,
244 thickness: u32,
245 color: char,
246 custom_color: Option<String>,
247 rounding: u32,
248 reverse_print: bool,
249 ) -> ZplResult<()> {
250 let w = max(width, 1);
251 let h = max(height, 1);
252 let t = thickness;
253 let r = (rounding as f64 * 8.0) as i32;
254
255 let (draw_color, clear_color) = if let Some(custom) = custom_color {
256 (self.parse_hex_color(&Some(custom)), Rgb([255, 255, 255]))
257 } else if color == 'B' {
258 (Rgb([0, 0, 0]), Rgb([255, 255, 255]))
259 } else {
260 (Rgb([255, 255, 255]), Rgb([0, 0, 0]))
261 };
262
263 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
264 let draw_rounded_fill =
265 |img: &mut RgbImage, px: i32, py: i32, pw: u32, ph: u32, pr: i32, pc: Rgb<u8>| {
266 if pw == 0 || ph == 0 {
267 return;
268 }
269 if pr <= 0 {
270 draw_filled_rect_mut(img, Rect::at(px, py).of_size(pw, ph), pc);
271 } else {
272 let pr = pr.max(0).min((pw / 2) as i32).min((ph / 2) as i32);
273 let inner_w = pw.saturating_sub(2 * pr as u32).max(1);
274 let inner_h = ph.saturating_sub(2 * pr as u32).max(1);
275 draw_filled_rect_mut(img, Rect::at(px + pr, py).of_size(inner_w, ph), pc);
276 draw_filled_rect_mut(img, Rect::at(px, py + pr).of_size(pw, inner_h), pc);
277 draw_filled_circle_mut(img, (px + pr, py + pr), pr, pc);
278 draw_filled_circle_mut(img, (px + pw as i32 - pr - 1, py + pr), pr, pc);
279 draw_filled_circle_mut(img, (px + pr, py + ph as i32 - pr - 1), pr, pc);
280 draw_filled_circle_mut(
281 img,
282 (px + pw as i32 - pr - 1, py + ph as i32 - pr - 1),
283 pr,
284 pc,
285 );
286 }
287 };
288
289 draw_rounded_fill(img, px, py, w, h, r, draw_color);
290 if t * 2 < w && t * 2 < h {
291 draw_rounded_fill(
292 img,
293 px + t as i32,
294 py + t as i32,
295 w - t * 2,
296 h - t * 2,
297 (r - t as i32).max(0),
298 clear_color,
299 );
300 }
301 };
302
303 self.draw_wrapper(x, y, w, h, reverse_print, draw_op)
304 }
305
306 fn draw_graphic_circle(
307 &mut self,
308 x: u32,
309 y: u32,
310 radius: u32,
311 thickness: u32,
312 _color: char,
313 custom_color: Option<String>,
314 reverse_print: bool,
315 ) -> ZplResult<()> {
316 let color = self.parse_hex_color(&custom_color);
317 let clear_color = Rgb([255, 255, 255]);
318
319 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
320 let center_x = px + radius as i32;
321 let center_y = py + radius as i32;
322 draw_filled_circle_mut(img, (center_x, center_y), radius as i32, color);
323
324 if radius > thickness {
325 draw_filled_circle_mut(
326 img,
327 (center_x, center_y),
328 (radius - thickness) as i32,
329 clear_color,
330 );
331 }
332 };
333
334 self.draw_wrapper(x, y, radius * 2, radius * 2, reverse_print, draw_op)
335 }
336
337 fn draw_graphic_ellipse(
338 &mut self,
339 x: u32,
340 y: u32,
341 width: u32,
342 height: u32,
343 thickness: u32,
344 _color: char,
345 custom_color: Option<String>,
346 reverse_print: bool,
347 ) -> ZplResult<()> {
348 let color = self.parse_hex_color(&custom_color);
349 let clear_color = Rgb([255, 255, 255]);
350
351 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
352 let rx = (width / 2) as i32;
353 let ry = (height / 2) as i32;
354 let center_x = px + rx;
355 let center_y = py + ry;
356 draw_filled_ellipse_mut(img, (center_x, center_y), rx, ry, color);
357
358 let t = thickness as i32;
359 if rx > t && ry > t {
360 draw_filled_ellipse_mut(img, (center_x, center_y), rx - t, ry - t, clear_color);
361 }
362 };
363
364 self.draw_wrapper(x, y, width, height, reverse_print, draw_op)
365 }
366
367 fn draw_graphic_field(
368 &mut self,
369 x: u32,
370 y: u32,
371 width: u32,
372 height: u32,
373 data: Vec<u8>,
374 reverse_print: bool,
375 ) -> ZplResult<()> {
376 let draw_op = |img: &mut RgbImage, px: i32, py: i32| {
377 let row_bytes = width.div_ceil(8);
378 let (img_w, img_h) = (img.width() as i32, img.height() as i32);
379
380 for (row_idx, row_data) in data.chunks(row_bytes as usize).enumerate() {
381 let dy = py + row_idx as i32;
382 if dy < 0 || dy >= img_h || row_idx as u32 >= height {
383 continue;
384 }
385
386 for (byte_idx, &byte) in row_data.iter().enumerate() {
387 if byte == 0 {
388 continue;
389 }
390 let base_x = px + (byte_idx as i32 * 8);
391 for bit_idx in 0..8 {
392 let col_idx = byte_idx as u32 * 8 + bit_idx;
393 if col_idx >= width {
394 break;
395 }
396
397 if (byte & (0x80 >> bit_idx)) != 0 {
398 let dx = base_x + bit_idx as i32;
399 if dx >= 0 && dx < img_w {
400 img[(dx as u32, dy as u32)] = Rgb([0, 0, 0]);
401 }
402 }
403 }
404 }
405 }
406 };
407
408 self.draw_wrapper(x, y, width, height, reverse_print, draw_op)
409 }
410
411 fn draw_graphic_image_custom(
412 &mut self,
413 x: u32,
414 y: u32,
415 width: u32,
416 height: u32,
417 data: String,
418 ) -> ZplResult<()> {
419 let image_data = general_purpose::STANDARD
420 .decode(data.trim())
421 .map_err(|e| ZplError::ImageError(format!("Failed to decode base64: {}", e)))?;
422
423 let img = image::load_from_memory(&image_data)
424 .map_err(|e| ZplError::ImageError(format!("Failed to load image: {}", e)))?
425 .to_rgb8();
426
427 let (orig_w, orig_h) = img.dimensions();
428 let (target_w, target_h) = match (width, height) {
429 (0, 0) => (orig_w, orig_h),
430 (w, 0) => {
431 let h = (orig_h as f32 * (w as f32 / orig_w as f32)).round() as u32;
432 (w, h)
433 }
434 (0, h) => {
435 let w = (orig_w as f32 * (h as f32 / orig_h as f32)).round() as u32;
436 (w, h)
437 }
438 (w, h) => (w, h),
439 };
440
441 let resized_img = if target_w != orig_w || target_h != orig_h {
442 image::imageops::resize(
443 &img,
444 target_w,
445 target_h,
446 image::imageops::FilterType::Lanczos3,
447 )
448 } else {
449 img
450 };
451
452 overlay(&mut self.canvas, &resized_img, x as i64, y as i64);
453 Ok(())
454 }
455
456 fn draw_code128(
457 &mut self,
458 x: u32,
459 y: u32,
460 orientation: char,
461 height: u32,
462 module_width: u32,
463 interpretation_line: char,
464 interpretation_line_above: char,
465 _check_digit: char,
466 _mode: char,
467 data: String,
468 reverse_print: bool,
469 ) -> ZplResult<()> {
470 let (clean_data, hint_val) = if let Some(stripped) = data.strip_prefix(">:") {
471 (stripped, Some("B"))
472 } else if let Some(stripped) = data.strip_prefix(">;") {
473 (stripped, Some("C"))
474 } else if let Some(stripped) = data.strip_prefix(">9") {
475 (stripped, Some("A"))
476 } else {
477 (data.as_str(), None)
478 };
479
480 let hints = hint_val.map(|v| {
481 let mut h = HashMap::new();
482 h.insert(
483 EncodeHintType::FORCE_CODE_SET,
484 EncodeHintValue::ForceCodeSet(v.to_string()),
485 );
486 EncodeHints::from(h)
487 });
488
489 self.draw_1d_barcode(
490 x,
491 y,
492 orientation,
493 height,
494 module_width,
495 clean_data,
496 BarcodeFormat::CODE_128,
497 reverse_print,
498 interpretation_line,
499 interpretation_line_above,
500 hints,
501 )
502 }
503
504 fn draw_qr_code(
505 &mut self,
506 x: u32,
507 y: u32,
508 orientation: char,
509 _model: u32,
510 magnification: u32,
511 error_correction: char,
512 _mask: u32,
513 data: String,
514 reverse_print: bool,
515 ) -> ZplResult<()> {
516 let level = match error_correction {
517 'L' => "L",
518 'M' => "M",
519 'Q' => "Q",
520 'H' => "H",
521 _ => "M",
522 };
523
524 let mut hints = HashMap::new();
525 hints.insert(
526 EncodeHintType::ERROR_CORRECTION,
527 EncodeHintValue::ErrorCorrection(level.to_string()),
528 );
529 hints.insert(
530 EncodeHintType::MARGIN,
531 EncodeHintValue::Margin("0".to_owned()),
532 );
533 let hints: EncodeHints = hints.into();
534
535 let writer = MultiFormatWriter;
536 let bit_matrix = writer
537 .encode_with_hints(&data, &BarcodeFormat::QR_CODE, 0, 0, &hints)
538 .map_err(|e| ZplError::BackendError(format!("QR Generation Error: {}", e)))?;
539
540 let mag = max(magnification, 1);
541 let bw = bit_matrix.getWidth();
542 let bh = bit_matrix.getHeight();
543 let full_width = bw * mag;
544 let full_height = bh * mag;
545
546 let transform_rect = |lx: i32, ly: i32, w: u32, h: u32| -> Rect {
547 match orientation {
548 'N' => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
549 'R' => {
550 let new_x = full_height as i32 - (ly + h as i32);
551 let new_y = lx;
552 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
553 }
554 'I' => {
555 let new_x = full_width as i32 - (lx + w as i32);
556 let new_y = full_height as i32 - (ly + h as i32);
557 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(w, h)
558 }
559 'B' => {
560 let new_x = ly;
561 let new_y = full_width as i32 - (lx + w as i32);
562 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
563 }
564 _ => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
565 }
566 };
567
568 for gy in 0..bh {
569 for gx in 0..bw {
570 if bit_matrix.get(gx, gy) {
571 let rect = transform_rect((gx * mag) as i32, (gy * mag) as i32, mag, mag);
572 if reverse_print {
573 self.invert_rect(rect);
574 } else {
575 draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
576 }
577 }
578 }
579 }
580
581 Ok(())
582 }
583
584 fn draw_code39(
585 &mut self,
586 x: u32,
587 y: u32,
588 orientation: char,
589 _check_digit: char,
590 height: u32,
591 module_width: u32,
592 interpretation_line: char,
593 interpretation_line_above: char,
594 data: String,
595 reverse_print: bool,
596 ) -> ZplResult<()> {
597 self.draw_1d_barcode(
598 x,
599 y,
600 orientation,
601 height,
602 module_width,
603 &data,
604 BarcodeFormat::CODE_39,
605 reverse_print,
606 interpretation_line,
607 interpretation_line_above,
608 None,
609 )
610 }
611
612 fn finalize(&mut self) -> ZplResult<Vec<u8>> {
613 let mut bytes = Vec::new();
614 let mut cursor = std::io::Cursor::new(&mut bytes);
615 self.canvas
616 .write_to(&mut cursor, image::ImageFormat::Png)
617 .map_err(|e| ZplError::BackendError(format!("Failed to write PNG: {}", e)))?;
618 Ok(bytes)
619 }
620}
621
622impl PngBackend {
623 #[allow(clippy::too_many_arguments)]
624 fn draw_1d_barcode(
625 &mut self,
626 x: u32,
627 y: u32,
628 orientation: char,
629 height: u32,
630 module_width: u32,
631 data: &str,
632 format: BarcodeFormat,
633 reverse_print: bool,
634 interpretation_line: char,
635 interpretation_line_above: char,
636 hints: Option<EncodeHints>,
637 ) -> ZplResult<()> {
638 let writer = MultiFormatWriter;
639 let bit_matrix = if let Some(h) = hints {
640 writer.encode_with_hints(data, &format, 0, 0, &h)
641 } else {
642 writer.encode(data, &format, 0, 0)
643 }
644 .map_err(|e| ZplError::BackendError(format!("Barcode Generation Error: {}", e)))?;
645
646 let mw = max(module_width, 1);
647 let bh = height;
648 let bw = bit_matrix.getWidth() * mw;
649
650 let (full_w, full_h) = match orientation {
651 'N' | 'I' => (bw, bh),
652 'R' | 'B' => (bh, bw),
653 _ => (bw, bh),
654 };
655
656 let transform_rect = |lx: i32, ly: i32, w: u32, h: u32| -> Rect {
657 match orientation {
658 'N' => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
659 'R' => {
660 let new_x = bh as i32 - (ly + h as i32);
661 let new_y = lx;
662 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
663 }
664 'I' => {
665 let new_x = bw as i32 - (lx + w as i32);
666 let new_y = bh as i32 - (ly + h as i32);
667 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(w, h)
668 }
669 'B' => {
670 let new_x = ly;
671 let new_y = bw as i32 - (lx + w as i32);
672 Rect::at(x as i32 + new_x, y as i32 + new_y).of_size(h, w)
673 }
674 _ => Rect::at(x as i32 + lx, y as i32 + ly).of_size(w, h),
675 }
676 };
677
678 for gx in 0..bit_matrix.getWidth() {
679 if bit_matrix.get(gx, 0) {
680 let rect = transform_rect((gx * mw) as i32, 0, mw, bh);
681 if reverse_print {
682 self.invert_rect(rect);
683 } else {
684 draw_filled_rect_mut(&mut self.canvas, rect, Rgb([0, 0, 0]));
685 }
686 }
687 }
688
689 if interpretation_line == 'Y' {
690 let font_char = '0';
691 let text_h = 18;
692 let text_y = if interpretation_line_above == 'Y' {
693 y.saturating_sub(text_h)
694 } else {
695 y + full_h
696 } + 6;
697
698 let text_width = self.get_text_width(data, font_char, Some(text_h), None);
699 let text_x = if full_w > text_width {
700 x + (full_w - text_width) / 2
701 } else {
702 x
703 };
704
705 self.draw_text(
706 text_x,
707 text_y,
708 font_char,
709 Some(text_h),
710 None,
711 data.to_string(),
712 false,
713 None,
714 )?;
715 }
716
717 Ok(())
718 }
719}