1use crate::*;
2use crate::{Dictionary, Stream};
3
4#[cfg(feature = "embed_image")]
5use image::{self, ColorType, ImageFormat};
6
7#[cfg(feature = "embed_image")]
8use std::path::Path;
9
10#[cfg(feature = "embed_image")]
11use crate::Result;
12
13#[derive(Debug, Clone)]
14pub struct PdfImage<'a> {
15 pub id: ObjectId,
16 pub width: i64,
17 pub height: i64,
18 pub color_space: Option<String>,
19 pub filters: Option<Vec<String>>,
20 pub bits_per_component: Option<i64>,
21 pub content: &'a [u8],
23 pub origin_dict: &'a Dictionary,
25}
26
27pub fn form(boundingbox: Vec<f32>, matrix: Vec<f32>, content: Vec<u8>) -> Stream {
28 let mut dict = Dictionary::new();
29 dict.set("Type", Object::Name(b"XObject".to_vec()));
30 dict.set("Subtype", Object::Name(b"Form".to_vec()));
31 dict.set(
32 "BBox",
33 Object::Array(boundingbox.into_iter().map(Object::Real).collect()),
34 );
35 dict.set("Matrix", Object::Array(matrix.into_iter().map(Object::Real).collect()));
36 let mut xobject = Stream::new(dict, content);
37 let _ = xobject.compress();
39 xobject
40}
41
42#[cfg(feature = "embed_image")]
43pub fn image<P: AsRef<Path>>(path: P) -> Result<Stream> {
44 use std::fs::File;
45 use std::io::prelude::*;
46
47 let mut file = File::open(&path)?;
48 let mut buffer = Vec::new();
49 file.read_to_end(&mut buffer)?;
50
51 image_from(buffer)
52}
53
54#[cfg(feature = "embed_image")]
55pub fn image_from(buffer: Vec<u8>) -> Result<Stream> {
56 let ((width, height), color_type) = get_dimensions_and_color_type(&buffer)?;
57
58 let (bpc, color_space) = match color_type {
59 ColorType::L8 => (8, b"DeviceGray".to_vec()),
61 ColorType::La8 => (8, b"DeviceGray".to_vec()),
62 ColorType::Rgb8 => (8, b"DeviceRGB".to_vec()),
63 ColorType::Rgba8 => (8, b"DeviceRGB".to_vec()),
64 ColorType::L16 => (16, b"DeviceGray".to_vec()),
66 ColorType::La16 => (16, b"DeviceGray".to_vec()),
67 ColorType::Rgb16 => (16, b"DeviceRGB".to_vec()),
68 ColorType::Rgba16 => (16, b"DeviceRGB".to_vec()),
69 ColorType::Rgb32F => return Err(Error::Unimplemented("ColorType::Rgb32F is not supported")),
71 ColorType::Rgba32F => return Err(Error::Unimplemented("ColorType::Rgba32F is not supported")),
72 _ => {
75 return Err(Error::Unimplemented(
76 "The image crate supports a new color type, but lopdf has not been updated yet",
77 ));
78 }
79 };
80
81 let mut dict = Dictionary::new();
82 dict.set("Type", Object::Name(b"XObject".to_vec()));
83 dict.set("Subtype", Object::Name(b"Image".to_vec()));
84 dict.set("Width", width);
85 dict.set("Height", height);
86 dict.set("ColorSpace", Object::Name(color_space));
87 dict.set("BitsPerComponent", bpc);
88
89 let format = image::guess_format(&buffer)?;
90 if format == ImageFormat::Jpeg {
91 dict.set("Filter", Object::Name(b"DCTDecode".to_vec()));
93 Ok(Stream::new(dict, buffer))
94 } else {
95 let img = image::load_from_memory(&buffer)?;
97 let content = match img.color() {
98 ColorType::L8 => img.into_bytes(),
100 ColorType::La8 => img.into_luma8().into_raw(),
102 ColorType::Rgb8 => img.into_bytes(),
104 ColorType::Rgba8 => img.into_rgb8().into_raw(),
106 ColorType::L16 => img
108 .into_luma16()
109 .into_raw()
110 .iter()
111 .flat_map(|&pixel| pixel.to_be_bytes()) .collect(),
113 ColorType::La16 => img
115 .into_luma16() .into_raw()
117 .iter()
118 .flat_map(|&pixel| pixel.to_be_bytes()) .collect(),
120 ColorType::Rgb16 => img
122 .into_rgb16()
123 .into_raw()
124 .iter()
125 .flat_map(|&pixel| pixel.to_be_bytes()) .collect(),
127 ColorType::Rgba16 => img
129 .into_rgb16() .into_raw()
131 .iter()
132 .flat_map(|&pixel| pixel.to_be_bytes()) .collect(),
134 ColorType::Rgb32F => return Err(Error::Unimplemented("ColorType::Rgb32F is not supported")),
136 ColorType::Rgba32F => return Err(Error::Unimplemented("ColorType::Rgba32F is not supported")),
137 _ => {
140 return Err(Error::Unimplemented(
141 "The image library supports a new color type, but lopdf has not been updated yet",
142 ));
143 }
144 };
145
146 let mut img_object = Stream::new(dict, content);
147 let _ = img_object.compress();
149 Ok(img_object)
150 }
151}
152
153#[cfg(feature = "embed_image")]
155fn get_dimensions_and_color_type(buffer: &Vec<u8>) -> Result<((u32, u32), ColorType)> {
156 use image::{ImageDecoder, ImageReader};
157
158 let reader = ImageReader::new(std::io::Cursor::new(buffer));
159 let decoder = reader.with_guessed_format()?.into_decoder()?;
160
161 let dimensions = decoder.dimensions();
162 let color_type = decoder.color_type();
163
164 Ok((dimensions, color_type))
165}
166
167#[cfg(all(feature = "embed_image", not(feature = "async")))]
168#[test]
169fn insert_image() {
170 use super::xobject;
171 let mut doc = Document::load("assets/example.pdf").unwrap();
172 let pages = doc.get_pages();
173 let page_id = *pages.get(&1).expect(&format!("Page {} not exist.", 1));
174 let img = xobject::image("assets/pdf_icon.jpg").unwrap();
175 doc.insert_image(page_id, img, (100.0, 210.0), (400.0, 225.0)).unwrap();
176 doc.save("test_5_image.pdf").unwrap();
177}
178
179#[cfg(all(feature = "embed_image", feature = "async"))]
180#[tokio::test]
181async fn insert_image() {
182 use super::xobject;
183 let mut doc = Document::load("assets/example.pdf").await.unwrap();
184 let pages = doc.get_pages();
185 let page_id = *pages.get(&1).unwrap_or_else(|| panic!("Page {} not exist.", 1));
186 let img = xobject::image("assets/pdf_icon.jpg").unwrap();
187 doc.insert_image(page_id, img, (100.0, 210.0), (400.0, 225.0)).unwrap();
188 doc.save("test_5_image.pdf").unwrap();
189}
190
191#[cfg(feature = "embed_image")]
192#[test]
193fn embed_supported_color_type() -> Result<()> {
194 use content::{Content, Operation};
195 use image::GenericImageView;
196
197 let mut img_paths = std::fs::read_dir("assets/supported_color_type")?
198 .filter_map(|entry| entry.ok())
199 .map(|entry| entry.path())
200 .collect::<Vec<_>>();
201 img_paths.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
203
204 let mut doc = Document::with_version("1.5");
205 let pages_id = doc.new_object_id();
206 let mut page_ids = vec![];
207
208 for img_path in img_paths {
209 let img = image::open(&img_path)?;
210 let (width, height) = img.dimensions();
211 let color_type = img.color();
212 println!("Image: {img_path:?}, width: {width}, height: {height}, color type: {color_type:?}");
213
214 let image_stream = xobject::image(img_path)?;
215
216 let img_id = doc.add_object(image_stream);
217 let img_name = format!("X{}", img_id.0);
218
219 let cm_operation = Operation::new(
220 "cm",
221 vec![width.into(), 0.into(), 0.into(), height.into(), 0.into(), 0.into()],
222 );
223
224 let do_operation = Operation::new("Do", vec![Object::Name(img_name.as_bytes().to_vec())]);
225 let content = Content {
226 operations: vec![cm_operation, do_operation],
227 };
228
229 let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode()?));
230 let page_id = doc.add_object(dictionary! {
231 "Type" => "Page",
232 "Parent" => pages_id,
233 "Contents" => content_id,
234 "MediaBox" => vec![0.into(), 0.into(), width.into(), height.into()],
235 });
236
237 doc.add_xobject(page_id, img_name.as_bytes(), img_id)?;
238 page_ids.push(page_id);
240 }
241
242 let pages_dict = dictionary! {
243 "Type" => "Pages",
244 "Count" => page_ids.len() as u32,
245 "Kids" => page_ids.into_iter().map(Object::Reference).collect::<Vec<_>>(),
246 };
247 doc.objects.insert(pages_id, Object::Dictionary(pages_dict));
248
249 let catalog_id = doc.add_object(dictionary! {
250 "Type" => "Catalog",
251 "Pages" => pages_id,
252 });
253 doc.trailer.set("Root", catalog_id);
254
255 doc.compress();
256
257 doc.save("supported_color_type.pdf")?;
258 Ok(())
259}