A Simple Way to Generate formatted PDF Documents

written by amcvega on November 23rd, 2007 @ 12:37 AM

The Problem

Most of the applications we make are in aid of current manual systems. The whole reason clients come to us is because they want to streamline and automate parts of their processes but they still have to deal with the reality of the current, paper-based way of doing things. So it happens that recently I’ve gotten a sudden influx of requests to be able to generate printable pre-filled forms.

There are multiple ways to go about doing this. The easiest way would’ve been to just allow the user to click ‘print’ on their browser and serve them a formatted print.css page. However, a couple of projects back we ran into a problem using this approach – we could never get the same result across multiple computers. We tried restricting the browsers used to firefox but even that didn’t play nice with the differing operating systems. Using CSS to generate printable forms wasn’t an option.

The next ideal way would be to be able to generate a pdf using from an html output. This would be great because we could maybe use the existing templates and just generate the pdf from that. After looking around a while it seemed that PrinceXML was the way to go. Unfortunately for me, it didn’t come free. Alternatives such as HTML Doc just didn’t cut it.

Next in my quest was pdftk I thought maybe I could just design the templates by hand using Scribus and then just fill_form the values using pdftk. Again I hit a roadblock when I found that some of the forms had dynamic content (e.g. tables) that couldn’t be handled by this approach.

I was back to using PDF::Writer. The problem with pdf-writer is that it’s pretty raw. It allows you to do a lot of things with PDFs but you have to deal with lines, points, x/y coordinates, and all of the details. I didn’t have the time nor the patience to deal with all that for the amount of printables that were coming in.

The Solution?

So I decided to write my own little library that would fix all that. Introducing PDF::Cell – it’s a library based on PDF::Writer that allows you to layout documents in a straightforward manner without thinking about the nitty-gritty details about how exactly to position the elements. The model for laying out stuff is loosely based on Ruby Shoes’s concept of stacks and flows. Here’s an example:

pdf = PDF::Writer.new biodata = PDF::Cell::Base.new(pdf) biodata.build :width => 500, :font_size => 8 do header "Crew File #245: Jack Black (2/M of MV Nautilus)", :shaded => true, :font_size => 15 desc "The Content" cell :width => 375 do cell :width => 125 do text "<b>Given Name</b>" text "Jack" end cell :width => 125 do text "<b>Middle Name</b>" text "James" end cell :width => 125 do text "<b>Family Name</b>" text "Black" end cell :width => 250 do text "<b>Address</b>" text "#124 Main Street" end cell :width => 125 do text "<b>Contact No</b>" text "123-456-789" end end cell :width => 125, :height => 125 do text "Photo Here" end end

And here’s the result of the above code

Now for the explanation. The overall box is given a width of 500. (it uses pdf-writer’s measurements not mine) The width is strict – meaning everything else will adjust, just not the width. We add a cell to the box with 375 width – let’s call this the “Content” box. Within the Content box we add 3 cells each with a width of 125. These 3 boxes add up to exactly 375 – the limit of the Content box. Then we add another cell with a width of 250. Clearly this won’t fit into the Content box’s width so PDF::Cell automatically puts it below the previous 3 cells! And that’s basically it. Using that simple concept of cells you can potentially generate any layout your document needs.

I recently added support for tables within cells. Images within cells are coming up shortly although, if you know how to use pdf-writer, you can hack it yourself for now.


To install just copy/paste the following into your rails root directory:

ruby script/plugin install svn://svn.upstrat.com/plugins/pdf_cell/trunk

That’s it! You should be ready to go.

Post a comment