# Creating a thumbnail of a PDF using Cairo, Poppler and Rust

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),
};
}
$$$$


This seems pretty good to me. I didn't notice any bugs, and I don't have knowledge of what pdf crates are available, so I just have a few points about idiomatic Rust.

To start, I just want to note that when I tried to create this project locally to work on, I had to fiddle around with the versions of the crates to get a working combination, which makes it an especially good idea to include what versions you're using somewhere in your question.

You can simply remove all uses of extern crate some_crate, since they aren't needed anymore in rust 2018 edition (the default by now).

I like the use of an error enum. You could consider using something like the crate thiserror to derive Display and From instances for the enum. (This just reduces boilerplate.)

I often like having type Result<T, E = MyErrorType> = std::result::Result<T, E>;, especially for a library, which lets me write Result<()> to use my error or Result<(), OtherError> to use another.

pub fn create_thumbnail(pdf_path: &Path, out_path: &Path) -> Result<()> {
let doc = PopplerDocument::new_from_file(pdf_path, "")?;

let page = doc.get_page(0).ok_or(Error::NoPagesError)?;

let (width, height) = page.get_size();

let surface = ImageSurface::create(Format::Rgb24, width as i32, height as i32)?;

let ctxt = Context::new(&surface);
ctxt.set_source_rgb(1.0, 1.0, 1.0);
ctxt.paint();

page.render(&ctxt);

let mut f = File::create(out_path)?;
surface.write_to_png(&mut f)?;

Ok(())
}


You should use Paths when you're representing paths. You can use ok_or or ok_or_else to convert Option to Result, which works well with ?. Rust can infer the type of your floats in set_source_rgb, but if you want to be explicit then you should use 1.0f64 rather than 1.0 as f64. I removed your comments just for space in the answer, but they are meaningful since they give reason and don't just restate the code.

• Thanks for the comments! I didn't realise you could drop extern crate` now, and the other stuff is useful too. I’ll add this to the code. – alexwlchan Jan 14 at 21:43