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!