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