1use crate::engine::{FontManager, ZplForgeBackend};
2use crate::forge::png::PngBackend;
3use crate::{ZplError, ZplResult};
4
5use flate2::Compression;
6use flate2::write::ZlibEncoder;
7use image::ImageDecoder;
8use image::codecs::png::PngDecoder;
9use lopdf::content::{Content, Operation};
10use lopdf::{Document, Object, Stream, dictionary};
11use rayon::prelude::*;
12use std::io::{BufWriter, Write};
13
14pub struct PdfBackend {
20 png_backend: PngBackend,
21 width_dots: f64,
22 height_dots: f64,
23 resolution: f32,
24 compression: Compression,
25}
26
27impl Default for PdfBackend {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl PdfBackend {
34 pub fn with_compression(mut self, compression: Compression) -> Self {
38 self.compression = compression;
39 self
40 }
41
42 pub fn new() -> Self {
47 Self {
48 png_backend: PngBackend::new(),
49 width_dots: 0.0,
50 height_dots: 0.0,
51 resolution: 0.0,
52 compression: Compression::default(),
53 }
54 }
55}
56
57type PreparedPage = (Vec<u8>, u32, u32);
59
60fn decode_and_compress_png(
61 png_data: &[u8],
62 compression: Compression,
63) -> Result<PreparedPage, String> {
64 let decoder = PngDecoder::new(std::io::Cursor::new(png_data))
65 .map_err(|e| format!("Failed to create PNG decoder: {}", e))?;
66 let (w, h) = decoder.dimensions();
67 let channels = decoder.color_type().channel_count() as usize;
68 let mut raw_buf = vec![0u8; decoder.total_bytes() as usize];
69 decoder
70 .read_image(&mut raw_buf)
71 .map_err(|e| format!("Failed to decode PNG: {}", e))?;
72
73 let rgb_buf = if channels == 4 {
76 let mut rgb = Vec::with_capacity((w * h * 3) as usize);
77 for pixel in raw_buf.chunks_exact(4) {
78 let a = pixel[3] as u16;
79 let inv_a = 255 - a;
80 rgb.push(((pixel[0] as u16 * a + 255 * inv_a) / 255) as u8);
81 rgb.push(((pixel[1] as u16 * a + 255 * inv_a) / 255) as u8);
82 rgb.push(((pixel[2] as u16 * a + 255 * inv_a) / 255) as u8);
83 }
84 rgb
85 } else {
86 raw_buf
88 };
89
90 let mut encoder = ZlibEncoder::new(Vec::new(), compression);
91 encoder
92 .write_all(&rgb_buf)
93 .map_err(|e| format!("Failed to compress: {}", e))?;
94 let compressed = encoder
95 .finish()
96 .map_err(|e| format!("Failed to finish compression: {}", e))?;
97
98 Ok((compressed, w, h))
99}
100
101fn build_pdf(
103 prepared_pages: &[(Vec<u8>, u32, u32)],
104 page_w_pt: f64,
105 page_h_pt: f64,
106) -> Result<Vec<u8>, String> {
107 let mut doc = Document::with_version("1.5");
108 let pages_id = doc.new_object_id();
109 let mut page_ids: Vec<Object> = Vec::with_capacity(prepared_pages.len());
110
111 for (compressed_pixels, img_w, img_h) in prepared_pages {
112 let img_stream = Stream::new(
113 dictionary! {
114 "Type" => "XObject",
115 "Subtype" => "Image",
116 "Width" => *img_w as i64,
117 "Height" => *img_h as i64,
118 "ColorSpace" => "DeviceRGB",
119 "BitsPerComponent" => 8,
120 "Filter" => "FlateDecode",
121 "Length" => compressed_pixels.len() as i64,
122 },
123 compressed_pixels.clone(),
124 );
125 let img_id = doc.add_object(img_stream);
126
127 let content = Content {
128 operations: vec![
129 Operation::new("q", vec![]),
130 Operation::new(
131 "cm",
132 vec![
133 page_w_pt.into(),
134 0.into(),
135 0.into(),
136 page_h_pt.into(),
137 0.into(),
138 0.into(),
139 ],
140 ),
141 Operation::new("Do", vec!["Im0".into()]),
142 Operation::new("Q", vec![]),
143 ],
144 };
145 let content_bytes = content
146 .encode()
147 .map_err(|e| format!("Failed to encode content: {}", e))?;
148 let content_id = doc.add_object(Stream::new(dictionary! {}, content_bytes));
149
150 let resources = dictionary! {
151 "XObject" => dictionary! {
152 "Im0" => img_id,
153 },
154 };
155
156 let page_obj = dictionary! {
157 "Type" => "Page",
158 "Parent" => pages_id,
159 "MediaBox" => vec![
160 0.into(),
161 0.into(),
162 Object::Real(page_w_pt as f32),
163 Object::Real(page_h_pt as f32),
164 ],
165 "Contents" => content_id,
166 "Resources" => resources,
167 };
168 let page_id = doc.add_object(page_obj);
169 page_ids.push(page_id.into());
170 }
171
172 let pages_dict = dictionary! {
173 "Type" => "Pages",
174 "Count" => page_ids.len() as i64,
175 "Kids" => page_ids,
176 };
177 doc.objects.insert(pages_id, Object::Dictionary(pages_dict));
178
179 let catalog = dictionary! {
180 "Type" => "Catalog",
181 "Pages" => pages_id,
182 };
183 let catalog_id = doc.add_object(catalog);
184 doc.trailer.set("Root", catalog_id);
185 doc.compress();
186
187 let mut buf = BufWriter::new(Vec::new());
188 doc.save_to(&mut buf)
189 .map_err(|e| format!("Failed to save PDF: {}", e))?;
190
191 buf.into_inner()
192 .map_err(|e| format!("Failed to flush PDF buffer: {}", e))
193}
194
195pub fn png_merge_pages_to_pdf(
216 pages: &[Vec<u8>],
217 width_dots: f64,
218 height_dots: f64,
219 dpi: f32,
220 compression: Compression,
221) -> ZplResult<Vec<u8>> {
222 if pages.is_empty() {
223 return Err(ZplError::BackendError("No pages to merge".to_string()));
224 }
225
226 let dpi_f64 = if dpi == 0.0 { 203.2 } else { dpi as f64 };
227 let page_w_pt = (width_dots / dpi_f64) * 72.0;
228 let page_h_pt = (height_dots / dpi_f64) * 72.0;
229
230 let prepared: Vec<Result<PreparedPage, String>> = pages
232 .par_iter()
233 .map(|png_data| decode_and_compress_png(png_data, compression))
234 .collect();
235
236 let prepared: Vec<(Vec<u8>, u32, u32)> = prepared
238 .into_iter()
239 .collect::<Result<Vec<_>, _>>()
240 .map_err(ZplError::BackendError)?;
241
242 let pdf_bytes = build_pdf(&prepared, page_w_pt, page_h_pt).map_err(ZplError::BackendError)?;
244
245 Ok(pdf_bytes)
246}
247
248impl ZplForgeBackend for PdfBackend {
249 fn setup_page(&mut self, width: f64, height: f64, resolution: f32) {
250 self.width_dots = width;
251 self.height_dots = height;
252 self.resolution = resolution;
253 self.png_backend.setup_page(width, height, resolution);
254 }
255
256 fn setup_font_manager(&mut self, font_manager: &FontManager) {
257 self.png_backend.setup_font_manager(font_manager);
258 }
259
260 fn draw_text(
261 &mut self,
262 x: u32,
263 y: u32,
264 font: char,
265 height: Option<u32>,
266 width: Option<u32>,
267 text: &str,
268 reverse_print: bool,
269 color: Option<String>,
270 ) -> ZplResult<()> {
271 self.png_backend
272 .draw_text(x, y, font, height, width, text, reverse_print, color)
273 }
274
275 fn draw_graphic_box(
276 &mut self,
277 x: u32,
278 y: u32,
279 width: u32,
280 height: u32,
281 thickness: u32,
282 color: char,
283 custom_color: Option<String>,
284 rounding: u32,
285 reverse_print: bool,
286 ) -> ZplResult<()> {
287 self.png_backend.draw_graphic_box(
288 x,
289 y,
290 width,
291 height,
292 thickness,
293 color,
294 custom_color,
295 rounding,
296 reverse_print,
297 )
298 }
299
300 fn draw_graphic_circle(
301 &mut self,
302 x: u32,
303 y: u32,
304 radius: u32,
305 thickness: u32,
306 color: char,
307 custom_color: Option<String>,
308 reverse_print: bool,
309 ) -> ZplResult<()> {
310 self.png_backend.draw_graphic_circle(
311 x,
312 y,
313 radius,
314 thickness,
315 color,
316 custom_color,
317 reverse_print,
318 )
319 }
320
321 fn draw_graphic_ellipse(
322 &mut self,
323 x: u32,
324 y: u32,
325 width: u32,
326 height: u32,
327 thickness: u32,
328 color: char,
329 custom_color: Option<String>,
330 reverse_print: bool,
331 ) -> ZplResult<()> {
332 self.png_backend.draw_graphic_ellipse(
333 x,
334 y,
335 width,
336 height,
337 thickness,
338 color,
339 custom_color,
340 reverse_print,
341 )
342 }
343
344 fn draw_graphic_field(
345 &mut self,
346 x: u32,
347 y: u32,
348 width: u32,
349 height: u32,
350 data: &[u8],
351 reverse_print: bool,
352 ) -> ZplResult<()> {
353 self.png_backend
354 .draw_graphic_field(x, y, width, height, data, reverse_print)
355 }
356
357 fn draw_graphic_image_custom(
358 &mut self,
359 x: u32,
360 y: u32,
361 width: u32,
362 height: u32,
363 data: &str,
364 ) -> ZplResult<()> {
365 self.png_backend
366 .draw_graphic_image_custom(x, y, width, height, data)
367 }
368
369 fn draw_code128(
370 &mut self,
371 x: u32,
372 y: u32,
373 orientation: char,
374 height: u32,
375 module_width: u32,
376 interpretation_line: char,
377 interpretation_line_above: char,
378 check_digit: char,
379 mode: char,
380 data: &str,
381 reverse_print: bool,
382 ) -> ZplResult<()> {
383 self.png_backend.draw_code128(
384 x,
385 y,
386 orientation,
387 height,
388 module_width,
389 interpretation_line,
390 interpretation_line_above,
391 check_digit,
392 mode,
393 data,
394 reverse_print,
395 )
396 }
397
398 fn draw_qr_code(
399 &mut self,
400 x: u32,
401 y: u32,
402 orientation: char,
403 model: u32,
404 magnification: u32,
405 error_correction: char,
406 mask: u32,
407 data: &str,
408 reverse_print: bool,
409 ) -> ZplResult<()> {
410 self.png_backend.draw_qr_code(
411 x,
412 y,
413 orientation,
414 model,
415 magnification,
416 error_correction,
417 mask,
418 data,
419 reverse_print,
420 )
421 }
422
423 fn draw_code39(
424 &mut self,
425 x: u32,
426 y: u32,
427 orientation: char,
428 check_digit: char,
429 height: u32,
430 module_width: u32,
431 interpretation_line: char,
432 interpretation_line_above: char,
433 data: &str,
434 reverse_print: bool,
435 ) -> ZplResult<()> {
436 self.png_backend.draw_code39(
437 x,
438 y,
439 orientation,
440 check_digit,
441 height,
442 module_width,
443 interpretation_line,
444 interpretation_line_above,
445 data,
446 reverse_print,
447 )
448 }
449
450 fn finalize(&mut self) -> ZplResult<Vec<u8>> {
451 let png_data = self.png_backend.finalize()?;
452 png_merge_pages_to_pdf(
453 &[png_data],
454 self.width_dots,
455 self.height_dots,
456 self.resolution,
457 self.compression,
458 )
459 }
460}