1use std::cmp::max;
8use std::collections::{HashMap, HashSet};
9use std::io::Write;
10use std::sync::Arc;
11
12use ab_glyph::{Font, PxScale, ScaleFont};
13use base64::{Engine as _, engine::general_purpose};
14use flate2::Compression;
15use flate2::write::ZlibEncoder;
16use lopdf::content::{Content, Operation};
17use lopdf::{Document, FontData, Object, Stream, dictionary};
18use rxing::{
19 BarcodeFormat, EncodeHintType, EncodeHintValue, EncodeHints, MultiFormatWriter, Writer,
20};
21
22use crate::engine::{FontManager, ZplForgeBackend};
23use crate::{ZplError, ZplResult};
24
25const KAPPA: f64 = 0.5522847498;
27
28struct ImageXObject {
32 name: String,
33 data: Vec<u8>,
34 width: u32,
35 height: u32,
36}
37
38pub struct PdfNativeBackend {
47 width_dots: f64,
48 height_dots: f64,
49 width_pt: f64,
50 height_pt: f64,
51 resolution: f32,
52 scale: f64,
54 operations: Vec<Operation>,
55 font_manager: Option<Arc<FontManager>>,
56 images: Vec<ImageXObject>,
57 image_counter: usize,
58 used_fonts: HashSet<char>,
60 compression: Compression,
61}
62
63impl Default for PdfNativeBackend {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69impl PdfNativeBackend {
72 pub fn new() -> Self {
74 Self {
75 width_dots: 0.0,
76 height_dots: 0.0,
77 width_pt: 0.0,
78 height_pt: 0.0,
79 resolution: 0.0,
80 scale: 0.0,
81 operations: Vec::new(),
82 font_manager: None,
83 images: Vec::new(),
84 image_counter: 0,
85 used_fonts: HashSet::new(),
86 compression: Compression::default(),
87 }
88 }
89
90 pub fn with_compression(mut self, compression: Compression) -> Self {
92 self.compression = compression;
93 self
94 }
95}
96
97impl PdfNativeBackend {
100 #[inline]
104 fn d2pt(&self, dots: f64) -> f64 {
105 dots * self.scale
106 }
107
108 #[inline]
110 fn x_pt(&self, x: f64) -> f64 {
111 x * self.scale
112 }
113
114 #[inline]
117 fn y_pt_bottom(&self, y: f64, h: f64) -> f64 {
118 self.height_pt - (y + h) * self.scale
119 }
120
121 fn parse_hex_color_f64(color: &Option<String>) -> (f64, f64, f64) {
126 if let Some(hex) = color {
127 let hex = hex.trim_start_matches('#');
128 if hex.len() == 6 {
129 if let (Ok(r), Ok(g), Ok(b)) = (
130 u8::from_str_radix(&hex[0..2], 16),
131 u8::from_str_radix(&hex[2..4], 16),
132 u8::from_str_radix(&hex[4..6], 16),
133 ) {
134 return (r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0);
135 }
136 } else if hex.len() == 3
137 && let (Ok(r), Ok(g), Ok(b)) = (
138 u8::from_str_radix(&hex[0..1], 16),
139 u8::from_str_radix(&hex[1..2], 16),
140 u8::from_str_radix(&hex[2..3], 16),
141 )
142 {
143 return (
144 r as f64 * 17.0 / 255.0,
145 g as f64 * 17.0 / 255.0,
146 b as f64 * 17.0 / 255.0,
147 );
148 }
149 }
150 (0.0, 0.0, 0.0)
151 }
152
153 fn resolve_colors(
160 color: char,
161 custom_color: &Option<String>,
162 ) -> ((f64, f64, f64), (f64, f64, f64)) {
163 if custom_color.is_some() {
164 (Self::parse_hex_color_f64(custom_color), (1.0, 1.0, 1.0))
165 } else if color == 'B' {
166 ((0.0, 0.0, 0.0), (1.0, 1.0, 1.0))
167 } else {
168 ((1.0, 1.0, 1.0), (0.0, 0.0, 0.0))
169 }
170 }
171
172 fn op(&mut self, operator: &str, operands: Vec<Object>) {
175 self.operations.push(Operation::new(operator, operands));
176 }
177
178 fn set_fill_color(&mut self, r: f64, g: f64, b: f64) {
179 self.op("rg", vec![r.into(), g.into(), b.into()]);
180 }
181
182 fn save_state(&mut self) {
183 self.op("q", vec![]);
184 }
185
186 fn restore_state(&mut self) {
187 self.op("Q", vec![]);
188 }
189
190 fn begin_reverse(&mut self) {
194 self.save_state();
195 self.op("gs", vec!["GSDiff".into()]);
196 self.set_fill_color(1.0, 1.0, 1.0);
197 }
198
199 fn end_reverse(&mut self) {
200 self.restore_state();
201 }
202
203 fn push_rounded_rect_path(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
210 let r = r.min(w / 2.0).min(h / 2.0).max(0.0);
211 if r < 0.001 {
212 self.op("re", vec![x.into(), y.into(), w.into(), h.into()]);
213 return;
214 }
215 let kr = KAPPA * r;
216 self.op("m", vec![(x + r).into(), y.into()]);
218 self.op("l", vec![(x + w - r).into(), y.into()]);
219 self.op(
221 "c",
222 vec![
223 (x + w - r + kr).into(),
224 y.into(),
225 (x + w).into(),
226 (y + r - kr).into(),
227 (x + w).into(),
228 (y + r).into(),
229 ],
230 );
231 self.op("l", vec![(x + w).into(), (y + h - r).into()]);
233 self.op(
235 "c",
236 vec![
237 (x + w).into(),
238 (y + h - r + kr).into(),
239 (x + w - r + kr).into(),
240 (y + h).into(),
241 (x + w - r).into(),
242 (y + h).into(),
243 ],
244 );
245 self.op("l", vec![(x + r).into(), (y + h).into()]);
247 self.op(
249 "c",
250 vec![
251 (x + r - kr).into(),
252 (y + h).into(),
253 x.into(),
254 (y + h - r + kr).into(),
255 x.into(),
256 (y + h - r).into(),
257 ],
258 );
259 self.op("l", vec![x.into(), (y + r).into()]);
261 self.op(
263 "c",
264 vec![
265 x.into(),
266 (y + r - kr).into(),
267 (x + r - kr).into(),
268 y.into(),
269 (x + r).into(),
270 y.into(),
271 ],
272 );
273 self.op("h", vec![]);
274 }
275
276 fn push_ellipse_path(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) {
279 let kx = KAPPA * rx;
280 let ky = KAPPA * ry;
281 self.op("m", vec![(cx + rx).into(), cy.into()]);
283 self.op(
285 "c",
286 vec![
287 (cx + rx).into(),
288 (cy + ky).into(),
289 (cx + kx).into(),
290 (cy + ry).into(),
291 cx.into(),
292 (cy + ry).into(),
293 ],
294 );
295 self.op(
297 "c",
298 vec![
299 (cx - kx).into(),
300 (cy + ry).into(),
301 (cx - rx).into(),
302 (cy + ky).into(),
303 (cx - rx).into(),
304 cy.into(),
305 ],
306 );
307 self.op(
309 "c",
310 vec![
311 (cx - rx).into(),
312 (cy - ky).into(),
313 (cx - kx).into(),
314 (cy - ry).into(),
315 cx.into(),
316 (cy - ry).into(),
317 ],
318 );
319 self.op(
321 "c",
322 vec![
323 (cx + kx).into(),
324 (cy - ry).into(),
325 (cx + rx).into(),
326 (cy - ky).into(),
327 (cx + rx).into(),
328 cy.into(),
329 ],
330 );
331 self.op("h", vec![]);
332 }
333
334 fn get_font_arc(&self, font_char: char) -> ZplResult<&ab_glyph::FontArc> {
337 let fm = self
338 .font_manager
339 .as_ref()
340 .ok_or_else(|| ZplError::FontError("Font manager not initialized".into()))?;
341 fm.get_font(&font_char.to_string())
342 .or_else(|| fm.get_font("0"))
343 .ok_or_else(|| ZplError::FontError(format!("Font not found: {}", font_char)))
344 }
345
346 fn get_text_width(
347 &self,
348 text: &str,
349 font_char: char,
350 height: Option<u32>,
351 width: Option<u32>,
352 ) -> u32 {
353 let font = match self.get_font_arc(font_char) {
354 Ok(f) => f,
355 Err(_) => return 0,
356 };
357 let scale_y = height.unwrap_or(9) as f32;
358 let scale_x = width.unwrap_or(scale_y as u32) as f32;
359 let scale = PxScale {
360 x: scale_x,
361 y: scale_y,
362 };
363 let scaled = font.as_scaled(scale);
364 let mut w = 0.0_f32;
365 let mut last_glyph = None;
366 for c in text.chars() {
367 let gid = font.glyph_id(c);
368 if let Some(prev) = last_glyph {
369 w += scaled.kern(prev, gid);
370 }
371 w += scaled.h_advance(gid);
372 last_glyph = Some(gid);
373 }
374 w.ceil() as u32
375 }
376
377 fn embed_rgb_image(
382 &mut self,
383 x_dots: f64,
384 y_dots: f64,
385 img_w: u32,
386 img_h: u32,
387 rgb_data: Vec<u8>,
388 ) {
389 let name = format!("Im{}", self.image_counter);
390 self.image_counter += 1;
391
392 let px = self.x_pt(x_dots);
393 let py = self.y_pt_bottom(y_dots, img_h as f64);
394 let pw = self.d2pt(img_w as f64);
395 let ph = self.d2pt(img_h as f64);
396
397 self.save_state();
398 self.op(
399 "cm",
400 vec![
401 pw.into(),
402 0.into(),
403 0.into(),
404 ph.into(),
405 px.into(),
406 py.into(),
407 ],
408 );
409 self.op("Do", vec![Object::Name(name.as_bytes().to_vec())]);
410 self.restore_state();
411
412 self.images.push(ImageXObject {
413 name,
414 data: rgb_data,
415 width: img_w,
416 height: img_h,
417 });
418 }
419
420 #[allow(clippy::too_many_arguments)]
427 fn transform_1d_bar(
428 orientation: char,
429 base_x: u32,
430 base_y: u32,
431 lx: i32,
432 ly: i32,
433 w: u32,
434 h: u32,
435 bw: u32,
436 bh: u32,
437 ) -> (i32, i32, u32, u32) {
438 match orientation {
439 'R' => {
440 let nx = bh as i32 - (ly + h as i32);
441 let ny = lx;
442 (base_x as i32 + nx, base_y as i32 + ny, h, w)
443 }
444 'I' => {
445 let nx = bw as i32 - (lx + w as i32);
446 let ny = bh as i32 - (ly + h as i32);
447 (base_x as i32 + nx, base_y as i32 + ny, w, h)
448 }
449 'B' => {
450 let nx = ly;
451 let ny = bw as i32 - (lx + w as i32);
452 (base_x as i32 + nx, base_y as i32 + ny, h, w)
453 }
454 _ => (base_x as i32 + lx, base_y as i32 + ly, w, h),
455 }
456 }
457
458 #[allow(clippy::too_many_arguments)]
460 fn transform_2d_cell(
461 orientation: char,
462 base_x: u32,
463 base_y: u32,
464 lx: i32,
465 ly: i32,
466 w: u32,
467 h: u32,
468 full_w: u32,
469 full_h: u32,
470 ) -> (i32, i32, u32, u32) {
471 match orientation {
472 'R' => {
473 let nx = full_h as i32 - (ly + h as i32);
474 let ny = lx;
475 (base_x as i32 + nx, base_y as i32 + ny, h, w)
476 }
477 'I' => {
478 let nx = full_w as i32 - (lx + w as i32);
479 let ny = full_h as i32 - (ly + h as i32);
480 (base_x as i32 + nx, base_y as i32 + ny, w, h)
481 }
482 'B' => {
483 let nx = ly;
484 let ny = full_w as i32 - (lx + w as i32);
485 (base_x as i32 + nx, base_y as i32 + ny, h, w)
486 }
487 _ => (base_x as i32 + lx, base_y as i32 + ly, w, h),
488 }
489 }
490
491 #[allow(clippy::too_many_arguments)]
494 fn draw_1d_barcode(
495 &mut self,
496 x: u32,
497 y: u32,
498 orientation: char,
499 height: u32,
500 module_width: u32,
501 data: &str,
502 format: BarcodeFormat,
503 reverse_print: bool,
504 interpretation_line: char,
505 interpretation_line_above: char,
506 hints: Option<EncodeHints>,
507 ) -> ZplResult<()> {
508 let writer = MultiFormatWriter;
509 let bit_matrix = if let Some(h) = hints {
510 writer.encode_with_hints(data, &format, 0, 0, &h)
511 } else {
512 writer.encode(data, &format, 0, 0)
513 }
514 .map_err(|e| ZplError::BackendError(format!("Barcode Generation Error: {}", e)))?;
515
516 let mw = max(module_width, 1);
517 let bh = height;
518 let bw = bit_matrix.getWidth() * mw;
519
520 let (full_w, full_h) = match orientation {
521 'R' | 'B' => (bh, bw),
522 _ => (bw, bh),
523 };
524
525 if reverse_print {
527 self.begin_reverse();
528 } else {
529 self.save_state();
530 self.set_fill_color(0.0, 0.0, 0.0);
531 }
532
533 for gx in 0..bit_matrix.getWidth() {
534 if bit_matrix.get(gx, 0) {
535 let (rx, ry, rw, rh) =
536 Self::transform_1d_bar(orientation, x, y, (gx * mw) as i32, 0, mw, bh, bw, bh);
537 let px = self.d2pt(rx as f64);
538 let py = self.height_pt - self.d2pt(ry as f64 + rh as f64);
539 let pw = self.d2pt(rw as f64);
540 let ph = self.d2pt(rh as f64);
541 self.op("re", vec![px.into(), py.into(), pw.into(), ph.into()]);
542 }
543 }
544 self.op("f", vec![]);
545
546 if reverse_print {
547 self.end_reverse();
548 } else {
549 self.restore_state();
550 }
551
552 if interpretation_line == 'Y' {
554 let font_char = '0';
555 let text_h: u32 = 18;
556 let text_y = if interpretation_line_above == 'Y' {
557 y.saturating_sub(text_h)
558 } else {
559 y + full_h
560 } + 6;
561
562 let text_width = self.get_text_width(data, font_char, Some(text_h), None);
563 let text_x = if full_w > text_width {
564 x + (full_w - text_width) / 2
565 } else {
566 x
567 };
568
569 self.draw_text(
570 text_x,
571 text_y,
572 font_char,
573 Some(text_h),
574 None,
575 data,
576 false,
577 None,
578 )?;
579 }
580
581 Ok(())
582 }
583}
584
585impl ZplForgeBackend for PdfNativeBackend {
588 fn setup_page(&mut self, width: f64, height: f64, resolution: f32) {
589 let dpi = if resolution == 0.0 { 203.2 } else { resolution };
590 self.width_dots = width;
591 self.height_dots = height;
592 self.resolution = dpi;
593 self.scale = 72.0 / dpi as f64;
594 self.width_pt = width * self.scale;
595 self.height_pt = height * self.scale;
596 }
597
598 fn setup_font_manager(&mut self, font_manager: &FontManager) {
599 self.font_manager = Some(Arc::new(font_manager.clone()));
600 }
601
602 fn draw_text(
605 &mut self,
606 x: u32,
607 y: u32,
608 font: char,
609 height: Option<u32>,
610 width: Option<u32>,
611 text: &str,
612 reverse_print: bool,
613 color: Option<String>,
614 ) -> ZplResult<()> {
615 if text.is_empty() {
616 return Ok(());
617 }
618
619 let scale_y_dots = height.unwrap_or(9) as f32;
620 let scale_x_dots = width.unwrap_or(scale_y_dots as u32) as f32;
621 let px_scale = PxScale {
622 x: scale_x_dots,
623 y: scale_y_dots,
624 };
625
626 let ascent_dots = {
628 let font_arc = self.get_font_arc(font)?;
629 font_arc.as_scaled(px_scale).ascent()
630 };
631
632 self.used_fonts.insert(font);
633
634 let scale_x_pt = self.d2pt(scale_x_dots as f64);
635 let scale_y_pt = self.d2pt(scale_y_dots as f64);
636 let tx = self.x_pt(x as f64);
637 let ty = self.height_pt - (y as f64 + ascent_dots as f64) * self.scale;
639
640 if reverse_print {
641 self.begin_reverse();
642 } else {
643 let (r, g, b) = Self::parse_hex_color_f64(&color);
644 self.save_state();
645 self.set_fill_color(r, g, b);
646 }
647
648 self.op("BT", vec![]);
649 self.op(
650 "Tm",
651 vec![
652 scale_x_pt.into(),
653 0.into(),
654 0.into(),
655 scale_y_pt.into(),
656 tx.into(),
657 ty.into(),
658 ],
659 );
660 let font_resource_name = format!("F_{}", font);
661 self.op(
662 "Tf",
663 vec![
664 Object::Name(font_resource_name.into_bytes()),
665 Object::Real(1.0),
666 ],
667 );
668 self.op("Tj", vec![Object::string_literal(text.as_bytes().to_vec())]);
669 self.op("ET", vec![]);
670
671 if reverse_print {
672 self.end_reverse();
673 } else {
674 self.restore_state();
675 }
676
677 Ok(())
678 }
679
680 fn draw_graphic_box(
683 &mut self,
684 x: u32,
685 y: u32,
686 width: u32,
687 height: u32,
688 thickness: u32,
689 color: char,
690 custom_color: Option<String>,
691 rounding: u32,
692 reverse_print: bool,
693 ) -> ZplResult<()> {
694 let w = max(width, 1) as f64;
695 let h = max(height, 1) as f64;
696 let t = thickness as f64;
697 let r_dots = rounding as f64 * 8.0;
698
699 let (draw_color, clear_color) = Self::resolve_colors(color, &custom_color);
700
701 let bx = self.x_pt(x as f64);
702 let by = self.y_pt_bottom(y as f64, h);
703 let bw = self.d2pt(w);
704 let bh = self.d2pt(h);
705 let br = self.d2pt(r_dots);
706
707 if reverse_print {
708 self.begin_reverse();
712 self.push_rounded_rect_path(bx, by, bw, bh, br);
713 self.op("f", vec![]);
714 if t * 2.0 < w && t * 2.0 < h {
715 let tp = self.d2pt(t);
716 let inner_r = self.d2pt((r_dots - t).max(0.0));
717 self.push_rounded_rect_path(
718 bx + tp,
719 by + tp,
720 bw - tp * 2.0,
721 bh - tp * 2.0,
722 inner_r,
723 );
724 self.op("f", vec![]);
725 }
726 self.end_reverse();
727 } else {
728 self.save_state();
729 let (r, g, b) = draw_color;
730 self.set_fill_color(r, g, b);
731 self.push_rounded_rect_path(bx, by, bw, bh, br);
732 self.op("f", vec![]);
733
734 if t * 2.0 < w && t * 2.0 < h {
735 let (cr, cg, cb) = clear_color;
736 self.set_fill_color(cr, cg, cb);
737 let tp = self.d2pt(t);
738 let inner_r = self.d2pt((r_dots - t).max(0.0));
739 self.push_rounded_rect_path(
740 bx + tp,
741 by + tp,
742 bw - tp * 2.0,
743 bh - tp * 2.0,
744 inner_r,
745 );
746 self.op("f", vec![]);
747 }
748 self.restore_state();
749 }
750
751 Ok(())
752 }
753
754 fn draw_graphic_circle(
757 &mut self,
758 x: u32,
759 y: u32,
760 radius: u32,
761 thickness: u32,
762 _color: char,
763 custom_color: Option<String>,
764 reverse_print: bool,
765 ) -> ZplResult<()> {
766 let (draw_color, _) = Self::resolve_colors('B', &custom_color);
767
768 let r_pt = self.d2pt(radius as f64);
769 let cx_pt = self.x_pt(x as f64) + r_pt;
771 let cy_pt = self.height_pt - (y as f64 + radius as f64) * self.scale;
772
773 if reverse_print {
774 self.begin_reverse();
775 self.push_ellipse_path(cx_pt, cy_pt, r_pt, r_pt);
776 self.op("f", vec![]);
777 if radius > thickness {
778 let inner_r = self.d2pt((radius - thickness) as f64);
779 self.push_ellipse_path(cx_pt, cy_pt, inner_r, inner_r);
780 self.op("f", vec![]);
781 }
782 self.end_reverse();
783 } else {
784 self.save_state();
785 let (r, g, b) = draw_color;
786 self.set_fill_color(r, g, b);
787 self.push_ellipse_path(cx_pt, cy_pt, r_pt, r_pt);
788 self.op("f", vec![]);
789
790 if radius > thickness {
791 self.set_fill_color(1.0, 1.0, 1.0);
792 let inner_r = self.d2pt((radius - thickness) as f64);
793 self.push_ellipse_path(cx_pt, cy_pt, inner_r, inner_r);
794 self.op("f", vec![]);
795 }
796 self.restore_state();
797 }
798
799 Ok(())
800 }
801
802 fn draw_graphic_ellipse(
805 &mut self,
806 x: u32,
807 y: u32,
808 width: u32,
809 height: u32,
810 thickness: u32,
811 _color: char,
812 custom_color: Option<String>,
813 reverse_print: bool,
814 ) -> ZplResult<()> {
815 let (draw_color, _) = Self::resolve_colors('B', &custom_color);
816
817 let rx_pt = self.d2pt(width as f64 / 2.0);
818 let ry_pt = self.d2pt(height as f64 / 2.0);
819 let cx_pt = self.x_pt(x as f64) + rx_pt;
820 let cy_pt = self.height_pt - (y as f64 + height as f64 / 2.0) * self.scale;
821
822 let t = thickness as f64;
823
824 if reverse_print {
825 self.begin_reverse();
826 self.push_ellipse_path(cx_pt, cy_pt, rx_pt, ry_pt);
827 self.op("f", vec![]);
828 if (width as f64 / 2.0) > t && (height as f64 / 2.0) > t {
829 let irx = self.d2pt(width as f64 / 2.0 - t);
830 let iry = self.d2pt(height as f64 / 2.0 - t);
831 self.push_ellipse_path(cx_pt, cy_pt, irx, iry);
832 self.op("f", vec![]);
833 }
834 self.end_reverse();
835 } else {
836 self.save_state();
837 let (r, g, b) = draw_color;
838 self.set_fill_color(r, g, b);
839 self.push_ellipse_path(cx_pt, cy_pt, rx_pt, ry_pt);
840 self.op("f", vec![]);
841
842 if (width as f64 / 2.0) > t && (height as f64 / 2.0) > t {
843 self.set_fill_color(1.0, 1.0, 1.0);
844 let irx = self.d2pt(width as f64 / 2.0 - t);
845 let iry = self.d2pt(height as f64 / 2.0 - t);
846 self.push_ellipse_path(cx_pt, cy_pt, irx, iry);
847 self.op("f", vec![]);
848 }
849 self.restore_state();
850 }
851
852 Ok(())
853 }
854
855 fn draw_graphic_field(
858 &mut self,
859 x: u32,
860 y: u32,
861 width: u32,
862 height: u32,
863 data: &[u8],
864 _reverse_print: bool,
865 ) -> ZplResult<()> {
866 if width == 0 || height == 0 {
867 return Ok(());
868 }
869
870 let row_bytes = width.div_ceil(8) as usize;
871 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
872
873 for row_idx in 0..height {
874 let row_start = row_idx as usize * row_bytes;
875 let row_end = (row_start + row_bytes).min(data.len());
876 let row_data = if row_start < data.len() {
877 &data[row_start..row_end]
878 } else {
879 &[]
880 };
881
882 for col in 0..width {
883 let byte_idx = (col / 8) as usize;
884 let bit_idx = 7 - (col % 8);
885 let is_set =
886 byte_idx < row_data.len() && (row_data[byte_idx] & (1 << bit_idx)) != 0;
887 if is_set {
888 rgb_data.extend_from_slice(&[0, 0, 0]);
889 } else {
890 rgb_data.extend_from_slice(&[255, 255, 255]);
891 }
892 }
893 }
894
895 self.embed_rgb_image(x as f64, y as f64, width, height, rgb_data);
896 Ok(())
897 }
898
899 fn draw_graphic_image_custom(
902 &mut self,
903 x: u32,
904 y: u32,
905 width: u32,
906 height: u32,
907 data: &str,
908 ) -> ZplResult<()> {
909 let image_data = general_purpose::STANDARD
910 .decode(data.trim())
911 .map_err(|e| ZplError::ImageError(format!("Failed to decode base64: {}", e)))?;
912
913 let img = image::load_from_memory(&image_data)
914 .map_err(|e| ZplError::ImageError(format!("Failed to load image: {}", e)))?
915 .to_rgb8();
916
917 let (orig_w, orig_h) = img.dimensions();
918 let (target_w, target_h) = match (width, height) {
919 (0, 0) => (orig_w, orig_h),
920 (w, 0) => {
921 let h = (orig_h as f32 * (w as f32 / orig_w as f32)).round() as u32;
922 (w, h)
923 }
924 (0, h) => {
925 let w = (orig_w as f32 * (h as f32 / orig_h as f32)).round() as u32;
926 (w, h)
927 }
928 (w, h) => (w, h),
929 };
930
931 let final_img = if target_w != orig_w || target_h != orig_h {
932 image::imageops::resize(
933 &img,
934 target_w,
935 target_h,
936 image::imageops::FilterType::Lanczos3,
937 )
938 } else {
939 img
940 };
941
942 let rgb_data = final_img.into_raw();
943 self.embed_rgb_image(x as f64, y as f64, target_w, target_h, rgb_data);
944 Ok(())
945 }
946
947 fn draw_code128(
950 &mut self,
951 x: u32,
952 y: u32,
953 orientation: char,
954 height: u32,
955 module_width: u32,
956 interpretation_line: char,
957 interpretation_line_above: char,
958 _check_digit: char,
959 _mode: char,
960 data: &str,
961 reverse_print: bool,
962 ) -> ZplResult<()> {
963 let (clean_data, hint_val) = if let Some(stripped) = data.strip_prefix(">:") {
964 (stripped, Some("B"))
965 } else if let Some(stripped) = data.strip_prefix(">;") {
966 (stripped, Some("C"))
967 } else if let Some(stripped) = data.strip_prefix(">9") {
968 (stripped, Some("A"))
969 } else {
970 (data, None)
971 };
972
973 let hints = hint_val.map(|v| {
974 let mut h = HashMap::new();
975 h.insert(
976 EncodeHintType::FORCE_CODE_SET,
977 EncodeHintValue::ForceCodeSet(v.to_string()),
978 );
979 EncodeHints::from(h)
980 });
981
982 self.draw_1d_barcode(
983 x,
984 y,
985 orientation,
986 height,
987 module_width,
988 clean_data,
989 BarcodeFormat::CODE_128,
990 reverse_print,
991 interpretation_line,
992 interpretation_line_above,
993 hints,
994 )
995 }
996
997 fn draw_qr_code(
1000 &mut self,
1001 x: u32,
1002 y: u32,
1003 orientation: char,
1004 _model: u32,
1005 magnification: u32,
1006 error_correction: char,
1007 _mask: u32,
1008 data: &str,
1009 reverse_print: bool,
1010 ) -> ZplResult<()> {
1011 let level = match error_correction {
1012 'L' => "L",
1013 'M' => "M",
1014 'Q' => "Q",
1015 'H' => "H",
1016 _ => "M",
1017 };
1018
1019 let mut hints = HashMap::new();
1020 hints.insert(
1021 EncodeHintType::ERROR_CORRECTION,
1022 EncodeHintValue::ErrorCorrection(level.to_string()),
1023 );
1024 hints.insert(
1025 EncodeHintType::MARGIN,
1026 EncodeHintValue::Margin("0".to_owned()),
1027 );
1028 let hints: EncodeHints = hints.into();
1029
1030 let writer = MultiFormatWriter;
1031 let bit_matrix = writer
1032 .encode_with_hints(data, &BarcodeFormat::QR_CODE, 0, 0, &hints)
1033 .map_err(|e| ZplError::BackendError(format!("QR Generation Error: {}", e)))?;
1034
1035 let mag = max(magnification, 1);
1036 let bw = bit_matrix.getWidth();
1037 let bh = bit_matrix.getHeight();
1038 let full_w = bw * mag;
1039 let full_h = bh * mag;
1040
1041 if reverse_print {
1042 self.begin_reverse();
1043 } else {
1044 self.save_state();
1045 self.set_fill_color(0.0, 0.0, 0.0);
1046 }
1047
1048 for gy in 0..bh {
1049 for gx in 0..bw {
1050 if bit_matrix.get(gx, gy) {
1051 let (rx, ry, rw, rh) = Self::transform_2d_cell(
1052 orientation,
1053 x,
1054 y,
1055 (gx * mag) as i32,
1056 (gy * mag) as i32,
1057 mag,
1058 mag,
1059 full_w,
1060 full_h,
1061 );
1062 let px = self.d2pt(rx as f64);
1063 let py = self.height_pt - self.d2pt(ry as f64 + rh as f64);
1064 let pw = self.d2pt(rw as f64);
1065 let ph = self.d2pt(rh as f64);
1066 self.op("re", vec![px.into(), py.into(), pw.into(), ph.into()]);
1067 }
1068 }
1069 }
1070 self.op("f", vec![]);
1071
1072 if reverse_print {
1073 self.end_reverse();
1074 } else {
1075 self.restore_state();
1076 }
1077
1078 Ok(())
1079 }
1080
1081 fn draw_code39(
1084 &mut self,
1085 x: u32,
1086 y: u32,
1087 orientation: char,
1088 _check_digit: char,
1089 height: u32,
1090 module_width: u32,
1091 interpretation_line: char,
1092 interpretation_line_above: char,
1093 data: &str,
1094 reverse_print: bool,
1095 ) -> ZplResult<()> {
1096 self.draw_1d_barcode(
1097 x,
1098 y,
1099 orientation,
1100 height,
1101 module_width,
1102 data,
1103 BarcodeFormat::CODE_39,
1104 reverse_print,
1105 interpretation_line,
1106 interpretation_line_above,
1107 None,
1108 )
1109 }
1110
1111 fn finalize(&mut self) -> ZplResult<Vec<u8>> {
1114 let mut doc = Document::with_version("1.5");
1115 let pages_id = doc.new_object_id();
1116
1117 let default_font_bytes: &[u8] = include_bytes!("../assets/Oswald-Regular.ttf");
1119 let mut font_dict = lopdf::Dictionary::new();
1120 let mut embedded_fonts: HashSet<String> = HashSet::new();
1121
1122 for font_char in &self.used_fonts {
1123 let font_key = font_char.to_string();
1124 let resource_name = format!("F_{}", font_char);
1125
1126 let font_name = self
1128 .font_manager
1129 .as_ref()
1130 .and_then(|fm| fm.get_font_name(&font_key).map(|s| s.to_string()));
1131
1132 let actual_name = font_name.unwrap_or_else(|| "Oswald".to_string());
1133
1134 if embedded_fonts.contains(&actual_name) {
1137 }
1140
1141 let raw_bytes = self
1142 .font_manager
1143 .as_ref()
1144 .and_then(|fm| fm.get_font_bytes(&font_key))
1145 .unwrap_or(default_font_bytes);
1146
1147 let font_data = FontData::new(raw_bytes, actual_name.clone());
1148 let font_id = doc
1149 .add_font(font_data)
1150 .map_err(|e| ZplError::BackendError(format!("Failed to embed font: {}", e)))?;
1151
1152 font_dict.set(resource_name.as_str(), font_id);
1153 embedded_fonts.insert(actual_name);
1154 }
1155
1156 let mut xobject_dict = lopdf::Dictionary::new();
1158 for img in &self.images {
1159 let mut encoder = ZlibEncoder::new(Vec::new(), self.compression);
1160 encoder
1161 .write_all(&img.data)
1162 .map_err(|e| ZplError::BackendError(e.to_string()))?;
1163 let compressed = encoder
1164 .finish()
1165 .map_err(|e| ZplError::BackendError(e.to_string()))?;
1166
1167 let img_stream = Stream::new(
1168 dictionary! {
1169 "Type" => "XObject",
1170 "Subtype" => "Image",
1171 "Width" => img.width as i64,
1172 "Height" => img.height as i64,
1173 "ColorSpace" => "DeviceRGB",
1174 "BitsPerComponent" => 8,
1175 "Filter" => "FlateDecode",
1176 },
1177 compressed,
1178 );
1179 let img_id = doc.add_object(img_stream);
1180 xobject_dict.set(img.name.as_str(), img_id);
1181 }
1182
1183 let mut gs_dict = lopdf::Dictionary::new();
1185 let gs_diff = doc.add_object(dictionary! {
1186 "Type" => "ExtGState",
1187 "BM" => "Difference",
1188 });
1189 gs_dict.set("GSDiff", gs_diff);
1190
1191 let gs_normal = doc.add_object(dictionary! {
1192 "Type" => "ExtGState",
1193 "BM" => "Normal",
1194 });
1195 gs_dict.set("GSNormal", gs_normal);
1196
1197 let resources_id = doc.add_object(dictionary! {
1199 "Font" => lopdf::Object::Dictionary(font_dict),
1200 "XObject" => lopdf::Object::Dictionary(xobject_dict),
1201 "ExtGState" => lopdf::Object::Dictionary(gs_dict),
1202 });
1203
1204 let content = Content {
1206 operations: std::mem::take(&mut self.operations),
1207 };
1208 let content_bytes = content
1209 .encode()
1210 .map_err(|e| ZplError::BackendError(format!("Failed to encode content: {}", e)))?;
1211 let content_id = doc.add_object(Stream::new(dictionary! {}, content_bytes));
1212
1213 let page_id = doc.add_object(dictionary! {
1215 "Type" => "Page",
1216 "Parent" => pages_id,
1217 "MediaBox" => vec![
1218 0.into(),
1219 0.into(),
1220 Object::Real(self.width_pt as f32),
1221 Object::Real(self.height_pt as f32),
1222 ],
1223 "Contents" => content_id,
1224 "Resources" => resources_id,
1225 });
1226
1227 let pages_dict = dictionary! {
1229 "Type" => "Pages",
1230 "Count" => 1_i64,
1231 "Kids" => vec![page_id.into()],
1232 };
1233 doc.objects.insert(pages_id, Object::Dictionary(pages_dict));
1234
1235 let catalog_id = doc.add_object(dictionary! {
1237 "Type" => "Catalog",
1238 "Pages" => pages_id,
1239 });
1240 doc.trailer.set("Root", catalog_id);
1241 doc.compress();
1242
1243 let mut buf = std::io::BufWriter::new(Vec::new());
1245 doc.save_to(&mut buf)
1246 .map_err(|e| ZplError::BackendError(format!("Failed to save PDF: {}", e)))?;
1247 buf.into_inner()
1248 .map_err(|e| ZplError::BackendError(format!("Failed to flush: {}", e)))
1249 }
1250}