1use std::cmp::max;
7use std::collections::{HashMap, HashSet};
8use std::io::Write;
9use std::sync::Arc;
10
11use ab_glyph::{Font, FontArc};
12use base64::{Engine as _, engine::general_purpose};
13use flate2::Compression;
14use flate2::write::ZlibEncoder;
15use lopdf::{Document, FontData, Object, Stream, dictionary};
16use rxing::common::BitMatrix;
17use rxing::datamatrix::encoder::SymbolShapeHint;
18use rxing::{BarcodeFormat, EncodeHintType, EncodeHintValue, EncodeHints};
19
20use super::{barcode_1d_format, barcode_cache, symbology};
21use crate::engine::{Barcode1DKind, FontManager, ZplForgeBackend};
22use crate::{ZplError, ZplResult};
23
24const KAPPA: f64 = 0.5522847498;
26
27const CP1252_80_9F: [char; 32] = [
35 '\u{20AC}', '\u{0}', '\u{201A}', '\u{0192}', '\u{201E}', '\u{2026}', '\u{2020}', '\u{2021}',
36 '\u{02C6}', '\u{2030}', '\u{0160}', '\u{2039}', '\u{0152}', '\u{0}', '\u{017D}', '\u{0}',
37 '\u{0}', '\u{2018}', '\u{2019}', '\u{201C}', '\u{201D}', '\u{2022}', '\u{2013}', '\u{2014}',
38 '\u{02DC}', '\u{2122}', '\u{0161}', '\u{203A}', '\u{0153}', '\u{0}', '\u{017E}', '\u{0178}',
39];
40
41fn char_to_winansi(c: char) -> Option<u8> {
43 let cp = c as u32;
44 match cp {
45 0x20..=0x7E => Some(cp as u8),
46 0xA0..=0xFF => Some(cp as u8),
48 _ => CP1252_80_9F
49 .iter()
50 .position(|&m| m == c && m != '\u{0}')
51 .map(|i| 0x80 + i as u8),
52 }
53}
54
55fn winansi_to_char(code: u8) -> Option<char> {
57 match code {
58 0x20..=0x7E => Some(code as char),
59 0xA0..=0xFF => Some(code as char),
60 0x80..=0x9F => {
61 let c = CP1252_80_9F[(code - 0x80) as usize];
62 (c != '\u{0}').then_some(c)
63 }
64 _ => None,
65 }
66}
67
68fn build_tounicode_cmap() -> Vec<u8> {
70 let mut s = String::with_capacity(4096);
71 s.push_str(
72 "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n\
73 /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n\
74 /CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n\
75 1 begincodespacerange\n<20> <FF>\nendcodespacerange\n",
76 );
77 let entries: Vec<(u8, char)> = (0x20..=0xFFu32)
78 .filter_map(|c| winansi_to_char(c as u8).map(|ch| (c as u8, ch)))
79 .collect();
80 for chunk in entries.chunks(100) {
81 s.push_str(&format!("{} beginbfchar\n", chunk.len()));
82 for (code, ch) in chunk {
83 s.push_str(&format!("<{:02X}> <{:04X}>\n", code, *ch as u32));
84 }
85 s.push_str("endbfchar\n");
86 }
87 s.push_str("endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend\n");
88 s.into_bytes()
89}
90
91struct ImageXObject {
95 name: String,
96 data: Vec<u8>,
97 width: u32,
98 height: u32,
99 is_mask: bool,
101}
102
103pub struct PdfNativeBackend {
112 width_dots: f64,
113 height_dots: f64,
114 width_pt: f64,
115 height_pt: f64,
116 resolution: f32,
117 scale: f64,
119 content: Vec<u8>,
121 finished_pages: Vec<Vec<u8>>,
123 font_manager: Option<Arc<FontManager>>,
124 images: Vec<ImageXObject>,
125 image_counter: usize,
126 used_fonts: HashSet<char>,
128 compression: Compression,
129 title: Option<String>,
131 #[allow(clippy::type_complexity)]
135 backdrop_rects: Vec<(f64, f64, f64, f64, (f64, f64, f64))>,
136}
137
138impl Default for PdfNativeBackend {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl PdfNativeBackend {
147 pub fn new() -> Self {
149 Self {
150 width_dots: 0.0,
151 height_dots: 0.0,
152 width_pt: 0.0,
153 height_pt: 0.0,
154 resolution: 0.0,
155 scale: 0.0,
156 content: Vec::with_capacity(4096),
157 finished_pages: Vec::new(),
158 font_manager: None,
159 images: Vec::new(),
160 image_counter: 0,
161 used_fonts: HashSet::new(),
162 compression: Compression::default(),
163 title: None,
164 backdrop_rects: Vec::new(),
165 }
166 }
167
168 pub fn with_compression(mut self, compression: Compression) -> Self {
170 self.compression = compression;
171 self
172 }
173
174 pub fn with_title(mut self, title: impl Into<String>) -> Self {
176 self.title = Some(title.into());
177 self
178 }
179}
180
181impl PdfNativeBackend {
184 #[inline]
188 fn d2pt(&self, dots: f64) -> f64 {
189 dots * self.scale
190 }
191
192 #[inline]
194 fn x_pt(&self, x: f64) -> f64 {
195 x * self.scale
196 }
197
198 #[inline]
201 fn y_pt_bottom(&self, y: f64, h: f64) -> f64 {
202 self.height_pt - (y + h) * self.scale
203 }
204
205 fn parse_hex_color_f64(color: &Option<String>) -> (f64, f64, f64) {
210 if let Some(hex) = color {
211 let hex = hex.trim_start_matches('#');
212 if hex.len() == 6 {
213 if let (Ok(r), Ok(g), Ok(b)) = (
214 u8::from_str_radix(&hex[0..2], 16),
215 u8::from_str_radix(&hex[2..4], 16),
216 u8::from_str_radix(&hex[4..6], 16),
217 ) {
218 return (r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0);
219 }
220 } else if hex.len() == 3
221 && let (Ok(r), Ok(g), Ok(b)) = (
222 u8::from_str_radix(&hex[0..1], 16),
223 u8::from_str_radix(&hex[1..2], 16),
224 u8::from_str_radix(&hex[2..3], 16),
225 )
226 {
227 return (
228 r as f64 * 17.0 / 255.0,
229 g as f64 * 17.0 / 255.0,
230 b as f64 * 17.0 / 255.0,
231 );
232 }
233 }
234 (0.0, 0.0, 0.0)
235 }
236
237 fn resolve_colors(
244 color: char,
245 custom_color: &Option<String>,
246 ) -> ((f64, f64, f64), (f64, f64, f64)) {
247 if custom_color.is_some() {
248 (Self::parse_hex_color_f64(custom_color), (1.0, 1.0, 1.0))
249 } else if color == 'B' {
250 ((0.0, 0.0, 0.0), (1.0, 1.0, 1.0))
251 } else {
252 ((1.0, 1.0, 1.0), (0.0, 0.0, 0.0))
253 }
254 }
255
256 fn put_num(buf: &mut Vec<u8>, v: f64) {
265 if v == v.trunc() && v.abs() < 1e12 {
266 let mut itoa = [0u8; 20];
267 let mut n = v as i64;
268 if n < 0 {
269 buf.push(b'-');
270 n = -n;
271 }
272 let mut i = itoa.len();
273 loop {
274 i -= 1;
275 itoa[i] = b'0' + (n % 10) as u8;
276 n /= 10;
277 if n == 0 {
278 break;
279 }
280 }
281 buf.extend_from_slice(&itoa[i..]);
282 } else {
283 let mut s = format!("{:.3}", v);
284 while s.ends_with('0') {
285 s.pop();
286 }
287 if s.ends_with('.') {
288 s.pop();
289 }
290 buf.extend_from_slice(s.as_bytes());
291 }
292 }
293
294 fn emit_nums(&mut self, nums: &[f64], op: &str) {
296 for n in nums {
297 Self::put_num(&mut self.content, *n);
298 self.content.push(b' ');
299 }
300 self.content.extend_from_slice(op.as_bytes());
301 self.content.push(b'\n');
302 }
303
304 fn emit_op(&mut self, op: &str) {
306 self.content.extend_from_slice(op.as_bytes());
307 self.content.push(b'\n');
308 }
309
310 fn emit_name_op(&mut self, name: &str, op: &str) {
312 self.content.push(b'/');
313 self.content.extend_from_slice(name.as_bytes());
314 self.content.push(b' ');
315 self.content.extend_from_slice(op.as_bytes());
316 self.content.push(b'\n');
317 }
318
319 fn emit_tj(&mut self, text: &str) {
322 self.content.push(b'(');
323 for c in text.chars() {
324 let b = char_to_winansi(c).unwrap_or(b'?');
325 match b {
326 b'(' | b')' | b'\\' => {
327 self.content.push(b'\\');
328 self.content.push(b);
329 }
330 _ => self.content.push(b),
331 }
332 }
333 self.content.extend_from_slice(b") Tj\n");
334 }
335
336 fn set_fill_color(&mut self, r: f64, g: f64, b: f64) {
337 self.emit_nums(&[r, g, b], "rg");
338 }
339
340 fn save_state(&mut self) {
341 self.emit_op("q");
342 }
343
344 fn restore_state(&mut self) {
345 self.emit_op("Q");
346 }
347
348 fn track_backdrop_rect(&mut self, x: f64, y: f64, w: f64, h: f64, color: (f64, f64, f64)) {
358 if w > 0.0 && h > 0.0 {
359 self.backdrop_rects.push((x, y, w, h, color));
360 }
361 }
362
363 fn backdrop_color_at(&self, px: f64, py: f64) -> (f64, f64, f64) {
365 let mut color = (1.0, 1.0, 1.0);
366 for (rx, ry, rw, rh, c) in &self.backdrop_rects {
367 if px >= *rx && px < rx + rw && py >= *ry && py < ry + rh {
368 color = *c;
369 }
370 }
371 color
372 }
373
374 fn fill_inverse_backdrop(&mut self, ex: f64, ey: f64, ew: f64, eh: f64) {
378 self.set_fill_color(0.0, 0.0, 0.0);
380 let px = self.x_pt(ex);
381 let py = self.y_pt_bottom(ey, eh);
382 let (pw, ph) = (self.d2pt(ew), self.d2pt(eh));
383 self.emit_nums(&[px, py, pw, ph], "re");
384 self.emit_op("f");
385
386 let rects = self.backdrop_rects.clone();
389 for (rx, ry, rw, rh, (cr, cg, cb)) in rects {
390 let ix0 = rx.max(ex);
391 let iy0 = ry.max(ey);
392 let ix1 = (rx + rw).min(ex + ew);
393 let iy1 = (ry + rh).min(ey + eh);
394 if ix1 > ix0 && iy1 > iy0 {
395 self.set_fill_color(1.0 - cr, 1.0 - cg, 1.0 - cb);
396 let px = self.x_pt(ix0);
397 let py = self.y_pt_bottom(iy0, iy1 - iy0);
398 self.emit_nums(&[px, py, self.d2pt(ix1 - ix0), self.d2pt(iy1 - iy0)], "re");
399 self.emit_op("f");
400 }
401 }
402 }
403
404 fn push_rounded_rect_path(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
411 let r = r.min(w / 2.0).min(h / 2.0).max(0.0);
412 if r < 0.001 {
413 self.emit_nums(&[x, y, w, h], "re");
414 return;
415 }
416 let kr = KAPPA * r;
417 self.emit_nums(&[x + r, y], "m");
419 self.emit_nums(&[x + w - r, y], "l");
420 self.emit_nums(&[x + w - r + kr, y, x + w, y + r - kr, x + w, y + r], "c");
422 self.emit_nums(&[x + w, y + h - r], "l");
424 self.emit_nums(
426 &[
427 x + w,
428 y + h - r + kr,
429 x + w - r + kr,
430 y + h,
431 x + w - r,
432 y + h,
433 ],
434 "c",
435 );
436 self.emit_nums(&[x + r, y + h], "l");
438 self.emit_nums(&[x + r - kr, y + h, x, y + h - r + kr, x, y + h - r], "c");
440 self.emit_nums(&[x, y + r], "l");
442 self.emit_nums(&[x, y + r - kr, x + r - kr, y, x + r, y], "c");
444 self.emit_op("h");
445 }
446
447 fn push_ellipse_path(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) {
450 let kx = KAPPA * rx;
451 let ky = KAPPA * ry;
452 self.emit_nums(&[cx + rx, cy], "m");
454 self.emit_nums(&[cx + rx, cy + ky, cx + kx, cy + ry, cx, cy + ry], "c");
456 self.emit_nums(&[cx - kx, cy + ry, cx - rx, cy + ky, cx - rx, cy], "c");
458 self.emit_nums(&[cx - rx, cy - ky, cx - kx, cy - ry, cx, cy - ry], "c");
460 self.emit_nums(&[cx + kx, cy - ry, cx + rx, cy - ky, cx + rx, cy], "c");
462 self.emit_op("h");
463 }
464
465 fn get_text_width(
468 &self,
469 text: &str,
470 font_char: char,
471 height: Option<u32>,
472 width: Option<u32>,
473 ) -> u32 {
474 match self.font_manager.as_ref() {
475 Some(fm) => fm.measure_text(font_char, height, width, text),
476 None => 0,
477 }
478 }
479
480 fn embed_rgb_image(
485 &mut self,
486 x_dots: f64,
487 y_dots: f64,
488 img_w: u32,
489 img_h: u32,
490 rgb_data: Vec<u8>,
491 ) {
492 let name = format!("Im{}", self.image_counter);
493 self.image_counter += 1;
494
495 let px = self.x_pt(x_dots);
496 let py = self.y_pt_bottom(y_dots, img_h as f64);
497 let pw = self.d2pt(img_w as f64);
498 let ph = self.d2pt(img_h as f64);
499
500 self.save_state();
501 self.emit_nums(&[pw, 0.0, 0.0, ph, px, py], "cm");
502 self.emit_name_op(&name, "Do");
503 self.restore_state();
504
505 self.images.push(ImageXObject {
506 name,
507 data: rgb_data,
508 width: img_w,
509 height: img_h,
510 is_mask: false,
511 });
512 }
513
514 fn embed_mask_image(
518 &mut self,
519 x_dots: f64,
520 y_dots: f64,
521 img_w: u32,
522 img_h: u32,
523 bits: Vec<u8>,
524 reverse_print: bool,
525 ) {
526 let name = format!("Im{}", self.image_counter);
527 self.image_counter += 1;
528
529 let px = self.x_pt(x_dots);
530 let py = self.y_pt_bottom(y_dots, img_h as f64);
531 let pw = self.d2pt(img_w as f64);
532 let ph = self.d2pt(img_h as f64);
533
534 self.save_state();
535 if reverse_print {
536 let (br, bg, bb) =
540 self.backdrop_color_at(x_dots + img_w as f64 / 2.0, y_dots + img_h as f64 / 2.0);
541 self.set_fill_color(1.0 - br, 1.0 - bg, 1.0 - bb);
542 } else {
543 self.set_fill_color(0.0, 0.0, 0.0);
544 }
545 self.emit_nums(&[pw, 0.0, 0.0, ph, px, py], "cm");
546 self.emit_name_op(&name, "Do");
547 self.restore_state();
548
549 self.images.push(ImageXObject {
550 name,
551 data: bits,
552 width: img_w,
553 height: img_h,
554 is_mask: true,
555 });
556 }
557
558 #[allow(clippy::too_many_arguments)]
565 fn transform_1d_bar(
566 orientation: char,
567 base_x: u32,
568 base_y: u32,
569 lx: i32,
570 ly: i32,
571 w: u32,
572 h: u32,
573 bw: u32,
574 bh: u32,
575 ) -> (i32, i32, u32, u32) {
576 match orientation {
577 'R' => {
578 let nx = bh as i32 - (ly + h as i32);
579 let ny = lx;
580 (base_x as i32 + nx, base_y as i32 + ny, h, w)
581 }
582 'I' => {
583 let nx = bw as i32 - (lx + w as i32);
584 let ny = bh as i32 - (ly + h as i32);
585 (base_x as i32 + nx, base_y as i32 + ny, w, h)
586 }
587 'B' => {
588 let nx = ly;
589 let ny = bw as i32 - (lx + w as i32);
590 (base_x as i32 + nx, base_y as i32 + ny, h, w)
591 }
592 _ => (base_x as i32 + lx, base_y as i32 + ly, w, h),
593 }
594 }
595
596 #[allow(clippy::too_many_arguments)]
598 fn transform_2d_cell(
599 orientation: char,
600 base_x: u32,
601 base_y: u32,
602 lx: i32,
603 ly: i32,
604 w: u32,
605 h: u32,
606 full_w: u32,
607 full_h: u32,
608 ) -> (i32, i32, u32, u32) {
609 match orientation {
610 'R' => {
611 let nx = full_h as i32 - (ly + h as i32);
612 let ny = lx;
613 (base_x as i32 + nx, base_y as i32 + ny, h, w)
614 }
615 'I' => {
616 let nx = full_w as i32 - (lx + w as i32);
617 let ny = full_h as i32 - (ly + h as i32);
618 (base_x as i32 + nx, base_y as i32 + ny, w, h)
619 }
620 'B' => {
621 let nx = ly;
622 let ny = full_w as i32 - (lx + w as i32);
623 (base_x as i32 + nx, base_y as i32 + ny, h, w)
624 }
625 _ => (base_x as i32 + lx, base_y as i32 + ly, w, h),
626 }
627 }
628
629 #[allow(clippy::too_many_arguments)]
632 fn draw_1d_barcode(
633 &mut self,
634 x: u32,
635 y: u32,
636 orientation: char,
637 height: u32,
638 module_width: u32,
639 ratio: f32,
640 data: &str,
641 format: BarcodeFormat,
642 reverse_print: bool,
643 interpretation_line: char,
644 interpretation_line_above: char,
645 hints: Option<EncodeHints>,
646 hints_key: &str,
647 ) -> ZplResult<()> {
648 let numeric_data;
649 let data = match format {
650 BarcodeFormat::EAN_13
651 | BarcodeFormat::EAN_8
652 | BarcodeFormat::UPC_A
653 | BarcodeFormat::UPC_E
654 | BarcodeFormat::ITF => {
655 numeric_data = data
656 .chars()
657 .filter(|c| c.is_ascii_digit())
658 .collect::<String>();
659 &numeric_data
660 }
661 _ => data,
662 };
663
664 let padded_data;
665 let data = if format == BarcodeFormat::ITF && data.len() % 2 != 0 {
666 padded_data = format!("0{}", data);
667 &padded_data
668 } else {
669 data
670 };
671
672 let bit_matrix = barcode_cache::encode_cached(format, data, hints_key, hints.as_ref())?;
673
674 let mw = max(module_width, 1);
675 let elements = symbology::matrix_elements(
678 &bit_matrix,
679 mw,
680 symbology::is_two_width(format).then_some(ratio),
681 );
682
683 self.draw_elements(
684 x,
685 y,
686 orientation,
687 height,
688 module_width,
689 &elements,
690 reverse_print,
691 interpretation_line,
692 interpretation_line_above,
693 data,
694 )
695 }
696
697 #[allow(clippy::too_many_arguments)]
701 fn draw_elements(
702 &mut self,
703 x: u32,
704 y: u32,
705 orientation: char,
706 height: u32,
707 module_width: u32,
708 elements: &[symbology::Element],
709 reverse_print: bool,
710 interpretation_line: char,
711 interpretation_line_above: char,
712 data: &str,
713 ) -> ZplResult<()> {
714 let bh = height;
715 let bw: u32 = elements.iter().map(|e| e.width).sum();
716
717 let (full_w, full_h) = match orientation {
718 'R' | 'B' => (bh, bw),
719 _ => (bw, bh),
720 };
721
722 self.save_state();
724 if !reverse_print {
725 self.set_fill_color(0.0, 0.0, 0.0);
726 }
727
728 let mut cursor = 0u32;
729 for element in elements {
730 if element.bar {
731 let (rx, ry, rw, rh) = Self::transform_1d_bar(
732 orientation,
733 x,
734 y,
735 cursor as i32,
736 0,
737 element.width,
738 bh,
739 bw,
740 bh,
741 );
742 let px = self.d2pt(rx as f64);
743 let py = self.height_pt - self.d2pt(ry as f64 + rh as f64);
744 let pw = self.d2pt(rw as f64);
745 let ph = self.d2pt(rh as f64);
746 self.emit_nums(&[px, py, pw, ph], "re");
747 }
748 cursor += element.width;
749 }
750 if reverse_print {
751 self.emit_op("W");
753 self.emit_op("n");
754 self.fill_inverse_backdrop(x as f64, y as f64, full_w as f64, full_h as f64);
755 } else {
756 self.emit_op("f");
757 }
758 self.restore_state();
759
760 if interpretation_line == 'Y' {
762 self.draw_interpretation_line(
763 x,
764 y,
765 full_w,
766 full_h,
767 module_width,
768 data,
769 interpretation_line_above,
770 )?;
771 }
772
773 Ok(())
774 }
775
776 #[allow(clippy::too_many_arguments)]
782 fn draw_postnet(
783 &mut self,
784 x: u32,
785 y: u32,
786 orientation: char,
787 height: u32,
788 module_width: u32,
789 data: &str,
790 reverse_print: bool,
791 interpretation_line: char,
792 interpretation_line_above: char,
793 ) -> ZplResult<()> {
794 let bars = symbology::postnet_bars(data);
795 let (bar_w, gap) = symbology::postnet_pitch(module_width);
796 let pitch = bar_w + gap;
797 let short_h = ((height as f32) * symbology::POSTNET_SHORT_RATIO).round() as u32;
798 let full_w = bars.len() as u32 * bar_w + bars.len().saturating_sub(1) as u32 * gap;
799 let (span_w, span_h) = match orientation {
800 'R' | 'B' => (height, full_w),
801 _ => (full_w, height),
802 };
803
804 self.save_state();
805 if !reverse_print {
806 self.set_fill_color(0.0, 0.0, 0.0);
807 }
808 for (i, bar) in bars.iter().enumerate() {
809 let h = match bar {
810 symbology::BarHeight::Full => height,
811 symbology::BarHeight::Half => short_h,
812 };
813 let top = height.saturating_sub(h);
815 let (rx, ry, rw, rh) = Self::transform_1d_bar(
816 orientation,
817 x,
818 y,
819 (i as u32 * pitch) as i32,
820 top as i32,
821 bar_w,
822 h,
823 full_w,
824 height,
825 );
826 let px = self.d2pt(rx as f64);
827 let py = self.height_pt - self.d2pt(ry as f64 + rh as f64);
828 self.emit_nums(&[px, py, self.d2pt(rw as f64), self.d2pt(rh as f64)], "re");
829 }
830 if reverse_print {
831 self.emit_op("W");
832 self.emit_op("n");
833 self.fill_inverse_backdrop(x as f64, y as f64, span_w as f64, span_h as f64);
834 } else {
835 self.emit_op("f");
836 }
837 self.restore_state();
838
839 if interpretation_line == 'Y' {
840 let digits: String = data.chars().filter(|c| c.is_ascii_digit()).collect();
841 self.draw_interpretation_line(
842 x,
843 y,
844 span_w,
845 span_h,
846 module_width,
847 &digits,
848 interpretation_line_above,
849 )?;
850 }
851
852 Ok(())
853 }
854
855 #[allow(clippy::too_many_arguments)]
856 fn draw_interpretation_line(
857 &mut self,
858 x: u32,
859 y: u32,
860 full_w: u32,
861 full_h: u32,
862 module_width: u32,
863 data: &str,
864 interpretation_line_above: char,
865 ) -> ZplResult<()> {
866 {
867 let (font_char, text_h, text_w, gap) =
868 crate::engine::font::interpretation_metrics(module_width);
869 let text_y = if interpretation_line_above == 'Y' {
870 y.saturating_sub(text_h + gap)
871 } else {
872 y + full_h + gap
873 };
874
875 let text_width = self.get_text_width(data, font_char, Some(text_h), Some(text_w));
876 let text_x = if full_w > text_width {
877 x + (full_w - text_width) / 2
878 } else {
879 x
880 };
881
882 self.draw_text(
883 text_x,
884 text_y,
885 font_char,
886 Some(text_h),
887 Some(text_w),
888 'N',
889 data,
890 false,
891 None,
892 )?;
893 }
894
895 Ok(())
896 }
897
898 #[allow(clippy::too_many_arguments)]
902 fn fill_matrix_cells(
903 &mut self,
904 x: u32,
905 y: u32,
906 orientation: char,
907 cell_w: u32,
908 cell_h: u32,
909 bit_matrix: &BitMatrix,
910 reverse_print: bool,
911 ) {
912 let bw = bit_matrix.getWidth();
913 let bh = bit_matrix.getHeight();
914 let full_w = bw * cell_w;
915 let full_h = bh * cell_h;
916
917 self.save_state();
918 if !reverse_print {
919 self.set_fill_color(0.0, 0.0, 0.0);
920 }
921
922 for gy in 0..bh {
923 for gx in 0..bw {
924 if bit_matrix.get(gx, gy) {
925 let (rx, ry, rw, rh) = Self::transform_2d_cell(
926 orientation,
927 x,
928 y,
929 (gx * cell_w) as i32,
930 (gy * cell_h) as i32,
931 cell_w,
932 cell_h,
933 full_w,
934 full_h,
935 );
936 let px = self.d2pt(rx as f64);
937 let py = self.height_pt - self.d2pt(ry as f64 + rh as f64);
938 let pw = self.d2pt(rw as f64);
939 let ph = self.d2pt(rh as f64);
940 self.emit_nums(&[px, py, pw, ph], "re");
941 }
942 }
943 }
944 if reverse_print {
945 self.emit_op("W");
946 self.emit_op("n");
947 let (fw, fh) = match orientation {
948 'R' | 'B' => (full_h, full_w),
949 _ => (full_w, full_h),
950 };
951 self.fill_inverse_backdrop(x as f64, y as f64, fw as f64, fh as f64);
952 } else {
953 self.emit_op("f");
954 }
955 self.restore_state();
956 }
957}
958
959impl ZplForgeBackend for PdfNativeBackend {
962 fn setup_page(&mut self, width: f64, height: f64, resolution: f32) {
963 let dpi = if resolution == 0.0 { 203.2 } else { resolution };
964 self.width_dots = width;
965 self.height_dots = height;
966 self.resolution = dpi;
967 self.scale = 72.0 / dpi as f64;
968 self.width_pt = width * self.scale;
969 self.height_pt = height * self.scale;
970 }
971
972 fn setup_font_manager(&mut self, font_manager: &FontManager) {
973 self.font_manager = Some(Arc::new(font_manager.clone()));
974 }
975
976 fn new_page(&mut self) -> ZplResult<()> {
977 self.finished_pages.push(std::mem::take(&mut self.content));
978 self.backdrop_rects.clear();
979 Ok(())
980 }
981
982 fn draw_text(
985 &mut self,
986 x: u32,
987 y: u32,
988 font: char,
989 height: Option<u32>,
990 width: Option<u32>,
991 orientation: char,
992 text: &str,
993 reverse_print: bool,
994 color: Option<String>,
995 ) -> ZplResult<()> {
996 if text.is_empty() {
997 return Ok(());
998 }
999
1000 let layout = {
1001 let fm = self
1002 .font_manager
1003 .as_ref()
1004 .ok_or_else(|| ZplError::FontError("Font manager not initialized".into()))?;
1005 fm.text_layout(font, height, width)
1006 .ok_or_else(|| ZplError::FontError(format!("Font not found: {}", font)))?
1007 .1
1008 };
1009
1010 self.used_fonts.insert(font);
1011
1012 let em_x_pt = self.d2pt(layout.em_x as f64);
1015 let em_y_pt = self.d2pt(layout.em_y as f64);
1016 let baseline_dots = layout.baseline as f64;
1018 let h_dots = layout.cell_h as f64;
1019 let x = x as f64;
1020 let y = y as f64;
1021
1022 let tw_dots = if reverse_print || orientation == 'I' || orientation == 'B' {
1024 self.get_text_width(text, font, height, width) as f64
1025 } else {
1026 0.0
1027 };
1028
1029 let tm = match orientation {
1032 'R' => [
1033 0.0,
1034 -em_x_pt,
1035 em_y_pt,
1036 0.0,
1037 self.x_pt(x + h_dots - baseline_dots),
1038 self.height_pt - y * self.scale,
1039 ],
1040 'I' => [
1041 -em_x_pt,
1042 0.0,
1043 0.0,
1044 -em_y_pt,
1045 self.x_pt(x + tw_dots),
1046 self.height_pt - (y + h_dots - baseline_dots) * self.scale,
1047 ],
1048 'B' => [
1049 0.0,
1050 em_x_pt,
1051 -em_y_pt,
1052 0.0,
1053 self.x_pt(x + baseline_dots),
1054 self.height_pt - (y + tw_dots) * self.scale,
1055 ],
1056 _ => [
1057 em_x_pt,
1058 0.0,
1059 0.0,
1060 em_y_pt,
1061 self.x_pt(x),
1062 self.height_pt - (y + baseline_dots) * self.scale,
1063 ],
1064 };
1065
1066 self.save_state();
1067 if !reverse_print {
1068 let (r, g, b) = Self::parse_hex_color_f64(&color);
1069 self.set_fill_color(r, g, b);
1070 }
1071
1072 self.emit_op("BT");
1073 if reverse_print {
1074 self.emit_nums(&[7.0], "Tr");
1076 }
1077 self.emit_nums(&tm, "Tm");
1078 let font_resource_name = format!("F_{}", font);
1079 self.emit_name_op(&format!("{} 1", font_resource_name), "Tf");
1080 self.emit_tj(text);
1081 self.emit_op("ET");
1082
1083 if reverse_print {
1084 let (bw_dots, bh_dots) = match orientation {
1085 'R' | 'B' => (h_dots, tw_dots),
1086 _ => (tw_dots, h_dots),
1087 };
1088 self.fill_inverse_backdrop(x, y, bw_dots, bh_dots);
1089 }
1090 self.restore_state();
1091
1092 Ok(())
1093 }
1094
1095 fn draw_graphic_box(
1098 &mut self,
1099 x: u32,
1100 y: u32,
1101 width: u32,
1102 height: u32,
1103 thickness: u32,
1104 color: char,
1105 custom_color: Option<String>,
1106 rounding: u32,
1107 reverse_print: bool,
1108 ) -> ZplResult<()> {
1109 let w = max(width, 1) as f64;
1110 let h = max(height, 1) as f64;
1111 let t = thickness as f64;
1112 let r_dots = rounding as f64 * 8.0;
1113
1114 let (draw_color, clear_color) = Self::resolve_colors(color, &custom_color);
1115
1116 let bx = self.x_pt(x as f64);
1117 let by = self.y_pt_bottom(y as f64, h);
1118 let bw = self.d2pt(w);
1119 let bh = self.d2pt(h);
1120 let br = self.d2pt(r_dots);
1121
1122 let has_inner = t * 2.0 < w && t * 2.0 < h;
1123
1124 if reverse_print {
1125 self.save_state();
1128 self.push_rounded_rect_path(bx, by, bw, bh, br);
1129 if has_inner {
1130 let tp = self.d2pt(t);
1131 let inner_r = self.d2pt((r_dots - t).max(0.0));
1132 self.push_rounded_rect_path(
1133 bx + tp,
1134 by + tp,
1135 bw - tp * 2.0,
1136 bh - tp * 2.0,
1137 inner_r,
1138 );
1139 self.emit_op("W*");
1140 } else {
1141 self.emit_op("W");
1142 }
1143 self.emit_op("n");
1144 self.fill_inverse_backdrop(x as f64, y as f64, w, h);
1145 self.restore_state();
1146 } else {
1147 self.save_state();
1148 let (r, g, b) = draw_color;
1149 self.set_fill_color(r, g, b);
1150 self.push_rounded_rect_path(bx, by, bw, bh, br);
1151 self.emit_op("f");
1152 self.track_backdrop_rect(x as f64, y as f64, w, h, draw_color);
1153
1154 if has_inner {
1155 let (cr, cg, cb) = clear_color;
1156 self.set_fill_color(cr, cg, cb);
1157 let tp = self.d2pt(t);
1158 let inner_r = self.d2pt((r_dots - t).max(0.0));
1159 self.push_rounded_rect_path(
1160 bx + tp,
1161 by + tp,
1162 bw - tp * 2.0,
1163 bh - tp * 2.0,
1164 inner_r,
1165 );
1166 self.emit_op("f");
1167 self.track_backdrop_rect(
1168 x as f64 + t,
1169 y as f64 + t,
1170 w - t * 2.0,
1171 h - t * 2.0,
1172 clear_color,
1173 );
1174 }
1175 self.restore_state();
1176 }
1177
1178 Ok(())
1179 }
1180
1181 fn draw_graphic_circle(
1184 &mut self,
1185 x: u32,
1186 y: u32,
1187 radius: u32,
1188 thickness: u32,
1189 _color: char,
1190 custom_color: Option<String>,
1191 reverse_print: bool,
1192 ) -> ZplResult<()> {
1193 let (draw_color, _) = Self::resolve_colors('B', &custom_color);
1194
1195 let r_pt = self.d2pt(radius as f64);
1196 let cx_pt = self.x_pt(x as f64) + r_pt;
1198 let cy_pt = self.height_pt - (y as f64 + radius as f64) * self.scale;
1199
1200 if reverse_print {
1201 self.save_state();
1202 self.push_ellipse_path(cx_pt, cy_pt, r_pt, r_pt);
1203 if radius > thickness {
1204 let inner_r = self.d2pt((radius - thickness) as f64);
1205 self.push_ellipse_path(cx_pt, cy_pt, inner_r, inner_r);
1206 self.emit_op("W*");
1207 } else {
1208 self.emit_op("W");
1209 }
1210 self.emit_op("n");
1211 self.fill_inverse_backdrop(
1212 x as f64,
1213 y as f64,
1214 radius as f64 * 2.0,
1215 radius as f64 * 2.0,
1216 );
1217 self.restore_state();
1218 } else {
1219 self.save_state();
1220 let (r, g, b) = draw_color;
1221 self.set_fill_color(r, g, b);
1222 self.push_ellipse_path(cx_pt, cy_pt, r_pt, r_pt);
1223 self.emit_op("f");
1224
1225 if radius > thickness {
1226 self.set_fill_color(1.0, 1.0, 1.0);
1227 let inner_r = self.d2pt((radius - thickness) as f64);
1228 self.push_ellipse_path(cx_pt, cy_pt, inner_r, inner_r);
1229 self.emit_op("f");
1230 }
1231 self.restore_state();
1232 }
1233
1234 Ok(())
1235 }
1236
1237 fn draw_graphic_ellipse(
1240 &mut self,
1241 x: u32,
1242 y: u32,
1243 width: u32,
1244 height: u32,
1245 thickness: u32,
1246 _color: char,
1247 custom_color: Option<String>,
1248 reverse_print: bool,
1249 ) -> ZplResult<()> {
1250 let (draw_color, _) = Self::resolve_colors('B', &custom_color);
1251
1252 let rx_pt = self.d2pt(width as f64 / 2.0);
1253 let ry_pt = self.d2pt(height as f64 / 2.0);
1254 let cx_pt = self.x_pt(x as f64) + rx_pt;
1255 let cy_pt = self.height_pt - (y as f64 + height as f64 / 2.0) * self.scale;
1256
1257 let t = thickness as f64;
1258
1259 if reverse_print {
1260 self.save_state();
1261 self.push_ellipse_path(cx_pt, cy_pt, rx_pt, ry_pt);
1262 if (width as f64 / 2.0) > t && (height as f64 / 2.0) > t {
1263 let irx = self.d2pt(width as f64 / 2.0 - t);
1264 let iry = self.d2pt(height as f64 / 2.0 - t);
1265 self.push_ellipse_path(cx_pt, cy_pt, irx, iry);
1266 self.emit_op("W*");
1267 } else {
1268 self.emit_op("W");
1269 }
1270 self.emit_op("n");
1271 self.fill_inverse_backdrop(x as f64, y as f64, width as f64, height as f64);
1272 self.restore_state();
1273 } else {
1274 self.save_state();
1275 let (r, g, b) = draw_color;
1276 self.set_fill_color(r, g, b);
1277 self.push_ellipse_path(cx_pt, cy_pt, rx_pt, ry_pt);
1278 self.emit_op("f");
1279
1280 if (width as f64 / 2.0) > t && (height as f64 / 2.0) > t {
1281 self.set_fill_color(1.0, 1.0, 1.0);
1282 let irx = self.d2pt(width as f64 / 2.0 - t);
1283 let iry = self.d2pt(height as f64 / 2.0 - t);
1284 self.push_ellipse_path(cx_pt, cy_pt, irx, iry);
1285 self.emit_op("f");
1286 }
1287 self.restore_state();
1288 }
1289
1290 Ok(())
1291 }
1292
1293 fn draw_graphic_field(
1296 &mut self,
1297 x: u32,
1298 y: u32,
1299 width: u32,
1300 height: u32,
1301 data: &[u8],
1302 reverse_print: bool,
1303 ) -> ZplResult<()> {
1304 if width == 0 || height == 0 {
1305 return Ok(());
1306 }
1307
1308 let row_bytes = width.div_ceil(8) as usize;
1312 let total_bytes = row_bytes * height as usize;
1313 let mut bits = data.to_vec();
1314 bits.resize(total_bytes, 0x00);
1315
1316 self.embed_mask_image(x as f64, y as f64, width, height, bits, reverse_print);
1317 Ok(())
1318 }
1319
1320 fn draw_graphic_image_custom(
1323 &mut self,
1324 x: u32,
1325 y: u32,
1326 width: u32,
1327 height: u32,
1328 data: &str,
1329 ) -> ZplResult<()> {
1330 let image_data = general_purpose::STANDARD
1331 .decode(data.trim())
1332 .map_err(|e| ZplError::ImageError(format!("Failed to decode base64: {}", e)))?;
1333
1334 let img = image::load_from_memory(&image_data)
1335 .map_err(|e| ZplError::ImageError(format!("Failed to load image: {}", e)))?
1336 .to_rgb8();
1337
1338 let (orig_w, orig_h) = img.dimensions();
1339 let (target_w, target_h) = match (width, height) {
1340 (0, 0) => (orig_w, orig_h),
1341 (w, 0) => {
1342 let h = (orig_h as f32 * (w as f32 / orig_w as f32)).round() as u32;
1343 (w, h)
1344 }
1345 (0, h) => {
1346 let w = (orig_w as f32 * (h as f32 / orig_h as f32)).round() as u32;
1347 (w, h)
1348 }
1349 (w, h) => (w, h),
1350 };
1351
1352 let final_img = if target_w != orig_w || target_h != orig_h {
1353 image::imageops::resize(
1354 &img,
1355 target_w,
1356 target_h,
1357 image::imageops::FilterType::Lanczos3,
1358 )
1359 } else {
1360 img
1361 };
1362
1363 let rgb_data = final_img.into_raw();
1364 self.embed_rgb_image(x as f64, y as f64, target_w, target_h, rgb_data);
1365 Ok(())
1366 }
1367
1368 fn draw_code128(
1371 &mut self,
1372 x: u32,
1373 y: u32,
1374 orientation: char,
1375 height: u32,
1376 module_width: u32,
1377 interpretation_line: char,
1378 interpretation_line_above: char,
1379 _check_digit: char,
1380 _mode: char,
1381 data: &str,
1382 reverse_print: bool,
1383 ) -> ZplResult<()> {
1384 let (clean_data, code_set) = super::code128_code_set(data);
1385
1386 let mut h = HashMap::new();
1387 h.insert(
1388 EncodeHintType::FORCE_CODE_SET,
1389 EncodeHintValue::ForceCodeSet(code_set.to_string()),
1390 );
1391 let hints = Some(EncodeHints::from(h));
1392
1393 self.draw_1d_barcode(
1394 x,
1395 y,
1396 orientation,
1397 height,
1398 module_width,
1399 0.0,
1401 clean_data,
1402 BarcodeFormat::CODE_128,
1403 reverse_print,
1404 interpretation_line,
1405 interpretation_line_above,
1406 hints,
1407 code_set,
1408 )
1409 }
1410
1411 fn draw_qr_code(
1414 &mut self,
1415 x: u32,
1416 y: u32,
1417 orientation: char,
1418 _model: u32,
1419 magnification: u32,
1420 error_correction: char,
1421 _mask: u32,
1422 data: &str,
1423 reverse_print: bool,
1424 ) -> ZplResult<()> {
1425 let (ec, payload) = super::qr_field_data(data, error_correction);
1426 let level = match ec {
1427 'L' => "L",
1428 'Q' => "Q",
1429 'H' => "H",
1430 _ => "M",
1431 };
1432
1433 let mut hints = HashMap::new();
1434 hints.insert(
1435 EncodeHintType::ERROR_CORRECTION,
1436 EncodeHintValue::ErrorCorrection(level.to_string()),
1437 );
1438 let hints: EncodeHints = hints.into();
1439
1440 let bit_matrix = barcode_cache::encode_cached(
1441 BarcodeFormat::QR_CODE,
1442 payload,
1443 &format!("ec:{}", level),
1444 Some(&hints),
1445 )?;
1446
1447 let mag = max(magnification, 1);
1448 self.fill_matrix_cells(x, y, orientation, mag, mag, &bit_matrix, reverse_print);
1449 Ok(())
1450 }
1451
1452 fn draw_datamatrix(
1455 &mut self,
1456 x: u32,
1457 y: u32,
1458 orientation: char,
1459 module_size: u32,
1460 data: &str,
1461 reverse_print: bool,
1462 ) -> ZplResult<()> {
1463 let hints = EncodeHints {
1466 DataMatrixShape: Some(SymbolShapeHint::FORCE_SQUARE),
1467 ..Default::default()
1468 };
1469
1470 let bit_matrix =
1471 barcode_cache::encode_cached(BarcodeFormat::DATA_MATRIX, data, "sq", Some(&hints))?;
1472
1473 let m = max(module_size, 1);
1474 self.fill_matrix_cells(x, y, orientation, m, m, &bit_matrix, reverse_print);
1475 Ok(())
1476 }
1477
1478 fn draw_pdf417(
1481 &mut self,
1482 x: u32,
1483 y: u32,
1484 orientation: char,
1485 row_height: u32,
1486 module_width: u32,
1487 security_level: u32,
1488 data: &str,
1489 reverse_print: bool,
1490 ) -> ZplResult<()> {
1491 let mut hints = HashMap::new();
1492 hints.insert(
1493 EncodeHintType::ERROR_CORRECTION,
1494 EncodeHintValue::ErrorCorrection(security_level.min(8).to_string()),
1495 );
1496 hints.insert(
1497 EncodeHintType::MARGIN,
1498 EncodeHintValue::Margin("0".to_owned()),
1499 );
1500 let hints: EncodeHints = hints.into();
1501
1502 let bit_matrix = barcode_cache::encode_cached(
1503 BarcodeFormat::PDF_417,
1504 data,
1505 &format!("ec:{}", security_level.min(8)),
1506 Some(&hints),
1507 )?;
1508
1509 let cw = max(module_width, 1);
1510 let ch = max(row_height, 1);
1511 self.fill_matrix_cells(x, y, orientation, cw, ch, &bit_matrix, reverse_print);
1512 Ok(())
1513 }
1514
1515 fn draw_micropdf417(
1516 &mut self,
1517 x: u32,
1518 y: u32,
1519 orientation: char,
1520 height: u32,
1521 _mode: u32,
1522 data: &str,
1523 reverse_print: bool,
1524 ) -> ZplResult<()> {
1525 let bit_matrix = barcode_cache::encode_cached(BarcodeFormat::PDF_417, data, "", None)?;
1526 let ch = max(height, 1);
1527 self.fill_matrix_cells(x, y, orientation, 1, ch, &bit_matrix, reverse_print);
1528 Ok(())
1529 }
1530
1531 fn draw_aztec_code(
1532 &mut self,
1533 x: u32,
1534 y: u32,
1535 orientation: char,
1536 magnification: u32,
1537 data: &str,
1538 reverse_print: bool,
1539 ) -> ZplResult<()> {
1540 let bit_matrix = barcode_cache::encode_cached(BarcodeFormat::AZTEC, data, "", None)?;
1541 let m = max(magnification, 1);
1542 self.fill_matrix_cells(x, y, orientation, m, m, &bit_matrix, reverse_print);
1543 Ok(())
1544 }
1545
1546 fn draw_code39(
1549 &mut self,
1550 x: u32,
1551 y: u32,
1552 orientation: char,
1553 _check_digit: char,
1554 height: u32,
1555 module_width: u32,
1556 ratio: f32,
1557 interpretation_line: char,
1558 interpretation_line_above: char,
1559 data: &str,
1560 reverse_print: bool,
1561 ) -> ZplResult<()> {
1562 self.draw_1d_barcode(
1563 x,
1564 y,
1565 orientation,
1566 height,
1567 module_width,
1568 ratio,
1569 data,
1570 BarcodeFormat::CODE_39,
1571 reverse_print,
1572 interpretation_line,
1573 interpretation_line_above,
1574 None,
1575 "",
1576 )
1577 }
1578
1579 fn draw_barcode_1d(
1582 &mut self,
1583 kind: Barcode1DKind,
1584 x: u32,
1585 y: u32,
1586 orientation: char,
1587 height: u32,
1588 module_width: u32,
1589 ratio: f32,
1590 check_digit: char,
1591 interpretation_line: char,
1592 interpretation_line_above: char,
1593 data: &str,
1594 reverse_print: bool,
1595 ) -> ZplResult<()> {
1596 match kind {
1598 Barcode1DKind::Msi => {
1599 let check = symbology::MsiCheck::from_zpl(check_digit);
1600 let elements = symbology::msi_elements(data, check, module_width, ratio);
1601 let text = symbology::msi_text(data, check);
1602 return self.draw_elements(
1603 x,
1604 y,
1605 orientation,
1606 height,
1607 module_width,
1608 &elements,
1609 reverse_print,
1610 interpretation_line,
1611 interpretation_line_above,
1612 &text,
1613 );
1614 }
1615 Barcode1DKind::Postnet => {
1616 return self.draw_postnet(
1617 x,
1618 y,
1619 orientation,
1620 height,
1621 module_width,
1622 data,
1623 reverse_print,
1624 interpretation_line,
1625 interpretation_line_above,
1626 );
1627 }
1628 _ => {}
1629 }
1630
1631 self.draw_1d_barcode(
1632 x,
1633 y,
1634 orientation,
1635 height,
1636 module_width,
1637 ratio,
1638 data,
1639 barcode_1d_format(kind),
1640 reverse_print,
1641 interpretation_line,
1642 interpretation_line_above,
1643 None,
1644 "",
1645 )
1646 }
1647
1648 fn draw_graphic_diagonal(
1651 &mut self,
1652 x: u32,
1653 y: u32,
1654 width: u32,
1655 height: u32,
1656 thickness: u32,
1657 color: char,
1658 custom_color: Option<String>,
1659 diagonal_orientation: char,
1660 reverse_print: bool,
1661 ) -> ZplResult<()> {
1662 let (draw_color, _) = Self::resolve_colors(color, &custom_color);
1663
1664 let w = max(width, 1) as f64;
1665 let h = max(height, 1) as f64;
1666 let t = (max(thickness, 1) as f64).min(w);
1667 let x = x as f64;
1668 let y = y as f64;
1669
1670 let pts: [(f64, f64); 4] = if diagonal_orientation == 'L' {
1672 [(x, y), (x + t, y), (x + w, y + h), (x + w - t, y + h)]
1674 } else {
1675 [(x, y + h), (x + t, y + h), (x + w, y), (x + w - t, y)]
1677 };
1678
1679 self.save_state();
1680 if !reverse_print {
1681 let (r, g, b) = draw_color;
1682 self.set_fill_color(r, g, b);
1683 }
1684
1685 for (i, (dx, dy)) in pts.iter().enumerate() {
1686 let px = self.x_pt(*dx);
1687 let py = self.height_pt - dy * self.scale;
1688 self.emit_nums(&[px, py], if i == 0 { "m" } else { "l" });
1689 }
1690 self.emit_op("h");
1691 if reverse_print {
1692 self.emit_op("W");
1693 self.emit_op("n");
1694 self.fill_inverse_backdrop(x, y, w, h);
1695 } else {
1696 self.emit_op("f");
1697 }
1698 self.restore_state();
1699
1700 Ok(())
1701 }
1702
1703 fn finalize(&mut self) -> ZplResult<Vec<u8>> {
1706 let mut doc = Document::with_version("1.5");
1707 let pages_id = doc.new_object_id();
1708
1709 let default_font_bytes: &[u8] = include_bytes!("../assets/IosevkaTermSlab-Regular.ttf");
1717 let mut font_dict = lopdf::Dictionary::new();
1718 let mut embedded_fonts: HashMap<String, lopdf::ObjectId> = HashMap::new();
1720 let tounicode_id = doc.add_object(Stream::new(dictionary! {}, build_tounicode_cmap()));
1721
1722 for font_char in &self.used_fonts {
1723 let font_key = font_char.to_string();
1724 let resource_name = format!("F_{}", font_char);
1725
1726 let actual_name = self
1727 .font_manager
1728 .as_ref()
1729 .and_then(|fm| fm.get_font_name(&font_key).map(|s| s.to_string()))
1730 .unwrap_or_else(|| "Iosevka Term Slab".to_string());
1731
1732 if let Some(font_id) = embedded_fonts.get(&actual_name) {
1733 font_dict.set(resource_name.as_str(), *font_id);
1734 continue;
1735 }
1736
1737 let raw_bytes = self
1738 .font_manager
1739 .as_ref()
1740 .and_then(|fm| fm.get_font_bytes(&font_key))
1741 .unwrap_or(default_font_bytes);
1742
1743 let face = FontArc::try_from_vec(raw_bytes.to_vec())
1744 .map_err(|e| ZplError::FontError(format!("Invalid font data: {}", e)))?;
1745 let upem = face.units_per_em().unwrap_or(1000.0) as f64;
1746 let to_glyph_space = |v: f64| (v * 1000.0 / upem).round() as i64;
1747
1748 let widths: Vec<Object> = (0x20..=0xFFu32)
1750 .map(|code| {
1751 let w = winansi_to_char(code as u8)
1752 .map(|ch| to_glyph_space(face.h_advance_unscaled(face.glyph_id(ch)) as f64))
1753 .unwrap_or(0);
1754 w.into()
1755 })
1756 .collect();
1757
1758 let fd = FontData::new(raw_bytes, actual_name.clone());
1760
1761 let font_stream = Stream::new(
1762 dictionary! { "Length1" => raw_bytes.len() as i64 },
1763 raw_bytes.to_vec(),
1764 );
1765 let font_file_id = doc.add_object(font_stream);
1766
1767 let descriptor_id = doc.add_object(dictionary! {
1768 "Type" => "FontDescriptor",
1769 "FontName" => Object::Name(actual_name.clone().into_bytes()),
1770 "Flags" => 32_i64,
1771 "FontBBox" => vec![
1772 to_glyph_space(fd.font_bbox.0 as f64).into(),
1773 to_glyph_space(fd.font_bbox.1 as f64).into(),
1774 to_glyph_space(fd.font_bbox.2 as f64).into(),
1775 to_glyph_space(fd.font_bbox.3 as f64).into(),
1776 ],
1777 "ItalicAngle" => fd.italic_angle,
1778 "Ascent" => to_glyph_space(fd.ascent as f64),
1779 "Descent" => to_glyph_space(fd.descent as f64),
1780 "CapHeight" => to_glyph_space(fd.cap_height as f64),
1781 "StemV" => 80_i64,
1782 "FontFile2" => font_file_id,
1783 });
1784
1785 let font_id = doc.add_object(dictionary! {
1786 "Type" => "Font",
1787 "Subtype" => "TrueType",
1788 "BaseFont" => Object::Name(actual_name.clone().into_bytes()),
1789 "FirstChar" => 32_i64,
1790 "LastChar" => 255_i64,
1791 "Widths" => widths,
1792 "FontDescriptor" => descriptor_id,
1793 "Encoding" => "WinAnsiEncoding",
1794 "ToUnicode" => tounicode_id,
1795 });
1796
1797 font_dict.set(resource_name.as_str(), font_id);
1798 embedded_fonts.insert(actual_name, font_id);
1799 }
1800
1801 let mut xobject_dict = lopdf::Dictionary::new();
1803 for img in &self.images {
1804 let mut encoder = ZlibEncoder::new(Vec::new(), self.compression);
1805 encoder
1806 .write_all(&img.data)
1807 .map_err(|e| ZplError::BackendError(e.to_string()))?;
1808 let compressed = encoder
1809 .finish()
1810 .map_err(|e| ZplError::BackendError(e.to_string()))?;
1811
1812 let dict = if img.is_mask {
1813 dictionary! {
1816 "Type" => "XObject",
1817 "Subtype" => "Image",
1818 "Width" => img.width as i64,
1819 "Height" => img.height as i64,
1820 "ImageMask" => true,
1821 "BitsPerComponent" => 1,
1822 "Decode" => vec![0.into(), 1.into()],
1823 "Filter" => "FlateDecode",
1824 }
1825 } else {
1826 dictionary! {
1827 "Type" => "XObject",
1828 "Subtype" => "Image",
1829 "Width" => img.width as i64,
1830 "Height" => img.height as i64,
1831 "ColorSpace" => "DeviceRGB",
1832 "BitsPerComponent" => 8,
1833 "Filter" => "FlateDecode",
1834 }
1835 };
1836 let img_stream = Stream::new(dict, compressed);
1837 let img_id = doc.add_object(img_stream);
1838 xobject_dict.set(img.name.as_str(), img_id);
1839 }
1840
1841 let resources_id = doc.add_object(dictionary! {
1843 "Font" => lopdf::Object::Dictionary(font_dict),
1844 "XObject" => lopdf::Object::Dictionary(xobject_dict),
1845 });
1846
1847 let mut page_contents = std::mem::take(&mut self.finished_pages);
1849 page_contents.push(std::mem::take(&mut self.content));
1850
1851 let mut kids: Vec<Object> = Vec::with_capacity(page_contents.len());
1852 for content_bytes in page_contents {
1853 let content_id = doc.add_object(Stream::new(dictionary! {}, content_bytes));
1854 let page_id = doc.add_object(dictionary! {
1855 "Type" => "Page",
1856 "Parent" => pages_id,
1857 "MediaBox" => vec![
1858 0.into(),
1859 0.into(),
1860 Object::Real(self.width_pt as f32),
1861 Object::Real(self.height_pt as f32),
1862 ],
1863 "Contents" => content_id,
1864 "Resources" => resources_id,
1865 });
1866 kids.push(page_id.into());
1867 }
1868
1869 let pages_dict = dictionary! {
1871 "Type" => "Pages",
1872 "Count" => kids.len() as i64,
1873 "Kids" => kids,
1874 };
1875 doc.objects.insert(pages_id, Object::Dictionary(pages_dict));
1876
1877 let catalog_id = doc.add_object(dictionary! {
1879 "Type" => "Catalog",
1880 "Pages" => pages_id,
1881 });
1882 doc.trailer.set("Root", catalog_id);
1883
1884 let mut info = lopdf::Dictionary::new();
1886 info.set(
1887 "Producer",
1888 Object::string_literal(concat!("zpl-forge ", env!("CARGO_PKG_VERSION"))),
1889 );
1890 if let Some(title) = &self.title {
1891 info.set("Title", Object::string_literal(title.as_str()));
1892 }
1893 let info_id = doc.add_object(Object::Dictionary(info));
1894 doc.trailer.set("Info", info_id);
1895
1896 doc.compress();
1897
1898 let mut buf = std::io::BufWriter::new(Vec::new());
1900 doc.save_to(&mut buf)
1901 .map_err(|e| ZplError::BackendError(format!("Failed to save PDF: {}", e)))?;
1902 buf.into_inner()
1903 .map_err(|e| ZplError::BackendError(format!("Failed to flush: {}", e)))
1904 }
1905}