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&lt;glib::error::Error&gt; for ThumbnailError {
    fn from(err: glib::error::Error) -&gt; Self {
        ThumbnailError::GlibError(err)
    }
}

impl From&lt;cairo::Status&gt; for ThumbnailError {
    fn from(status: cairo::Status) -&gt; Self {
        ThumbnailError::CairoError(status)
    }
}

impl From&lt;cairo::IoError&gt; for ThumbnailError {
    fn from(err: cairo::IoError) -&gt; Self {
        ThumbnailError::CairoIoError(err)
    }
}

impl From&lt;std::io::Error&gt; for ThumbnailError {
    fn from(err: std::io::Error) -&gt; Self {
        ThumbnailError::IoError(err)
    }
}

/// Create a JPEG thumbnail from the first page of a PDF.
fn create_thumbnail(pdf_path: &amp;str, out_path: &amp;str) -&gt; Result&lt;(), ThumbnailError&gt; {

    // 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) =&gt; p,
        None =&gt; 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(&amp;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(&amp;ctxt);

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

    Ok(())
}

fn main() {
    let pdf_path = "myfile.pdf";
    let out_path = "thumbnail.png";

    match create_thumbnail(pdf_path, out_path) {
        Ok(_) =&gt; println!("Created thumbnail of {} at {}", pdf_path, out_path),
        Err(err) =&gt; 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&lt;T, E = MyErrorType&gt; = std::result::Result&lt;T, E&gt;;, especially for a library, which lets me write Result&lt;()&gt; to use my error or Result&lt;(), OtherError&gt; to use another.

pub fn create_thumbnail(pdf_path: &amp;Path, out_path: &amp;Path) -&gt; Result&lt;()&gt; {
    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(&amp;surface);
    ctxt.set_source_rgb(1.0, 1.0, 1.0);
    ctxt.paint();

    page.render(&amp;ctxt);

    let mut f = File::create(out_path)?;
    surface.write_to_png(&amp;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.