Crate recommendation for generating PDF

Hi, I am creating a small GUI application that aims to export weekly invoices as PDF files. Which means that the exported file will have aligned columns and rows and a small watermark.

Is there a crate you can recommend for this? Thank you.

I’ve never tried that myself but this is an interesting question, so I searched the internet for feasible approaches. Assuming you don’t want to use any of those apparently (AFAICT) very low-level PDF interfaces that some crates with "pdf" in their name seem to offer, one option would be to go through LaTeX.

In case you already have some LaTeX experience you might be able to just write a function that generates the necessary LaTeX source code for such a document with a table and your watermark, and write that LaTeX source into a String. Then you could try to use the tectonic crate for creating the actual PDF file.

This approach seems a bit overkill on one hand, and simultaneously still a bit too tedious on the other hand (since you can’t simply create a data structure that represents the Document through some kind of API). So if anyone else has better ideas, please add some more answers.

Another approach would be to use HTML and perhaps wkhtmltopdf. You can also use markdown with HTML. Here’s the example code that came from my experimentation:

use pulldown_cmark::{Event::{self, *}, Tag::*, Alignment::*};
use futures::prelude::*;
use futures::pin_mut;
use async_stream::stream;
use wkhtmltopdf::{PdfApplication};
use std::error::Error;

struct IntegerTable {
    title: String,
    heads: Vec<String>,
    data: Vec<(i32, i32, i32)>,
}

// You don’t *need* to use a Stream,
// I just wanted nice `yield` syntax ^^
fn render_table(t: &IntegerTable) -> impl Stream<Item = Event<'_>> + '_ {
    stream!{
        yield Start(Heading(2));
        yield Text(t.title[..].into());
        yield End(Heading(2));
        yield Start(Table(vec![Right, Right, Right]));
        yield Start(TableHead);
        for head in &t.heads {
            yield Start(TableCell);
            yield Text(head[..].into());
            yield End(TableCell);
        }
        yield End(TableHead);
        for row in &t.data {
            yield Start(TableRow);
            for &n in &[row.0, row.1, row.2] {
                yield Start(TableCell);
                yield Text(n.to_string().into());
                yield End(TableCell);
            }
            yield End(TableRow);
        }
        yield End(Table(vec![]));
    }
}


fn main() -> Result<(), Box<dyn Error>> {
    let mut app = PdfApplication::new()?;

    let t = IntegerTable {
        title: "Nice Table".into(),
        heads: vec!["col1".into(), "col2".into(), "col3".into()],
        data: vec![
            (1,2,3),
            (4,5,6),
            (7,8,9),
            (10,11,12),
            (13,14,15),
            (16,17,18),
            (19,20,21),
        ],
    };

    let mut html = String::new();
    let contents = render_table(&t);
    pin_mut!(contents);

    pulldown_cmark::html::push_html(&mut html, futures::executor::block_on_stream(contents));

    let mut b = app.builder();

    b.title("A Fancy Title");
    b.build_from_html(&html)?.save("output.pdf")?;


    Ok(())
}
Resulting document looks like this (click here to show)

(Look at that nice title information in the title of my window; ...also, yes, I’ll admit it’s a bit small :D)

Crates needed for the example: pulldown-cmark, wkhtmltopdf, futures, async-stream
I also needed to install wkhtmltox

2 Likes