jpeg_to_pdf/
lib.rs

1#![doc(html_root_url = "https://docs.rs/jpeg-to-pdf/0.2.3")]
2//! Creates PDFs from JPEG images.
3//!
4//! Images are embedded directly in the PDF, without any re-encoding.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use std::fs::{self, File};
10//! use std::io::BufWriter;
11//!
12//! use jpeg_to_pdf::JpegToPdf;
13//!
14//! let out_file = File::create("out.pdf").unwrap();
15//!
16//! JpegToPdf::new()
17//!     .add_image(fs::read("one.jpg").unwrap())
18//!     .add_image(fs::read("two.jpg").unwrap())
19//!     .add_image(fs::read("three.jpg").unwrap())
20//!     .create_pdf(&mut BufWriter::new(out_file))
21//!     .unwrap();
22//! ```
23
24use 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;
37/// Creates a PDF from JPEG images.
38pub 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    /// Add an image to the PDF output.
56    pub fn add_image(mut self, image: Vec<u8>) -> JpegToPdf {
57        self.images.push(image);
58        self
59    }
60
61    /// Add one or more images to the PDF output.
62    pub fn add_images(mut self, images: impl IntoIterator<Item = Vec<u8>>) -> JpegToPdf {
63        self.images.extend(images);
64        self
65    }
66
67    /// Set the DPI scaling of the PDF output.
68    pub fn set_dpi(mut self, dpi: f64) -> JpegToPdf {
69        self.dpi = dpi;
70        self
71    }
72
73    /// Strip EXIF metadata from the provided images.
74    ///
75    /// Some PDF renderers have issues rendering JPEG images that still have EXIF metadata.
76    pub fn strip_exif(mut self, strip_exif: bool) -> JpegToPdf {
77        self.strip_exif = strip_exif;
78        self
79    }
80
81    /// Sets the title of the PDF output.
82    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    /// Writes the PDF output to `out`.
88    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), // decoder.read_info would return Err, so we should never see this
118        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/// Creates a PDF file from the provided JPEG data.
180///
181/// PDF data is written to `out`.
182///
183/// `dpi` defaults to `300.0`.
184///
185/// Please use [`JpegToPdf`] instead.
186#[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}