1#![doc(html_root_url = "https://docs.rs/jpeg-to-pdf/0.2.3")]
2use errors::Error;
25pub use errors::*;
26use exif::{In, Reader as ExifReader, Tag};
27use img_parts::{jpeg::Jpeg, ImageEXIF};
28use jpeg_decoder::{Decoder as JpegDecoder, PixelFormat};
29use ori::Orientation;
30use printpdf::*;
31use std::io::{prelude::*, BufWriter, Cursor};
32
33mod errors;
34mod ori;
35
36mod tests;
37pub struct JpegToPdf {
39 images: Vec<Vec<u8>>,
40 dpi: f64,
41 strip_exif: bool,
42 document_title: String,
43}
44
45impl JpegToPdf {
46 pub fn new() -> JpegToPdf {
47 JpegToPdf {
48 images: Vec::new(),
49 dpi: 300.0,
50 strip_exif: false,
51 document_title: String::new(),
52 }
53 }
54
55 pub fn add_image(mut self, image: Vec<u8>) -> JpegToPdf {
57 self.images.push(image);
58 self
59 }
60
61 pub fn add_images(mut self, images: impl IntoIterator<Item = Vec<u8>>) -> JpegToPdf {
63 self.images.extend(images);
64 self
65 }
66
67 pub fn set_dpi(mut self, dpi: f64) -> JpegToPdf {
69 self.dpi = dpi;
70 self
71 }
72
73 pub fn strip_exif(mut self, strip_exif: bool) -> JpegToPdf {
77 self.strip_exif = strip_exif;
78 self
79 }
80
81 pub fn set_document_title(mut self, document_title: impl Into<String>) -> JpegToPdf {
83 self.document_title = document_title.into();
84 self
85 }
86
87 pub fn create_pdf(self, out: &mut BufWriter<impl Write>) -> Result<(), Error> {
89 let (dpi, strip_exif) = (self.dpi, self.strip_exif);
90
91 let doc = PdfDocument::empty(self.document_title);
92 self.images
93 .into_iter()
94 .enumerate()
95 .try_for_each(|(index, image)| {
96 add_page(image, &doc, dpi, strip_exif).map_err(|cause| Error { index, cause })
97 })
98 .and_then(|()| {
99 doc.save(out).map_err(|e| Error {
100 index: 0,
101 cause: Cause::PdfWrite(e),
102 })
103 })
104 }
105}
106
107fn add_page(
108 image: Vec<u8>,
109 doc: &PdfDocumentReference,
110 dpi: f64,
111 strip_exif: bool,
112) -> Result<(), Cause> {
113 let mut decoder = JpegDecoder::new(Cursor::new(&image));
114 decoder.read_info()?;
115
116 match decoder.info() {
117 None => Err(Cause::UnexpectedImageInfo), Some(info) => {
119 let mut image = Jpeg::from_bytes(image.into())?;
120
121 let ori = image
122 .exif()
123 .and_then(|exif_data| ExifReader::new().read_raw(exif_data.to_vec()).ok())
124 .and_then(|exif| {
125 exif.get_field(Tag::Orientation, In::PRIMARY)
126 .and_then(|field| field.value.get_uint(0))
127 })
128 .unwrap_or(1);
129
130 let ori = Orientation {
131 value: ori,
132 width: info.width as usize,
133 height: info.height as usize,
134 };
135
136 if strip_exif {
137 image.set_exif(None);
138 }
139
140 let mut image_data = Vec::new();
141 image.encoder().write_to(&mut image_data).unwrap();
142
143 let (page, layer) = doc.add_page(
144 Px(ori.display_width()).into_pt(dpi).into(),
145 Px(ori.display_height()).into_pt(dpi).into(),
146 "",
147 );
148
149 let image = Image::from(ImageXObject {
150 width: Px(info.width as usize),
151 height: Px(info.height as usize),
152 color_space: match info.pixel_format {
153 PixelFormat::L8 => ColorSpace::Greyscale,
154 PixelFormat::RGB24 => ColorSpace::Rgb,
155 PixelFormat::CMYK32 => ColorSpace::Cmyk,
156 },
157 bits_per_component: ColorBits::Bit8,
158 interpolate: false,
159 image_data,
160 image_filter: Some(ImageFilter::DCT),
161 clipping_bbox: None,
162 });
163
164 image.add_to_layer(
165 doc.get_page(page).get_layer(layer),
166 ori.translate_x().map(|px| Px(px).into_pt(dpi).into()),
167 ori.translate_y().map(|px| Px(px).into_pt(dpi).into()),
168 ori.rotate_cw(),
169 ori.scale_x(),
170 None,
171 Some(dpi),
172 );
173
174 Ok(())
175 }
176 }
177}
178
179#[deprecated]
187pub fn create_pdf_from_jpegs(
188 jpegs: Vec<Vec<u8>>,
189 out: &mut BufWriter<impl Write>,
190 dpi: Option<f64>,
191) -> Result<(), Error> {
192 JpegToPdf::new()
193 .add_images(jpegs)
194 .set_dpi(dpi.unwrap_or(300.0))
195 .create_pdf(out)
196}