I want to create thumbnails from PDF files – specifically, a thumbnail of the first page. Currently I use pdftocairo
on the command line:
$ pdftocairo myfile.pdf -png -singlefile -scale-to-x 400 -scale-to-y -1
but I wanted to see if I could implement this without shelling out to an external tool. I've written a function in Rust that links to poppler, takes the path to a PDF file, and a path to write a thumbnail to. It reads the first page of the PDF, renders it as a PNG, and saves it to the thumbnail path.
The function seems to work in the handful of PDFs I've tested, and I have a larger corpus I can test on if I decide to use this code.
I'd be interested in knowing:
Is this idiomatic Rust, especially the error handling? I don't have much experience writing Rust and I've been doing everything on my own, so my knowledge of what counts as "good" Rust code is quite limited.
Are there any bugs? I think I've covered the happy path and all the errors (thanks Rust!), but I might have missed something less obvious.
Is there a better way of doing this in Rust? I'm not tied to Cairo or Poppler, and if there's a Rust-native PDF library or similar that can do this sort of thing, I'd have a look at that instead.
extern crate cairo;
extern crate glib;
extern crate poppler;
use std::convert::From;
use std::fs::File;
use std::result::Result;
use cairo::{Context, Format, ImageSurface};
use poppler::{PopplerDocument, PopplerPage};
#[derive(Debug)]
pub enum ThumbnailError {
GlibError(glib::error::Error),
NoPagesError,
CairoError(cairo::Status),
CairoIoError(cairo::IoError),
IoError(std::io::Error),
}
impl From<glib::error::Error> for ThumbnailError {
fn from(err: glib::error::Error) -> Self {
ThumbnailError::GlibError(err)
}
}
impl From<cairo::Status> for ThumbnailError {
fn from(status: cairo::Status) -> Self {
ThumbnailError::CairoError(status)
}
}
impl From<cairo::IoError> for ThumbnailError {
fn from(err: cairo::IoError) -> Self {
ThumbnailError::CairoIoError(err)
}
}
impl From<std::io::Error> for ThumbnailError {
fn from(err: std::io::Error) -> Self {
ThumbnailError::IoError(err)
}
}
/// Create a JPEG thumbnail from the first page of a PDF.
fn create_thumbnail(pdf_path: &str, out_path: &str) -> Result<(), ThumbnailError> {
// Assume the PDF is not password protected.
let doc: PopplerDocument = PopplerDocument::new_from_file(pdf_path, "")?;
// Note: PDF pages are 0-indexed
let page: PopplerPage = match doc.get_page(0) {
Some(p) => p,
None => return Err(ThumbnailError::NoPagesError),
};
let (width, height) = page.get_size();
let mut surface = ImageSurface::create(
Format::Rgb24,
width as i32,
height as i32)?;
// Draw a white background to start with. If you don't, any transparent
// regions in the PDF will be rendered as black in the final image.
let ctxt = Context::new(&mut surface);
ctxt.set_source_rgb(1.0 as f64, 1.0 as f64, 1.0 as f64);
ctxt.paint();
// Draw the contents of the PDF onto the page.
page.render(&ctxt);
let mut f: File = File::create(out_path)?;
surface.write_to_png(&mut f)?;
Ok(())
}
fn main() {
let pdf_path = "myfile.pdf";
let out_path = "thumbnail.png";
match create_thumbnail(pdf_path, out_path) {
Ok(_) => println!("Created thumbnail of {} at {}", pdf_path, out_path),
Err(err) => println!("Something went wrong: {:?}", err),
};
}
```