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(
36 "Matrix",
37 Object::Array(matrix.into_iter().map(Object::Real).collect()),
38 );
39 let mut xobject = Stream::new(dict, content);
40 let _ = xobject.compress();
42 xobject
43}
44
45#[cfg(feature = "embed_image")]
46pub fn image<P: AsRef<Path>>(path: P) -> Result<Stream> {
47 use std::fs::File;
48 use std::io::prelude::*;
49
50 let mut file = File::open(&path)?;
51 let mut buffer = Vec::new();
52 file.read_to_end(&mut buffer)?;
53
54 image_from(buffer)
55}
56
57#[cfg(feature = "embed_image")]
58pub fn image_from(buffer: Vec<u8>) -> Result<Stream> {
59 let ((width, height), color_type) = get_dimensions_and_color_type(&buffer)?;
60
61 let (bpc, color_space) = match color_type {
62 ColorType::L8 => (8, b"DeviceGray".to_vec()),
64 ColorType::La8 => (8, b"DeviceGray".to_vec()),
65 ColorType::Rgb8 => (8, b"DeviceRGB".to_vec()),
66 ColorType::Rgba8 => (8, b"DeviceRGB".to_vec()),
67 ColorType::L16 => (16, b"DeviceGray".to_vec()),
69 ColorType::La16 => (16, b"DeviceGray".to_vec()),
70 ColorType::Rgb16 => (16, b"DeviceRGB".to_vec()),
71 ColorType::Rgba16 => (16, b"DeviceRGB".to_vec()),
72 ColorType::Rgb32F => {
74 return Err(Error::Unimplemented("ColorType::Rgb32F is not supported"));
75 }
76 ColorType::Rgba32F => {
77 return Err(Error::Unimplemented("ColorType::Rgba32F is not supported"));
78 }
79 _ => {
82 return Err(Error::Unimplemented(
83 "The image crate supports a new color type, but lopdf has not been updated yet",
84 ));
85 }
86 };
87
88 let mut dict = Dictionary::new();
89 dict.set("Type", Object::Name(b"XObject".to_vec()));
90 dict.set("Subtype", Object::Name(b"Image".to_vec()));
91 dict.set("Width", width);
92 dict.set("Height", height);
93 dict.set("ColorSpace", Object::Name(color_space));
94 dict.set("BitsPerComponent", bpc);
95
96 let format = image::guess_format(&buffer)?;
97 if format == ImageFormat::Jpeg {
98 dict.set("Filter", Object::Name(b"DCTDecode".to_vec()));
100 Ok(Stream::new(dict, buffer))
101 } else {
102 let img = image::load_from_memory(&buffer)?;
104 let content = match img.color() {
105 ColorType::L8 => img.into_bytes(),
107 ColorType::La8 => img.into_luma8().into_raw(),
109 ColorType::Rgb8 => img.into_bytes(),
111 ColorType::Rgba8 => img.into_rgb8().into_raw(),
113 ColorType::L16 => img
115 .into_luma16()
116 .into_raw()
117 .iter()
118 .flat_map(|&pixel| pixel.to_be_bytes()) .collect(),
120 ColorType::La16 => img
122 .into_luma16() .into_raw()
124 .iter()
125 .flat_map(|&pixel| pixel.to_be_bytes()) .collect(),
127 ColorType::Rgb16 => img
129 .into_rgb16()
130 .into_raw()
131 .iter()
132 .flat_map(|&pixel| pixel.to_be_bytes()) .collect(),
134 ColorType::Rgba16 => img
136 .into_rgb16() .into_raw()
138 .iter()
139 .flat_map(|&pixel| pixel.to_be_bytes()) .collect(),
141 ColorType::Rgb32F => {
143 return Err(Error::Unimplemented("ColorType::Rgb32F is not supported"));
144 }
145 ColorType::Rgba32F => {
146 return Err(Error::Unimplemented("ColorType::Rgba32F is not supported"));
147 }
148 _ => {
151 return Err(Error::Unimplemented(
152 "The image library supports a new color type, but lopdf has not been updated yet",
153 ));
154 }
155 };
156
157 let mut img_object = Stream::new(dict, content);
158 let _ = img_object.compress();
160 Ok(img_object)
161 }
162}
163
164#[cfg(feature = "embed_image")]
166fn get_dimensions_and_color_type(buffer: &Vec<u8>) -> Result<((u32, u32), ColorType)> {
167 use image::{ImageDecoder, ImageReader};
168
169 let reader = ImageReader::new(std::io::Cursor::new(buffer));
170 let decoder = reader.with_guessed_format()?.into_decoder()?;
171
172 let dimensions = decoder.dimensions();
173 let color_type = decoder.color_type();
174
175 Ok((dimensions, color_type))
176}
177
178#[cfg(all(feature = "embed_image", not(feature = "async")))]
179#[test]
180#[ignore = "depends on assets/pdf_icon.jpg fixture not committed to the repo"]
181fn insert_image() {
182 use super::xobject;
183 let mut doc = Document::load("assets/example.pdf").unwrap();
184 let pages = doc.get_pages();
185 let page_id = *pages
186 .get(&1)
187 .unwrap_or_else(|| panic!("Page {} not exist.", 1));
188 let img = xobject::image("assets/pdf_icon.jpg").unwrap();
189 doc.insert_image(page_id, img, (100.0, 210.0), (400.0, 225.0))
190 .unwrap();
191 doc.save("test_5_image.pdf").unwrap();
192}
193
194#[cfg(all(feature = "embed_image", feature = "async"))]
195#[tokio::test]
196#[ignore = "depends on assets/pdf_icon.jpg fixture not committed to the repo"]
197async fn insert_image() {
198 use super::xobject;
199 let mut doc = Document::load("assets/example.pdf").await.unwrap();
200 let pages = doc.get_pages();
201 let page_id = *pages
202 .get(&1)
203 .unwrap_or_else(|| panic!("Page {} not exist.", 1));
204 let img = xobject::image("assets/pdf_icon.jpg").unwrap();
205 doc.insert_image(page_id, img, (100.0, 210.0), (400.0, 225.0))
206 .unwrap();
207 doc.save("test_5_image.pdf").unwrap();
208}
209
210#[cfg(feature = "embed_image")]
211#[test]
212#[ignore = "depends on assets/supported_color_type/ fixture dir not committed to the repo"]
213fn embed_supported_color_type() -> Result<()> {
214 use content::{Content, Operation};
215 use image::GenericImageView;
216
217 let mut img_paths = std::fs::read_dir("assets/supported_color_type")?
218 .filter_map(|entry| entry.ok())
219 .map(|entry| entry.path())
220 .collect::<Vec<_>>();
221 img_paths.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
223
224 let mut doc = Document::with_version("1.5");
225 let pages_id = doc.new_object_id();
226 let mut page_ids = vec![];
227
228 for img_path in img_paths {
229 let img = image::open(&img_path)?;
230 let (width, height) = img.dimensions();
231 let color_type = img.color();
232 println!(
233 "Image: {img_path:?}, width: {width}, height: {height}, color type: {color_type:?}"
234 );
235
236 let image_stream = xobject::image(img_path)?;
237
238 let img_id = doc.add_object(image_stream);
239 let img_name = format!("X{}", img_id.0);
240
241 let cm_operation = Operation::new(
242 "cm",
243 vec![
244 width.into(),
245 0.into(),
246 0.into(),
247 height.into(),
248 0.into(),
249 0.into(),
250 ],
251 );
252
253 let do_operation = Operation::new("Do", vec![Object::Name(img_name.as_bytes().to_vec())]);
254 let content = Content {
255 operations: vec![cm_operation, do_operation],
256 };
257
258 let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode()?));
259 let page_id = doc.add_object(dictionary! {
260 "Type" => "Page",
261 "Parent" => pages_id,
262 "Contents" => content_id,
263 "MediaBox" => vec![0.into(), 0.into(), width.into(), height.into()],
264 });
265
266 doc.add_xobject(page_id, img_name.as_bytes(), img_id)?;
267 page_ids.push(page_id);
269 }
270
271 let pages_dict = dictionary! {
272 "Type" => "Pages",
273 "Count" => page_ids.len() as u32,
274 "Kids" => page_ids.into_iter().map(Object::Reference).collect::<Vec<_>>(),
275 };
276 doc.objects.insert(pages_id, Object::Dictionary(pages_dict));
277
278 let catalog_id = doc.add_object(dictionary! {
279 "Type" => "Catalog",
280 "Pages" => pages_id,
281 });
282 doc.trailer.set("Root", catalog_id);
283
284 doc.compress();
285
286 doc.save("supported_color_type.pdf")?;
287 Ok(())
288}