A Pollen PDF template based on HTML, instead of LaTeX

Update 2019-03-24: I forgot to mention that Chrom/ium adds a header and footer when printing to PDF from the command line. To work around this, set the top/bottom margin to 0, eg.:

@media print {
    body {
        margin: 0 2rem 0;
    }
}

Pollen's manual has a tutorial on how to make templates for PDF. It first builds a LaTeX source file, then renders that with pdflatex, then returns the bytes from the PDF, deleting the temporary files. The problem with this in my use case is that I don't use LaTeX, and would like a way to turn the HTML version of my pages into PDF. So far I had always manually rendered my pages in Firefox (my primary browser) or Chrome (offers a print preview), which I'd like to automate.

First was finding a programmable interface to render an HTML to PDF. I knew Chrom/ium has a --print-to-pdf option, but initially I thought it's going to have unavoidable headers and footers. So I started working with wkhtmltopdf.

First version with wkhtmltopdf

First we check for wkhtmltopdf's existance.

(unless (find-executable-path "wkhtmltopdf")
   (error 'pdf-render "wkhtmltopdf missing"))

Then we assemble the pagenode to render, then send it to render-pagenodes to render. Using render-pagenodes allows me to specify that I want to get HTML output.

(define html-pagenode
   (string->symbol
    (string-replace (symbol->string here)
                    #rx"\\.pdf$" ".html")))
(render-pagenodes `(root ,html-pagenode))

Lastly, we send the HTML to wkhtmltopdf, telling it to output to stdout, and capture it with with-output-to-bytes. ((thunk ...) is shorthand for (lambda () ...).)

(with-output-to-bytes (thunk (system (format "wkhtmltopdf ~a -" html-pagenode))))

The entire thing:

wkhtmltopdf troubles

After getting the rendered PDF and printing them out, I found that the output from wkhtmltopdf differed too much from what I get from either Firefox or Chrome. Some of the CSS I have didn't seem to work as well. I then realized Chrome's --print-to-pdf option doesn't give me the headers and footers with the CSS I have, so I decided using Chrome was less trouble.

One problem with this is that Chrome has to output to a file, so I couldn't write to stdout to avoid using a temporary file, like I did with wkhtmltopdf.

Second version with Chrome

First we try to find the command for Chrome. The error only runs when all above fails.

(define chrome-executable
   (or (find-executable-path "chromium")
       (find-executable-path "google-chrome-stable")
       (find-executable-path "google-chrome-unstable")
       (find-executable-path "google-chrome")
       (find-executable-path "chrome")
       (error 'pdf-render "chrome missing")))

Let Racket handle making the temporary file for us. (Pardon my naming sense…)

(define temp-output (make-temporary-file))

This is the same as before, except this time we call system here and let it do its thing, suppressing its output with void.

(define html-pagenode
   (string->symbol
    (string-replace (symbol->string here)
                    #rx"\\.pdf$" ".html")))
(void
  (render-pagenodes `(root ,html-pagenode))
  (system (format "~a --headless --disable-gpu --print-to-pdf=~a ~a"
                  chrome-executable
                  temp-output
                  html-pagenode)))

Returning the bytes has to be done last for some reason, so we capture the bytes in output, delete the temporary file, then return output.

(define output (file->bytes temp-output))
(delete-file temp-output)
◊output

Full version: