1use crate::objects::{Dictionary, Object};
7use crate::{PdfError, Result};
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12#[derive(Debug, Clone)]
14pub struct Image {
15 data: Vec<u8>,
17 format: ImageFormat,
19 width: u32,
21 height: u32,
23 color_space: ColorSpace,
25 bits_per_component: u8,
27 alpha_data: Option<Vec<u8>>,
29 soft_mask: Option<Box<Image>>,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum ImageFormat {
36 Jpeg,
38 Png,
40 Tiff,
42 Raw,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq)]
48pub enum MaskType {
49 Soft,
51 Stencil,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq)]
57pub enum ColorSpace {
58 DeviceGray,
60 DeviceRGB,
62 DeviceCMYK,
64}
65
66impl Image {
67 pub fn from_jpeg_file<P: AsRef<Path>>(path: P) -> Result<Self> {
69 let mut file = File::open(path)?;
70 let mut data = Vec::new();
71 file.read_to_end(&mut data)?;
72 Self::from_jpeg_data(data)
73 }
74
75 pub fn from_jpeg_data(data: Vec<u8>) -> Result<Self> {
77 let (width, height, color_space, bits_per_component) = parse_jpeg_header(&data)?;
79
80 Ok(Image {
81 data,
82 format: ImageFormat::Jpeg,
83 width,
84 height,
85 color_space,
86 bits_per_component,
87 alpha_data: None,
88 soft_mask: None,
89 })
90 }
91
92 pub fn from_png_file<P: AsRef<Path>>(path: P) -> Result<Self> {
94 let mut file = File::open(path)?;
95 let mut data = Vec::new();
96 file.read_to_end(&mut data)?;
97 Self::from_png_data(data)
98 }
99
100 pub fn from_png_data(data: Vec<u8>) -> Result<Self> {
102 use crate::graphics::png_decoder::{decode_png, PngColorType};
103
104 let decoded = decode_png(&data)?;
106
107 let color_space = match decoded.color_type {
109 PngColorType::Grayscale | PngColorType::GrayscaleAlpha => ColorSpace::DeviceGray,
110 PngColorType::Rgb | PngColorType::RgbAlpha | PngColorType::Palette => {
111 ColorSpace::DeviceRGB
112 }
113 };
114
115 let soft_mask = if let Some(alpha) = &decoded.alpha_data {
117 Some(Box::new(Image {
118 data: alpha.clone(),
119 format: ImageFormat::Raw,
120 width: decoded.width,
121 height: decoded.height,
122 color_space: ColorSpace::DeviceGray,
123 bits_per_component: 8,
124 alpha_data: None,
125 soft_mask: None,
126 }))
127 } else {
128 None
129 };
130
131 Ok(Image {
132 data: decoded.image_data, format: ImageFormat::Png,
134 width: decoded.width,
135 height: decoded.height,
136 color_space,
137 bits_per_component: 8, alpha_data: decoded.alpha_data,
139 soft_mask,
140 })
141 }
142
143 pub fn from_tiff_file<P: AsRef<Path>>(path: P) -> Result<Self> {
145 let mut file = File::open(path)?;
146 let mut data = Vec::new();
147 file.read_to_end(&mut data)?;
148 Self::from_tiff_data(data)
149 }
150
151 pub fn from_tiff_data(data: Vec<u8>) -> Result<Self> {
153 let (width, height, color_space, bits_per_component) = parse_tiff_header(&data)?;
155
156 Ok(Image {
157 data,
158 format: ImageFormat::Tiff,
159 width,
160 height,
161 color_space,
162 bits_per_component,
163 alpha_data: None,
164 soft_mask: None,
165 })
166 }
167
168 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
181 let path = path.as_ref();
182 let ext = path
183 .extension()
184 .and_then(|e| e.to_str())
185 .map(|e| e.to_ascii_lowercase())
186 .unwrap_or_default();
187
188 match ext.as_str() {
189 "jpg" | "jpeg" => Self::from_jpeg_file(path),
190 "png" => Self::from_png_file(path),
191 "tif" | "tiff" => Self::from_tiff_file(path),
192 _ => Err(crate::PdfError::InvalidFormat(format!(
193 "Unsupported image format: .{ext}. Supported: jpg, jpeg, png, tif, tiff"
194 ))),
195 }
196 }
197
198 pub fn width(&self) -> u32 {
200 self.width
201 }
202
203 pub fn height(&self) -> u32 {
205 self.height
206 }
207
208 pub fn data(&self) -> &[u8] {
210 &self.data
211 }
212
213 pub fn format(&self) -> ImageFormat {
215 self.format
216 }
217
218 pub fn bits_per_component(&self) -> u8 {
220 self.bits_per_component
221 }
222
223 pub fn from_raw_data(
225 data: Vec<u8>,
226 width: u32,
227 height: u32,
228 color_space: ColorSpace,
229 bits_per_component: u8,
230 ) -> Self {
231 Image {
232 data,
233 format: ImageFormat::Raw,
234 width,
235 height,
236 color_space,
237 bits_per_component,
238 alpha_data: None,
239 soft_mask: None,
240 }
241 }
242
243 pub fn from_rgba_data(rgba_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
245 if rgba_data.len() != (width * height * 4) as usize {
246 return Err(PdfError::InvalidImage(
247 "RGBA data size doesn't match dimensions".to_string(),
248 ));
249 }
250
251 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
253 let mut alpha_data = Vec::with_capacity((width * height) as usize);
254
255 for chunk in rgba_data.chunks(4) {
256 rgb_data.push(chunk[0]); rgb_data.push(chunk[1]); rgb_data.push(chunk[2]); alpha_data.push(chunk[3]); }
261
262 let soft_mask = Some(Box::new(Image {
264 data: alpha_data.clone(),
265 format: ImageFormat::Raw,
266 width,
267 height,
268 color_space: ColorSpace::DeviceGray,
269 bits_per_component: 8,
270 alpha_data: None,
271 soft_mask: None,
272 }));
273
274 Ok(Image {
275 data: rgb_data,
276 format: ImageFormat::Raw,
277 width,
278 height,
279 color_space: ColorSpace::DeviceRGB,
280 bits_per_component: 8,
281 alpha_data: Some(alpha_data),
282 soft_mask,
283 })
284 }
285
286 pub fn from_gray_data(gray_data: Vec<u8>, width: u32, height: u32) -> Result<Self> {
288 if gray_data.len() != (width * height) as usize {
289 return Err(PdfError::InvalidImage(
290 "Gray data size doesn't match dimensions".to_string(),
291 ));
292 }
293
294 Ok(Image {
295 data: gray_data,
296 format: ImageFormat::Raw,
297 width,
298 height,
299 color_space: ColorSpace::DeviceGray,
300 bits_per_component: 8,
301 alpha_data: None,
302 soft_mask: None,
303 })
304 }
305
306 #[cfg(feature = "external-images")]
308 pub fn from_file_raw<P: AsRef<Path>>(
309 path: P,
310 width: u32,
311 height: u32,
312 format: ImageFormat,
313 ) -> Result<Self> {
314 let data = std::fs::read(path)
315 .map_err(|e| PdfError::InvalidImage(format!("Failed to read image file: {}", e)))?;
316
317 Ok(Image {
318 data,
319 format,
320 width,
321 height,
322 color_space: ColorSpace::DeviceRGB,
323 bits_per_component: 8,
324 alpha_data: None,
325 soft_mask: None,
326 })
327 }
328
329 pub fn to_pdf_object(&self) -> Object {
331 let mut dict = Dictionary::new();
332
333 dict.set("Type", Object::Name("XObject".to_string()));
335 dict.set("Subtype", Object::Name("Image".to_string()));
336 dict.set("Width", Object::Integer(self.width as i64));
337 dict.set("Height", Object::Integer(self.height as i64));
338
339 let color_space_name = match self.color_space {
341 ColorSpace::DeviceGray => "DeviceGray",
342 ColorSpace::DeviceRGB => "DeviceRGB",
343 ColorSpace::DeviceCMYK => "DeviceCMYK",
344 };
345 dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
346
347 dict.set(
349 "BitsPerComponent",
350 Object::Integer(self.bits_per_component as i64),
351 );
352
353 match self.format {
355 ImageFormat::Jpeg => {
356 dict.set("Filter", Object::Name("DCTDecode".to_string()));
357 dict.set("Length", Object::Integer(self.data.len() as i64));
358 Object::Stream(dict, self.data.clone())
359 }
360 ImageFormat::Png => {
361 use flate2::write::ZlibEncoder;
362 use flate2::Compression;
363 use std::io::Write as IoWrite;
364 dict.set("Filter", Object::Name("FlateDecode".to_string()));
366 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
367 encoder
368 .write_all(&self.data)
369 .expect("zlib compression failed");
370 let compressed = encoder.finish().expect("zlib finish failed");
371 dict.set("Length", Object::Integer(compressed.len() as i64));
372 Object::Stream(dict, compressed)
373 }
374 ImageFormat::Tiff => {
375 use flate2::write::ZlibEncoder;
376 use flate2::Compression;
377 use std::io::Write as IoWrite;
378 dict.set("Filter", Object::Name("FlateDecode".to_string()));
380 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
381 encoder
382 .write_all(&self.data)
383 .expect("zlib compression failed");
384 let compressed = encoder.finish().expect("zlib finish failed");
385 dict.set("Length", Object::Integer(compressed.len() as i64));
386 Object::Stream(dict, compressed)
387 }
388 ImageFormat::Raw => {
389 dict.set("Length", Object::Integer(self.data.len() as i64));
391 Object::Stream(dict, self.data.clone())
392 }
393 }
394 }
395
396 pub fn to_pdf_object_with_transparency(&self) -> Result<(Object, Option<Object>)> {
398 use flate2::write::ZlibEncoder;
399 use flate2::Compression;
400 use std::io::Write as IoWrite;
401
402 let mut main_dict = Dictionary::new();
403
404 main_dict.set("Type", Object::Name("XObject".to_string()));
406 main_dict.set("Subtype", Object::Name("Image".to_string()));
407 main_dict.set("Width", Object::Integer(self.width as i64));
408 main_dict.set("Height", Object::Integer(self.height as i64));
409
410 let color_space_name = match self.color_space {
412 ColorSpace::DeviceGray => "DeviceGray",
413 ColorSpace::DeviceRGB => "DeviceRGB",
414 ColorSpace::DeviceCMYK => "DeviceCMYK",
415 };
416 main_dict.set("ColorSpace", Object::Name(color_space_name.to_string()));
417
418 main_dict.set(
420 "BitsPerComponent",
421 Object::Integer(self.bits_per_component as i64),
422 );
423
424 let main_data = match self.format {
426 ImageFormat::Jpeg => {
427 main_dict.set("Filter", Object::Name("DCTDecode".to_string()));
428 self.data.clone()
429 }
430 ImageFormat::Png | ImageFormat::Raw => {
431 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
433 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
434 encoder.write_all(&self.data).map_err(|e| {
435 PdfError::InvalidImage(format!("Failed to compress image data: {}", e))
436 })?;
437 encoder.finish().map_err(|e| {
438 PdfError::InvalidImage(format!("Failed to finalize image compression: {}", e))
439 })?
440 }
441 ImageFormat::Tiff => {
442 main_dict.set("Filter", Object::Name("FlateDecode".to_string()));
443 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
444 encoder.write_all(&self.data).map_err(|e| {
445 PdfError::InvalidImage(format!("Failed to compress TIFF data: {}", e))
446 })?;
447 encoder.finish().map_err(|e| {
448 PdfError::InvalidImage(format!("Failed to finalize TIFF compression: {}", e))
449 })?
450 }
451 };
452
453 main_dict.set("Length", Object::Integer(main_data.len() as i64));
455
456 let smask_obj = if let Some(mask) = &self.soft_mask {
458 let mut mask_dict = Dictionary::new();
459 mask_dict.set("Type", Object::Name("XObject".to_string()));
460 mask_dict.set("Subtype", Object::Name("Image".to_string()));
461 mask_dict.set("Width", Object::Integer(mask.width as i64));
462 mask_dict.set("Height", Object::Integer(mask.height as i64));
463 mask_dict.set("ColorSpace", Object::Name("DeviceGray".to_string()));
464 mask_dict.set("BitsPerComponent", Object::Integer(8));
465 mask_dict.set("Filter", Object::Name("FlateDecode".to_string()));
466
467 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
469 encoder.write_all(&mask.data).map_err(|e| {
470 PdfError::InvalidImage(format!("Failed to compress alpha channel: {}", e))
471 })?;
472 let compressed_mask_data = encoder.finish().map_err(|e| {
473 PdfError::InvalidImage(format!("Failed to finalize alpha compression: {}", e))
474 })?;
475
476 mask_dict.set("Length", Object::Integer(compressed_mask_data.len() as i64));
478
479 Some(Object::Stream(mask_dict, compressed_mask_data))
480 } else {
481 None
482 };
483
484 Ok((Object::Stream(main_dict, main_data), smask_obj))
488 }
489
490 pub fn has_transparency(&self) -> bool {
492 self.soft_mask.is_some() || self.alpha_data.is_some()
493 }
494
495 pub fn create_stencil_mask(&self, threshold: u8) -> Option<Image> {
498 if let Some(alpha) = &self.alpha_data {
499 let mut mask_data = Vec::new();
501 let mut current_byte = 0u8;
502 let mut bit_count = 0;
503
504 for &alpha_value in alpha.iter() {
505 if alpha_value > threshold {
507 current_byte |= 1 << (7 - bit_count);
508 }
509
510 bit_count += 1;
511 if bit_count == 8 {
512 mask_data.push(current_byte);
513 current_byte = 0;
514 bit_count = 0;
515 }
516 }
517
518 if bit_count > 0 {
520 mask_data.push(current_byte);
521 }
522
523 Some(Image {
524 data: mask_data,
525 format: ImageFormat::Raw,
526 width: self.width,
527 height: self.height,
528 color_space: ColorSpace::DeviceGray,
529 bits_per_component: 1,
530 alpha_data: None,
531 soft_mask: None,
532 })
533 } else {
534 None
535 }
536 }
537
538 pub fn create_mask(&self, mask_type: MaskType, threshold: Option<u8>) -> Option<Image> {
540 match mask_type {
541 MaskType::Soft => self.soft_mask.as_ref().map(|m| m.as_ref().clone()),
542 MaskType::Stencil => self.create_stencil_mask(threshold.unwrap_or(128)),
543 }
544 }
545
546 pub fn with_mask(mut self, mask: Image, mask_type: MaskType) -> Self {
548 match mask_type {
549 MaskType::Soft => {
550 self.soft_mask = Some(Box::new(mask));
551 }
552 MaskType::Stencil => {
553 self.soft_mask = Some(Box::new(mask));
555 }
556 }
557 self
558 }
559
560 pub fn soft_mask(&self) -> Option<&Image> {
562 self.soft_mask.as_ref().map(|m| m.as_ref())
563 }
564
565 pub fn alpha_data(&self) -> Option<&[u8]> {
567 self.alpha_data.as_deref()
568 }
569}
570
571fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
573 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
574 return Err(PdfError::InvalidImage("Not a valid JPEG file".to_string()));
575 }
576
577 let mut pos = 2;
578 let mut width = 0;
579 let mut height = 0;
580 let mut components = 0;
581
582 while pos < data.len() - 1 {
583 if data[pos] != 0xFF {
584 return Err(PdfError::InvalidImage("Invalid JPEG marker".to_string()));
585 }
586
587 let marker = data[pos + 1];
588 pos += 2;
589
590 if marker == 0xFF {
592 continue;
593 }
594
595 if (0xC0..=0xCF).contains(&marker) && marker != 0xC4 && marker != 0xC8 && marker != 0xCC {
597 if pos + 7 >= data.len() {
599 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
600 }
601
602 pos += 2;
604
605 pos += 1;
607
608 height = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
610 pos += 2;
611 width = ((data[pos] as u32) << 8) | (data[pos + 1] as u32);
612 pos += 2;
613
614 components = data[pos];
616 break;
617 } else if marker == 0xD9 {
618 break;
620 } else if marker == 0xD8 || (0xD0..=0xD7).contains(&marker) {
621 continue;
623 } else {
624 if pos + 1 >= data.len() {
626 return Err(PdfError::InvalidImage("Truncated JPEG file".to_string()));
627 }
628 let length = ((data[pos] as usize) << 8) | (data[pos + 1] as usize);
629 pos += length;
630 }
631 }
632
633 if width == 0 || height == 0 {
634 return Err(PdfError::InvalidImage(
635 "Could not find image dimensions".to_string(),
636 ));
637 }
638
639 let color_space = match components {
640 1 => ColorSpace::DeviceGray,
641 3 => ColorSpace::DeviceRGB,
642 4 => ColorSpace::DeviceCMYK,
643 _ => {
644 return Err(PdfError::InvalidImage(format!(
645 "Unsupported number of components: {components}"
646 )))
647 }
648 };
649
650 Ok((width, height, color_space, 8)) }
652
653#[allow(dead_code)]
655fn parse_png_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
656 if data.len() < 8 || &data[0..8] != b"\x89PNG\r\n\x1a\n" {
658 return Err(PdfError::InvalidImage("Not a valid PNG file".to_string()));
659 }
660
661 let mut pos = 8;
663
664 while pos + 8 < data.len() {
665 let chunk_length =
667 u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
668
669 let chunk_type = &data[pos + 4..pos + 8];
671
672 if chunk_type == b"IHDR" {
673 if pos + 8 + chunk_length > data.len() || chunk_length < 13 {
675 return Err(PdfError::InvalidImage("Invalid PNG IHDR chunk".to_string()));
676 }
677
678 let ihdr_data = &data[pos + 8..pos + 8 + chunk_length];
679
680 let width =
682 u32::from_be_bytes([ihdr_data[0], ihdr_data[1], ihdr_data[2], ihdr_data[3]]);
683
684 let height =
685 u32::from_be_bytes([ihdr_data[4], ihdr_data[5], ihdr_data[6], ihdr_data[7]]);
686
687 let bit_depth = ihdr_data[8];
688 let color_type = ihdr_data[9];
689
690 let color_space = match color_type {
692 0 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 3 => ColorSpace::DeviceRGB, 4 => ColorSpace::DeviceGray, 6 => ColorSpace::DeviceRGB, _ => {
698 return Err(PdfError::InvalidImage(format!(
699 "Unsupported PNG color type: {color_type}"
700 )))
701 }
702 };
703
704 return Ok((width, height, color_space, bit_depth));
705 }
706
707 pos += 8 + chunk_length + 4; }
710
711 Err(PdfError::InvalidImage(
712 "PNG IHDR chunk not found".to_string(),
713 ))
714}
715
716fn parse_tiff_header(data: &[u8]) -> Result<(u32, u32, ColorSpace, u8)> {
718 if data.len() < 8 {
719 return Err(PdfError::InvalidImage(
720 "Invalid TIFF file: too short".to_string(),
721 ));
722 }
723
724 let (is_little_endian, offset) = if &data[0..2] == b"II" {
726 (true, 2) } else if &data[0..2] == b"MM" {
728 (false, 2) } else {
730 return Err(PdfError::InvalidImage(
731 "Invalid TIFF byte order".to_string(),
732 ));
733 };
734
735 let magic = if is_little_endian {
737 u16::from_le_bytes([data[offset], data[offset + 1]])
738 } else {
739 u16::from_be_bytes([data[offset], data[offset + 1]])
740 };
741
742 if magic != 42 {
743 return Err(PdfError::InvalidImage(
744 "Invalid TIFF magic number".to_string(),
745 ));
746 }
747
748 let ifd_offset = if is_little_endian {
750 u32::from_le_bytes([
751 data[offset + 2],
752 data[offset + 3],
753 data[offset + 4],
754 data[offset + 5],
755 ])
756 } else {
757 u32::from_be_bytes([
758 data[offset + 2],
759 data[offset + 3],
760 data[offset + 4],
761 data[offset + 5],
762 ])
763 } as usize;
764
765 if ifd_offset + 2 > data.len() {
766 return Err(PdfError::InvalidImage(
767 "Invalid TIFF IFD offset".to_string(),
768 ));
769 }
770
771 let num_entries = if is_little_endian {
773 u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
774 } else {
775 u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
776 };
777
778 let mut width = 0u32;
779 let mut height = 0u32;
780 let mut bits_per_sample = 8u16;
781 let mut photometric_interpretation = 0u16;
782
783 for i in 0..num_entries {
785 let entry_offset = ifd_offset + 2 + (i as usize * 12);
786
787 if entry_offset + 12 > data.len() {
788 break;
789 }
790
791 let tag = if is_little_endian {
792 u16::from_le_bytes([data[entry_offset], data[entry_offset + 1]])
793 } else {
794 u16::from_be_bytes([data[entry_offset], data[entry_offset + 1]])
795 };
796
797 let value_offset = entry_offset + 8;
798
799 match tag {
800 256 => {
801 width = if is_little_endian {
803 u32::from_le_bytes([
804 data[value_offset],
805 data[value_offset + 1],
806 data[value_offset + 2],
807 data[value_offset + 3],
808 ])
809 } else {
810 u32::from_be_bytes([
811 data[value_offset],
812 data[value_offset + 1],
813 data[value_offset + 2],
814 data[value_offset + 3],
815 ])
816 };
817 }
818 257 => {
819 height = if is_little_endian {
821 u32::from_le_bytes([
822 data[value_offset],
823 data[value_offset + 1],
824 data[value_offset + 2],
825 data[value_offset + 3],
826 ])
827 } else {
828 u32::from_be_bytes([
829 data[value_offset],
830 data[value_offset + 1],
831 data[value_offset + 2],
832 data[value_offset + 3],
833 ])
834 };
835 }
836 258 => {
837 bits_per_sample = if is_little_endian {
839 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
840 } else {
841 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
842 };
843 }
844 262 => {
845 photometric_interpretation = if is_little_endian {
847 u16::from_le_bytes([data[value_offset], data[value_offset + 1]])
848 } else {
849 u16::from_be_bytes([data[value_offset], data[value_offset + 1]])
850 };
851 }
852 _ => {} }
854 }
855
856 if width == 0 || height == 0 {
857 return Err(PdfError::InvalidImage(
858 "TIFF dimensions not found".to_string(),
859 ));
860 }
861
862 let color_space = match photometric_interpretation {
864 0 | 1 => ColorSpace::DeviceGray, 2 => ColorSpace::DeviceRGB, 5 => ColorSpace::DeviceCMYK, _ => ColorSpace::DeviceRGB, };
869
870 Ok((width, height, color_space, bits_per_sample as u8))
871}
872
873#[cfg(test)]
874mod tests {
875 use super::*;
876
877 fn create_minimal_png(width: u32, height: u32, color_type: u8) -> Vec<u8> {
879 match color_type {
882 0 => {
883 vec![
885 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x7E, 0x9B, 0x55, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00,
896 0x01, 0xE2, 0xF9, 0x8C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
902 }
903 2 => {
904 vec![
906 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, 0x00,
917 0x01, 0x27, 0x18, 0xAA, 0x61, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
923 }
924 3 => {
925 vec![
927 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x03, 0x00, 0x00, 0x00, 0xDB, 0xB4, 0x05, 0x70, 0x00, 0x00, 0x00, 0x03, 0x50, 0x4C, 0x54, 0x45, 0xFF, 0x00, 0x00, 0x19, 0xE2, 0x09, 0x37, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00,
942 0x01, 0xE5, 0x27, 0xDE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
948 }
949 6 => {
950 vec![
952 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0B, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x63, 0x60, 0x00, 0x02, 0x00, 0x00, 0x05, 0x00,
963 0x01, 0x75, 0xAA, 0x50, 0x19, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
969 }
970 _ => {
971 create_minimal_png(width, height, 2)
973 }
974 }
975 }
976
977 #[test]
978 fn test_parse_jpeg_header() {
979 let jpeg_data = vec![
981 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, ];
990
991 let result = parse_jpeg_header(&jpeg_data);
992 assert!(result.is_ok());
993 let (width, height, color_space, bits) = result.unwrap();
994 assert_eq!(width, 200);
995 assert_eq!(height, 100);
996 assert_eq!(color_space, ColorSpace::DeviceRGB);
997 assert_eq!(bits, 8);
998 }
999
1000 #[test]
1001 fn test_invalid_jpeg() {
1002 let invalid_data = vec![0x00, 0x00];
1003 let result = parse_jpeg_header(&invalid_data);
1004 assert!(result.is_err());
1005 }
1006
1007 #[test]
1008 fn test_parse_png_header() {
1009 let mut png_data = vec![
1011 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x08, 0x02, 0x00, 0x00, 0x00, ];
1022
1023 png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
1025
1026 let result = parse_png_header(&png_data);
1027 assert!(result.is_ok());
1028 let (width, height, color_space, bits) = result.unwrap();
1029 assert_eq!(width, 100);
1030 assert_eq!(height, 100);
1031 assert_eq!(color_space, ColorSpace::DeviceRGB);
1032 assert_eq!(bits, 8);
1033 }
1034
1035 #[test]
1036 fn test_invalid_png() {
1037 let invalid_data = vec![0x00, 0x00];
1038 let result = parse_png_header(&invalid_data);
1039 assert!(result.is_err());
1040 }
1041
1042 #[test]
1043 fn test_parse_tiff_header_little_endian() {
1044 let tiff_data = vec![
1046 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1052 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
1054 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1056 0x00, 0x00, ];
1058
1059 let result = parse_tiff_header(&tiff_data);
1060 assert!(result.is_ok());
1061 let (width, height, color_space, bits) = result.unwrap();
1062 assert_eq!(width, 100);
1063 assert_eq!(height, 100);
1064 assert_eq!(color_space, ColorSpace::DeviceGray);
1065 assert_eq!(bits, 8);
1066 }
1067
1068 #[test]
1069 fn test_parse_tiff_header_big_endian() {
1070 let tiff_data = vec![
1072 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1078 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
1080 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,
1082 0x00, 0x00, ];
1084
1085 let result = parse_tiff_header(&tiff_data);
1086 assert!(result.is_ok());
1087 let (width, height, color_space, bits) = result.unwrap();
1088 assert_eq!(width, 100);
1089 assert_eq!(height, 100);
1090 assert_eq!(color_space, ColorSpace::DeviceGray);
1091 assert_eq!(bits, 8);
1092 }
1093
1094 #[test]
1095 fn test_invalid_tiff() {
1096 let invalid_data = vec![0x00, 0x00];
1097 let result = parse_tiff_header(&invalid_data);
1098 assert!(result.is_err());
1099 }
1100
1101 #[test]
1102 fn test_image_format_enum() {
1103 assert_eq!(ImageFormat::Jpeg, ImageFormat::Jpeg);
1104 assert_eq!(ImageFormat::Png, ImageFormat::Png);
1105 assert_eq!(ImageFormat::Tiff, ImageFormat::Tiff);
1106 assert_ne!(ImageFormat::Jpeg, ImageFormat::Png);
1107 }
1108
1109 mod comprehensive_tests {
1111 use super::*;
1112 use std::fs;
1113 use tempfile::TempDir;
1114
1115 #[test]
1116 fn test_image_format_variants() {
1117 let jpeg = ImageFormat::Jpeg;
1119 let png = ImageFormat::Png;
1120 let tiff = ImageFormat::Tiff;
1121
1122 assert_eq!(jpeg, ImageFormat::Jpeg);
1123 assert_eq!(png, ImageFormat::Png);
1124 assert_eq!(tiff, ImageFormat::Tiff);
1125
1126 assert_ne!(jpeg, png);
1127 assert_ne!(png, tiff);
1128 assert_ne!(tiff, jpeg);
1129 }
1130
1131 #[test]
1132 fn test_image_format_debug() {
1133 let jpeg = ImageFormat::Jpeg;
1134 let png = ImageFormat::Png;
1135 let tiff = ImageFormat::Tiff;
1136
1137 assert_eq!(format!("{jpeg:?}"), "Jpeg");
1138 assert_eq!(format!("{png:?}"), "Png");
1139 assert_eq!(format!("{tiff:?}"), "Tiff");
1140 }
1141
1142 #[test]
1143 fn test_image_format_clone_copy() {
1144 let jpeg = ImageFormat::Jpeg;
1145 let jpeg_clone = jpeg;
1146 let jpeg_copy = jpeg;
1147
1148 assert_eq!(jpeg_clone, ImageFormat::Jpeg);
1149 assert_eq!(jpeg_copy, ImageFormat::Jpeg);
1150 }
1151
1152 #[test]
1153 fn test_color_space_variants() {
1154 let gray = ColorSpace::DeviceGray;
1156 let rgb = ColorSpace::DeviceRGB;
1157 let cmyk = ColorSpace::DeviceCMYK;
1158
1159 assert_eq!(gray, ColorSpace::DeviceGray);
1160 assert_eq!(rgb, ColorSpace::DeviceRGB);
1161 assert_eq!(cmyk, ColorSpace::DeviceCMYK);
1162
1163 assert_ne!(gray, rgb);
1164 assert_ne!(rgb, cmyk);
1165 assert_ne!(cmyk, gray);
1166 }
1167
1168 #[test]
1169 fn test_color_space_debug() {
1170 let gray = ColorSpace::DeviceGray;
1171 let rgb = ColorSpace::DeviceRGB;
1172 let cmyk = ColorSpace::DeviceCMYK;
1173
1174 assert_eq!(format!("{gray:?}"), "DeviceGray");
1175 assert_eq!(format!("{rgb:?}"), "DeviceRGB");
1176 assert_eq!(format!("{cmyk:?}"), "DeviceCMYK");
1177 }
1178
1179 #[test]
1180 fn test_color_space_clone_copy() {
1181 let rgb = ColorSpace::DeviceRGB;
1182 let rgb_clone = rgb;
1183 let rgb_copy = rgb;
1184
1185 assert_eq!(rgb_clone, ColorSpace::DeviceRGB);
1186 assert_eq!(rgb_copy, ColorSpace::DeviceRGB);
1187 }
1188
1189 #[test]
1190 fn test_image_from_jpeg_data() {
1191 let jpeg_data = vec![
1193 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1205
1206 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1207
1208 assert_eq!(image.width(), 200);
1209 assert_eq!(image.height(), 100);
1210 assert_eq!(image.format(), ImageFormat::Jpeg);
1211 assert_eq!(image.data(), jpeg_data);
1212 }
1213
1214 #[test]
1215 fn test_image_from_png_data() {
1216 let mut png_data = Vec::new();
1218
1219 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1221
1222 png_data.extend_from_slice(&[
1224 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1234 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1236
1237 let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1242 use flate2::Compression;
1243 use std::io::Write;
1244
1245 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1246 encoder.write_all(&raw_data).unwrap();
1247 let compressed_data = encoder.finish().unwrap();
1248
1249 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1251 png_data.extend_from_slice(b"IDAT");
1252 png_data.extend_from_slice(&compressed_data);
1253 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1257 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1261
1262 let image = Image::from_png_data(png_data).unwrap();
1263
1264 assert_eq!(image.width(), 1);
1265 assert_eq!(image.height(), 1);
1266 assert_eq!(image.format(), ImageFormat::Png);
1267 assert_eq!(image.data(), &[0u8, 0, 0]);
1270 }
1271
1272 #[test]
1273 fn test_image_from_tiff_data() {
1274 let tiff_data = vec![
1276 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1282 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
1284 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1286 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
1288 0x00, 0x00, ];
1290
1291 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1292
1293 assert_eq!(image.width(), 128);
1294 assert_eq!(image.height(), 128);
1295 assert_eq!(image.format(), ImageFormat::Tiff);
1296 assert_eq!(image.data(), tiff_data);
1297 }
1298
1299 #[test]
1300 fn test_image_from_jpeg_file() {
1301 let temp_dir = TempDir::new().unwrap();
1302 let file_path = temp_dir.path().join("test.jpg");
1303
1304 let jpeg_data = vec![
1306 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1318
1319 fs::write(&file_path, &jpeg_data).unwrap();
1320
1321 let image = Image::from_jpeg_file(&file_path).unwrap();
1322
1323 assert_eq!(image.width(), 100);
1324 assert_eq!(image.height(), 50);
1325 assert_eq!(image.format(), ImageFormat::Jpeg);
1326 assert_eq!(image.data(), jpeg_data);
1327 }
1328
1329 #[test]
1330 fn test_image_from_png_file() {
1331 let temp_dir = TempDir::new().unwrap();
1332 let file_path = temp_dir.path().join("test.png");
1333
1334 let mut png_data = Vec::new();
1336
1337 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1339
1340 png_data.extend_from_slice(&[
1342 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, ]);
1352 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]); let raw_data = vec![0x00, 0x00, 0x00, 0x00]; use flate2::write::ZlibEncoder;
1358 use flate2::Compression;
1359 use std::io::Write;
1360
1361 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1362 encoder.write_all(&raw_data).unwrap();
1363 let compressed_data = encoder.finish().unwrap();
1364
1365 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1366 png_data.extend_from_slice(b"IDAT");
1367 png_data.extend_from_slice(&compressed_data);
1368 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); png_data.extend_from_slice(&[
1372 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]);
1376
1377 fs::write(&file_path, &png_data).unwrap();
1378
1379 let image = Image::from_png_file(&file_path).unwrap();
1380
1381 assert_eq!(image.width(), 1);
1382 assert_eq!(image.height(), 1);
1383 assert_eq!(image.format(), ImageFormat::Png);
1384 assert_eq!(image.data(), &[0u8, 0, 0]);
1387 }
1388
1389 #[test]
1390 fn test_image_from_tiff_file() {
1391 let temp_dir = TempDir::new().unwrap();
1392 let file_path = temp_dir.path().join("test.tiff");
1393
1394 let tiff_data = vec![
1396 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1402 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
1404 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1406 0x00, 0x00, ];
1408
1409 fs::write(&file_path, &tiff_data).unwrap();
1410
1411 let image = Image::from_tiff_file(&file_path).unwrap();
1412
1413 assert_eq!(image.width(), 96);
1414 assert_eq!(image.height(), 96);
1415 assert_eq!(image.format(), ImageFormat::Tiff);
1416 assert_eq!(image.data(), tiff_data);
1417 }
1418
1419 #[test]
1420 fn test_image_from_file_jpeg() {
1421 let temp_dir = TempDir::new().unwrap();
1422 let file_path = temp_dir.path().join("test.jpg");
1423
1424 let jpeg_data = vec![
1425 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11,
1426 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9,
1427 ];
1428 fs::write(&file_path, &jpeg_data).unwrap();
1429
1430 let image = Image::from_file(&file_path).unwrap();
1431 assert_eq!(image.format(), ImageFormat::Jpeg);
1432 assert_eq!(image.width(), 100);
1433 }
1434
1435 #[test]
1436 fn test_image_from_file_uppercase_extension() {
1437 let temp_dir = TempDir::new().unwrap();
1438 let file_path = temp_dir.path().join("test.JPEG");
1439
1440 let jpeg_data = vec![
1441 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11,
1442 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9,
1443 ];
1444 fs::write(&file_path, &jpeg_data).unwrap();
1445
1446 let image = Image::from_file(&file_path).unwrap();
1447 assert_eq!(image.format(), ImageFormat::Jpeg);
1448 }
1449
1450 #[test]
1451 fn test_image_from_file_unsupported_extension() {
1452 let temp_dir = TempDir::new().unwrap();
1453 let file_path = temp_dir.path().join("test.bmp");
1454 fs::write(&file_path, b"dummy").unwrap();
1455
1456 let result = Image::from_file(&file_path);
1457 assert!(result.is_err());
1458 }
1459
1460 #[test]
1461 fn test_image_from_file_no_extension() {
1462 let temp_dir = TempDir::new().unwrap();
1463 let file_path = temp_dir.path().join("noext");
1464 fs::write(&file_path, b"dummy").unwrap();
1465
1466 let result = Image::from_file(&file_path);
1467 assert!(result.is_err());
1468 }
1469
1470 #[test]
1471 fn test_image_to_pdf_object_jpeg() {
1472 let jpeg_data = vec![
1473 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1485
1486 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
1487 let pdf_obj = image.to_pdf_object();
1488
1489 if let Object::Stream(dict, data) = pdf_obj {
1490 assert_eq!(
1491 dict.get("Type").unwrap(),
1492 &Object::Name("XObject".to_string())
1493 );
1494 assert_eq!(
1495 dict.get("Subtype").unwrap(),
1496 &Object::Name("Image".to_string())
1497 );
1498 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
1499 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
1500 assert_eq!(
1501 dict.get("ColorSpace").unwrap(),
1502 &Object::Name("DeviceRGB".to_string())
1503 );
1504 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1505 assert_eq!(
1506 dict.get("Filter").unwrap(),
1507 &Object::Name("DCTDecode".to_string())
1508 );
1509 assert_eq!(data, jpeg_data);
1510 } else {
1511 panic!("Expected Stream object");
1512 }
1513 }
1514
1515 #[test]
1516 fn test_image_to_pdf_object_png() {
1517 let mut png_data = Vec::new();
1519
1520 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
1521 png_data.extend_from_slice(&[
1522 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
1523 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00,
1524 ]);
1525 png_data.extend_from_slice(&[0x90, 0x77, 0x53, 0xDE]);
1526
1527 let raw_data = vec![0x00, 0x00, 0x00, 0x00];
1528 use flate2::write::ZlibEncoder;
1529 use flate2::Compression;
1530 use std::io::Write;
1531
1532 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1533 encoder.write_all(&raw_data).unwrap();
1534 let compressed_data = encoder.finish().unwrap();
1535
1536 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
1537 png_data.extend_from_slice(b"IDAT");
1538 png_data.extend_from_slice(&compressed_data);
1539 png_data.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
1540
1541 png_data.extend_from_slice(&[
1542 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
1543 ]);
1544
1545 let image = Image::from_png_data(png_data.clone()).unwrap();
1546 let pdf_obj = image.to_pdf_object();
1547
1548 if let Object::Stream(dict, data) = pdf_obj {
1549 assert_eq!(
1550 dict.get("Type").unwrap(),
1551 &Object::Name("XObject".to_string())
1552 );
1553 assert_eq!(
1554 dict.get("Subtype").unwrap(),
1555 &Object::Name("Image".to_string())
1556 );
1557 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(1));
1558 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(1));
1559 assert_eq!(
1560 dict.get("ColorSpace").unwrap(),
1561 &Object::Name("DeviceRGB".to_string())
1562 );
1563 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1564 assert_eq!(
1565 dict.get("Filter").unwrap(),
1566 &Object::Name("FlateDecode".to_string())
1567 );
1568 use flate2::read::ZlibDecoder;
1571 use std::io::Read as IoRead;
1572 let mut decoder = ZlibDecoder::new(data.as_slice());
1573 let mut decompressed = Vec::new();
1574 decoder.read_to_end(&mut decompressed).unwrap();
1575 assert_eq!(decompressed, vec![0u8, 0, 0]);
1576 } else {
1577 panic!("Expected Stream object");
1578 }
1579 }
1580
1581 #[test]
1582 fn test_image_to_pdf_object_tiff() {
1583 let tiff_data = vec![
1584 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1590 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
1592 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1594 0x00, 0x00, ];
1596
1597 let image = Image::from_tiff_data(tiff_data.clone()).unwrap();
1598 let pdf_obj = image.to_pdf_object();
1599
1600 if let Object::Stream(dict, data) = pdf_obj {
1601 assert_eq!(
1602 dict.get("Type").unwrap(),
1603 &Object::Name("XObject".to_string())
1604 );
1605 assert_eq!(
1606 dict.get("Subtype").unwrap(),
1607 &Object::Name("Image".to_string())
1608 );
1609 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(64));
1610 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(64));
1611 assert_eq!(
1612 dict.get("ColorSpace").unwrap(),
1613 &Object::Name("DeviceGray".to_string())
1614 );
1615 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
1616 assert_eq!(
1617 dict.get("Filter").unwrap(),
1618 &Object::Name("FlateDecode".to_string())
1619 );
1620 use flate2::read::ZlibDecoder;
1622 use std::io::Read as IoRead;
1623 let mut decoder = ZlibDecoder::new(data.as_slice());
1624 let mut decompressed = Vec::new();
1625 decoder.read_to_end(&mut decompressed).unwrap();
1626 assert_eq!(decompressed, tiff_data);
1627 } else {
1628 panic!("Expected Stream object");
1629 }
1630 }
1631
1632 #[test]
1633 fn test_image_clone() {
1634 let jpeg_data = vec![
1635 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1647
1648 let image1 = Image::from_jpeg_data(jpeg_data).unwrap();
1649 let image2 = image1.clone();
1650
1651 assert_eq!(image1.width(), image2.width());
1652 assert_eq!(image1.height(), image2.height());
1653 assert_eq!(image1.format(), image2.format());
1654 assert_eq!(image1.data(), image2.data());
1655 }
1656
1657 #[test]
1658 fn test_image_debug() {
1659 let jpeg_data = vec![
1660 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1672
1673 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1674 let debug_str = format!("{image:?}");
1675
1676 assert!(debug_str.contains("Image"));
1677 assert!(debug_str.contains("width"));
1678 assert!(debug_str.contains("height"));
1679 assert!(debug_str.contains("format"));
1680 }
1681
1682 #[test]
1683 fn test_jpeg_grayscale_image() {
1684 let jpeg_data = vec![
1685 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x01, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD9, ];
1696
1697 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1698 let pdf_obj = image.to_pdf_object();
1699
1700 if let Object::Stream(dict, _) = pdf_obj {
1701 assert_eq!(
1702 dict.get("ColorSpace").unwrap(),
1703 &Object::Name("DeviceGray".to_string())
1704 );
1705 } else {
1706 panic!("Expected Stream object");
1707 }
1708 }
1709
1710 #[test]
1711 fn test_jpeg_cmyk_image() {
1712 let jpeg_data = vec![
1713 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1725
1726 let image = Image::from_jpeg_data(jpeg_data).unwrap();
1727 let pdf_obj = image.to_pdf_object();
1728
1729 if let Object::Stream(dict, _) = pdf_obj {
1730 assert_eq!(
1731 dict.get("ColorSpace").unwrap(),
1732 &Object::Name("DeviceCMYK".to_string())
1733 );
1734 } else {
1735 panic!("Expected Stream object");
1736 }
1737 }
1738
1739 #[test]
1740 fn test_png_grayscale_image() {
1741 let png_data = create_minimal_png(1, 1, 0); let image = Image::from_png_data(png_data).unwrap();
1744 let pdf_obj = image.to_pdf_object();
1745
1746 if let Object::Stream(dict, _) = pdf_obj {
1747 assert_eq!(
1748 dict.get("ColorSpace").unwrap(),
1749 &Object::Name("DeviceGray".to_string())
1750 );
1751 } else {
1752 panic!("Expected Stream object");
1753 }
1754 }
1755
1756 #[test]
1757 fn test_png_palette_image_incomplete() {
1758 let png_data = vec![
1760 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x50, 0x08, 0x03, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
1772
1773 let result = Image::from_png_data(png_data);
1775 assert!(result.is_err(), "Incomplete PNG should return error");
1776 }
1777
1778 #[test]
1779 fn test_tiff_big_endian() {
1780 let tiff_data = vec![
1781 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1787 0x01, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80,
1789 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00,
1791 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
1793 0x00, 0x00, ];
1795
1796 let image = Image::from_tiff_data(tiff_data).unwrap();
1797
1798 assert_eq!(image.width(), 128);
1799 assert_eq!(image.height(), 128);
1800 assert_eq!(image.format(), ImageFormat::Tiff);
1801 }
1802
1803 #[test]
1804 fn test_tiff_cmyk_image() {
1805 let tiff_data = vec![
1806 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1812 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
1814 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
1816 0x06, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
1818 0x00, 0x00, ];
1820
1821 let image = Image::from_tiff_data(tiff_data).unwrap();
1822 let pdf_obj = image.to_pdf_object();
1823
1824 if let Object::Stream(dict, _) = pdf_obj {
1825 assert_eq!(
1826 dict.get("ColorSpace").unwrap(),
1827 &Object::Name("DeviceCMYK".to_string())
1828 );
1829 } else {
1830 panic!("Expected Stream object");
1831 }
1832 }
1833
1834 #[test]
1835 fn test_error_invalid_jpeg() {
1836 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_jpeg_data(invalid_data);
1838 assert!(result.is_err());
1839 }
1840
1841 #[test]
1842 fn test_error_invalid_png() {
1843 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_png_data(invalid_data);
1845 assert!(result.is_err());
1846 }
1847
1848 #[test]
1849 fn test_error_invalid_tiff() {
1850 let invalid_data = vec![0x00, 0x01, 0x02, 0x03]; let result = Image::from_tiff_data(invalid_data);
1852 assert!(result.is_err());
1853 }
1854
1855 #[test]
1856 fn test_error_truncated_jpeg() {
1857 let truncated_data = vec![0xFF, 0xD8, 0xFF]; let result = Image::from_jpeg_data(truncated_data);
1859 assert!(result.is_err());
1860 }
1861
1862 #[test]
1863 fn test_error_truncated_png() {
1864 let truncated_data = vec![0x89, 0x50, 0x4E, 0x47]; let result = Image::from_png_data(truncated_data);
1866 assert!(result.is_err());
1867 }
1868
1869 #[test]
1870 fn test_error_truncated_tiff() {
1871 let truncated_data = vec![0x49, 0x49, 0x2A]; let result = Image::from_tiff_data(truncated_data);
1873 assert!(result.is_err());
1874 }
1875
1876 #[test]
1877 fn test_error_jpeg_unsupported_components() {
1878 let invalid_jpeg = vec![
1879 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x64, 0x05, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
1891
1892 let result = Image::from_jpeg_data(invalid_jpeg);
1893 assert!(result.is_err());
1894 }
1895
1896 #[test]
1897 fn test_error_png_unsupported_color_type() {
1898 let invalid_png = vec![
1899 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x50, 0x08, 0x07, 0x00, 0x00, 0x00, 0x5C, 0x72, 0x6E, 0x38, ];
1911
1912 let result = Image::from_png_data(invalid_png);
1913 assert!(result.is_err());
1914 }
1915
1916 #[test]
1917 fn test_error_nonexistent_file() {
1918 let result = Image::from_jpeg_file("/nonexistent/path/image.jpg");
1919 assert!(result.is_err());
1920
1921 let result = Image::from_png_file("/nonexistent/path/image.png");
1922 assert!(result.is_err());
1923
1924 let result = Image::from_tiff_file("/nonexistent/path/image.tiff");
1925 assert!(result.is_err());
1926 }
1927
1928 #[test]
1929 fn test_jpeg_no_dimensions() {
1930 let jpeg_no_dims = vec![
1931 0xFF, 0xD8, 0xFF, 0xD9, ];
1934
1935 let result = Image::from_jpeg_data(jpeg_no_dims);
1936 assert!(result.is_err());
1937 }
1938
1939 #[test]
1940 fn test_png_no_ihdr() {
1941 let png_no_ihdr = vec![
1942 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x45, 0x4E, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C,
1946 0x72, 0x6E, 0x38, ];
1948
1949 let result = Image::from_png_data(png_no_ihdr);
1950 assert!(result.is_err());
1951 }
1952
1953 #[test]
1954 fn test_tiff_no_dimensions() {
1955 let tiff_no_dims = vec![
1956 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
1962 0x00, 0x00, ];
1964
1965 let result = Image::from_tiff_data(tiff_no_dims);
1966 assert!(result.is_err());
1967 }
1968
1969 fn png_crc32(data: &[u8]) -> u32 {
1971 let mut crc = 0xFFFFFFFF_u32;
1973 for &byte in data {
1974 crc ^= byte as u32;
1975 for _ in 0..8 {
1976 if crc & 1 != 0 {
1977 crc = (crc >> 1) ^ 0xEDB88320;
1978 } else {
1979 crc >>= 1;
1980 }
1981 }
1982 }
1983 !crc
1984 }
1985
1986 fn create_valid_png_data(
1988 width: u32,
1989 height: u32,
1990 bit_depth: u8,
1991 color_type: u8,
1992 ) -> Vec<u8> {
1993 use flate2::write::ZlibEncoder;
1994 use flate2::Compression;
1995 use std::io::Write;
1996
1997 let mut png_data = Vec::new();
1998
1999 png_data.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
2001
2002 let mut ihdr_data = Vec::new();
2004 ihdr_data.extend_from_slice(&width.to_be_bytes()); ihdr_data.extend_from_slice(&height.to_be_bytes()); ihdr_data.push(bit_depth); ihdr_data.push(color_type); ihdr_data.push(0); ihdr_data.push(0); ihdr_data.push(0); let mut ihdr_crc_data = Vec::new();
2014 ihdr_crc_data.extend_from_slice(b"IHDR");
2015 ihdr_crc_data.extend_from_slice(&ihdr_data);
2016 let ihdr_crc = png_crc32(&ihdr_crc_data);
2017
2018 png_data.extend_from_slice(&(ihdr_data.len() as u32).to_be_bytes());
2020 png_data.extend_from_slice(b"IHDR");
2021 png_data.extend_from_slice(&ihdr_data);
2022 png_data.extend_from_slice(&ihdr_crc.to_be_bytes());
2023
2024 let bytes_per_pixel = match (color_type, bit_depth) {
2026 (0, _) => (bit_depth / 8).max(1) as usize, (2, _) => (bit_depth * 3 / 8).max(3) as usize, (3, _) => 1, (4, _) => (bit_depth * 2 / 8).max(2) as usize, (6, _) => (bit_depth * 4 / 8).max(4) as usize, _ => 3,
2032 };
2033
2034 let mut raw_data = Vec::new();
2035 for _y in 0..height {
2036 raw_data.push(0); for _x in 0..width {
2038 for _c in 0..bytes_per_pixel {
2039 raw_data.push(0); }
2041 }
2042 }
2043
2044 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2046 encoder.write_all(&raw_data).unwrap();
2047 let compressed_data = encoder.finish().unwrap();
2048
2049 let mut idat_crc_data = Vec::new();
2051 idat_crc_data.extend_from_slice(b"IDAT");
2052 idat_crc_data.extend_from_slice(&compressed_data);
2053 let idat_crc = png_crc32(&idat_crc_data);
2054
2055 png_data.extend_from_slice(&(compressed_data.len() as u32).to_be_bytes());
2057 png_data.extend_from_slice(b"IDAT");
2058 png_data.extend_from_slice(&compressed_data);
2059 png_data.extend_from_slice(&idat_crc.to_be_bytes());
2060
2061 png_data.extend_from_slice(&[0, 0, 0, 0]); png_data.extend_from_slice(b"IEND");
2064 png_data.extend_from_slice(&[0xAE, 0x42, 0x60, 0x82]); png_data
2067 }
2068
2069 #[test]
2070 fn test_different_bit_depths() {
2071 let png_8bit = create_valid_png_data(2, 2, 8, 2); let image_8bit = Image::from_png_data(png_8bit).unwrap();
2076 let pdf_obj_8bit = image_8bit.to_pdf_object();
2077
2078 if let Object::Stream(dict, _) = pdf_obj_8bit {
2079 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2080 } else {
2081 panic!("Expected Stream object");
2082 }
2083
2084 let png_16bit = create_valid_png_data(2, 2, 16, 2); let image_16bit = Image::from_png_data(png_16bit).unwrap();
2087 let pdf_obj_16bit = image_16bit.to_pdf_object();
2088
2089 if let Object::Stream(dict, _) = pdf_obj_16bit {
2090 let bits = dict.get("BitsPerComponent").unwrap();
2092 assert!(matches!(bits, &Object::Integer(8) | &Object::Integer(16)));
2093 } else {
2094 panic!("Expected Stream object");
2095 }
2096 }
2097
2098 #[test]
2099 fn test_performance_large_image_data() {
2100 let mut large_jpeg = vec![
2102 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x04, 0x00, 0x04, 0x00, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, ];
2113
2114 large_jpeg.extend(vec![0x00; 10000]);
2116 large_jpeg.extend(vec![0xFF, 0xD9]); let start = std::time::Instant::now();
2119 let image = Image::from_jpeg_data(large_jpeg.clone()).unwrap();
2120 let duration = start.elapsed();
2121
2122 assert_eq!(image.width(), 1024);
2123 assert_eq!(image.height(), 1024);
2124 assert_eq!(image.data().len(), large_jpeg.len());
2125 assert!(duration.as_millis() < 100); }
2127
2128 #[test]
2129 fn test_memory_efficiency() {
2130 let jpeg_data = vec![
2131 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ];
2143
2144 let image = Image::from_jpeg_data(jpeg_data.clone()).unwrap();
2145
2146 assert_eq!(image.data().len(), jpeg_data.len());
2148 assert_eq!(image.data(), jpeg_data);
2149
2150 let cloned = image.clone();
2152 assert_eq!(cloned.data(), image.data());
2153 }
2154
2155 #[test]
2156 fn test_complete_workflow() {
2157 let test_cases = vec![
2159 (ImageFormat::Jpeg, "DCTDecode", "DeviceRGB"),
2160 (ImageFormat::Png, "FlateDecode", "DeviceRGB"),
2161 (ImageFormat::Tiff, "FlateDecode", "DeviceGray"),
2162 ];
2163
2164 for (expected_format, expected_filter, expected_color_space) in test_cases {
2165 let data = match expected_format {
2166 ImageFormat::Jpeg => vec![
2167 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xD9, ],
2179 ImageFormat::Png => create_valid_png_data(2, 2, 8, 2), ImageFormat::Tiff => vec![
2181 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
2187 0x01, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
2189 0x02, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
2191 0x00, 0x00, 0x00, 0x00, ],
2193 ImageFormat::Raw => Vec::new(), };
2195
2196 let image = match expected_format {
2197 ImageFormat::Jpeg => Image::from_jpeg_data(data.clone()).unwrap(),
2198 ImageFormat::Png => Image::from_png_data(data.clone()).unwrap(),
2199 ImageFormat::Tiff => Image::from_tiff_data(data.clone()).unwrap(),
2200 ImageFormat::Raw => continue, };
2202
2203 assert_eq!(image.format(), expected_format);
2205 if expected_format == ImageFormat::Png {
2207 assert_eq!(image.width(), 2);
2208 assert_eq!(image.height(), 2);
2209 } else if expected_format == ImageFormat::Jpeg {
2210 assert_eq!(image.width(), 200);
2211 assert_eq!(image.height(), 100);
2212 } else if expected_format == ImageFormat::Tiff {
2213 assert_eq!(image.width(), 200);
2214 assert_eq!(image.height(), 100);
2215 }
2216 if expected_format != ImageFormat::Png {
2218 assert_eq!(image.data(), data);
2219 }
2220
2221 let pdf_obj = image.to_pdf_object();
2223 if let Object::Stream(dict, stream_data) = pdf_obj {
2224 assert_eq!(
2225 dict.get("Type").unwrap(),
2226 &Object::Name("XObject".to_string())
2227 );
2228 assert_eq!(
2229 dict.get("Subtype").unwrap(),
2230 &Object::Name("Image".to_string())
2231 );
2232 if expected_format == ImageFormat::Png {
2234 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(2));
2235 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(2));
2236 } else {
2237 assert_eq!(dict.get("Width").unwrap(), &Object::Integer(200));
2238 assert_eq!(dict.get("Height").unwrap(), &Object::Integer(100));
2239 }
2240 assert_eq!(
2241 dict.get("ColorSpace").unwrap(),
2242 &Object::Name(expected_color_space.to_string())
2243 );
2244 assert_eq!(
2245 dict.get("Filter").unwrap(),
2246 &Object::Name(expected_filter.to_string())
2247 );
2248 assert_eq!(dict.get("BitsPerComponent").unwrap(), &Object::Integer(8));
2249 if expected_format == ImageFormat::Jpeg {
2251 assert_eq!(stream_data, data);
2252 } else {
2253 assert!(
2255 !stream_data.starts_with(b"\x89PNG"),
2256 "PDF stream must not contain a PNG container"
2257 );
2258 assert!(!stream_data.is_empty());
2259 }
2260 } else {
2261 panic!("Expected Stream object for format {expected_format:?}");
2262 }
2263 }
2264 }
2265
2266 fn build_png_with_pixels(
2271 width: u32,
2272 height: u32,
2273 color_type: u8,
2274 pixels: &[u8],
2275 ) -> Vec<u8> {
2276 use flate2::write::ZlibEncoder;
2277 use flate2::Compression;
2278 use std::io::Write;
2279
2280 let channels: usize = if color_type == 2 { 3 } else { 1 };
2281
2282 let mut raw_data: Vec<u8> = Vec::new();
2284 for y in 0..height as usize {
2285 raw_data.push(0x00);
2286 let start = y * width as usize * channels;
2287 raw_data.extend_from_slice(&pixels[start..start + width as usize * channels]);
2288 }
2289
2290 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2291 encoder.write_all(&raw_data).unwrap();
2292 let compressed = encoder.finish().unwrap();
2293
2294 fn crc32(data: &[u8]) -> u32 {
2295 let mut crc: u32 = 0xFFFF_FFFF;
2296 for &byte in data {
2297 let mut val = (crc ^ u32::from(byte)) & 0xFF;
2298 for _ in 0..8 {
2299 if val & 1 != 0 {
2300 val = (val >> 1) ^ 0xEDB8_8320;
2301 } else {
2302 val >>= 1;
2303 }
2304 }
2305 crc = (crc >> 8) ^ val;
2306 }
2307 crc ^ 0xFFFF_FFFF
2308 }
2309
2310 fn write_chunk(out: &mut Vec<u8>, chunk_type: &[u8; 4], chunk_data: &[u8]) {
2311 out.extend_from_slice(&(chunk_data.len() as u32).to_be_bytes());
2312 out.extend_from_slice(chunk_type);
2313 out.extend_from_slice(chunk_data);
2314 let mut crc_input = chunk_type.to_vec();
2315 crc_input.extend_from_slice(chunk_data);
2316 out.extend_from_slice(&crc32(&crc_input).to_be_bytes());
2317 }
2318
2319 let mut png: Vec<u8> = Vec::new();
2320 png.extend_from_slice(b"\x89PNG\r\n\x1a\n");
2321
2322 let mut ihdr = Vec::new();
2323 ihdr.extend_from_slice(&width.to_be_bytes());
2324 ihdr.extend_from_slice(&height.to_be_bytes());
2325 ihdr.push(8); ihdr.push(color_type);
2327 ihdr.push(0); ihdr.push(0); ihdr.push(0); write_chunk(&mut png, b"IHDR", &ihdr);
2331
2332 write_chunk(&mut png, b"IDAT", &compressed);
2333 write_chunk(&mut png, b"IEND", &[]);
2334
2335 png
2336 }
2337
2338 #[test]
2339 fn test_png_to_pdf_object_stream_not_png_container() {
2340 let pixels = vec![
2342 200u8, 100, 50, 200, 100, 50, 200u8, 100, 50, 200, 100, 50, ];
2345 let png_data = build_png_with_pixels(2, 2, 2, &pixels);
2346 let image = Image::from_png_data(png_data).unwrap();
2347
2348 let pdf_obj = image.to_pdf_object();
2349 if let Object::Stream(dict, stream_data) = pdf_obj {
2350 assert!(
2352 !stream_data.starts_with(b"\x89PNG"),
2353 "PDF stream must not contain a PNG container — got PNG signature bytes"
2354 );
2355 assert_eq!(
2357 dict.get("Filter").unwrap(),
2358 &Object::Name("FlateDecode".to_string())
2359 );
2360 use flate2::read::ZlibDecoder;
2362 use std::io::Read as IoRead;
2363 let mut decoder = ZlibDecoder::new(stream_data.as_slice());
2364 let mut decompressed = Vec::new();
2365 decoder
2366 .read_to_end(&mut decompressed)
2367 .expect("zlib decompression of PDF stream failed");
2368 assert_eq!(
2369 decompressed.len(),
2370 2 * 2 * 3,
2371 "decompressed data must be exactly width*height*channels bytes (no PNG framing)"
2372 );
2373 } else {
2374 panic!("Expected Stream object");
2375 }
2376 }
2377
2378 #[test]
2379 fn test_png_pdf_stream_pixel_values_correct() {
2380 let pixels = vec![200u8, 100, 50];
2382 let png_data = build_png_with_pixels(1, 1, 2, &pixels);
2383 let image = Image::from_png_data(png_data).unwrap();
2384
2385 let pdf_obj = image.to_pdf_object();
2386 if let Object::Stream(_, stream_data) = pdf_obj {
2387 use flate2::read::ZlibDecoder;
2388 use std::io::Read as IoRead;
2389 let mut decoder = ZlibDecoder::new(stream_data.as_slice());
2390 let mut decompressed = Vec::new();
2391 decoder
2392 .read_to_end(&mut decompressed)
2393 .expect("zlib decompression of PDF stream failed");
2394 assert_eq!(
2395 decompressed,
2396 vec![200u8, 100, 50],
2397 "pixel values must round-trip through PDF stream encoding correctly"
2398 );
2399 } else {
2400 panic!("Expected Stream object");
2401 }
2402 }
2403 }
2404}