Gravl: Insanely simple PDFs for Grails

And if I told you that 20 lines of code was probably plenty for fine looking PDFs? So you’ve just written a killer blog entry that legions of fan are keen to copy to their PDAs to read on the way home? Just click Gravl’s “PDF” icon and you’re off:

The result of a PDF export

PDF is a visual medium, so why design everything in non-visual XML… why not design your PDF export in html (which you’ve got great tools for), then pass it through a simple library to tranform from html straight to PDF?

I was totally inspired by Josh’s article on using Flying Saucer (which is a html rendering component for Java which can read a bunch of html then render to a Swing component or to PDF).

Integrating into Gravl was a matter of creating a PdfController then passing it a relative URL to render. Because rendering can take a little time, I wrap it in cache to make it faster for the next guy:

import org.xhtmlrenderer.pdf.ITextRenderer;

class PdfController {

    CacheService cacheService

    def index = {
        redirect(action: show)
    }

    private byte[] buildPdf(url) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(url);
        renderer.layout();
        renderer.createPDF(baos);
        byte[] b = baos.toByteArray();
        return b

    }

    def show = {

        def baseUri = request.scheme + "://" + request.serverName + ":" + request.serverPort +
                    grailsAttributes.getApplicationUri(request)
        log.debug "BaseUri is $baseUri"

        def url = baseUri + params.url + "?print=true"
        log.debug "Fetching url $url"

        // grab pdf bytes from cache if possible
        byte[] b = cacheService.getFromCache("pdfCache", 60, url)
        if (!b) {
            b = buildPdf(url)
            cacheService.putToCache("pdfCache", 60, url, b)
        }

        response.setContentType("application/pdf")
        response.setHeader("Content-disposition", "attachment; filename=blog.pdf")
        response.setContentLength(b.length)
        response.getOutputStream().write(b)

    }
}

Then you just need to add yourself a link to the PDF creation…

<g:if test="${print!=true}">
    <g:link controller="pdf" action="show" params="[url: entry.toPermalink('')]">PDF</g:link>
</g:if>

Now Flying Saucer is a strict xhtml renderer, so if all those <p> tags don’t terminate nicely… you need to know a few tricks. If you’re coming to my “whole nine yards” session at 2GX and I’ll let you in on a few other tricks…

Go forth and PDF with pleasure!

7 thoughts on “Gravl: Insanely simple PDFs for Grails

  1. Nicely done. It sure does seem simpler than loading up the entire iText library. Sorry.. I’ll miss your 2GEX … I did get to see Graeme’s talks at the SpringEX this past week. Quite good. I also demoed some Grails apps I’ve kicked around to some innocent by standards.

  2. Very nice.

    How much work would it be to bundle up the dependent frameworks and package this as a Grails plugin? In an ideal world, adding this functionality would be as simple as:

    grail install-plugin pdf-generator

  3. Cool plugin! Simple and clean!

    One question: what about using acegi? I generate the PDF OK but the content of PDF is the login page and not the page I’ve requested :S

    How can I workaround this issue?

    Regards

Comments are closed.