A Prawn Cocktail

I like my paper. As much as I enjoy the sight of a row of green dots, a compiler that’s not mad at me, or the ecstatic silence of a batch job exiting with no errors, there’s something incomparably satisfying when a snippet of code results in something beautiful that I can print on paper, hold in my hand, and admire. If it makes someone’s life easier or someone’s business more efficient, I’m happier still. So, you’ll excuse me if I can’t wait to share this little bundle of PDF joy with the world.

Naturally, I adore LaTeX and use it wherever it fits. One place where it does not fit is in designing forms or documents which need to be specifically and precisely laid out on a page, and this was the task I had to accomplish yesterday. I use Ruby’s comprehensive prawn library for doing such precise layout. The form in question was rather more intricate than usual, and I did not relish the thought of several hours’ worth of tweaking pdf.draw_text "hello", :at => [101, 130] commands to get all my elements lined up.

My thoughts quickly turned to OmniGraffle, another workhorse of mine for anything graphical. At the very least, I could prototype my form this way, but I hoped I could do more. OmniGraffle has many export formats available, one of which is SVG. A little searching and I found prawn-svg, a Ruby gem which adds some support for SVG to prawn. My first attempt at exporting to SVG and rendering as PDF was half-successful. I had some lovely boxes, but no text. A quick look at the SVG source from OmniGraffle and the prawn-svg documentation revealed that the tspan tag, which OmniGraffle wrapped around everything with letters, had not yet been implemented in prawn-svg. Fortunately, I was able to make some modifications to the source and get at least basic tspan support working.

Here’s an OmniGraffle example file, saved as a PNG:

And here is how it looks using Prawn and prawn-svg, the resulting PDF converted to PNG:


I had to change the original Hoefler Text font of the heading to Helvetica, and bold font weight is just ignored for the moment by prawn-svg, but other than that the results are strikingly similar. And, if you’ve ever hand-coded an intricate form using Prawn, I think you’ll appreciate the possibilities of being able to get all of this basically for free from an OmniGraffle file. Also, bear in mind that if you (or your graphic designer!) makes any changes to this form, you simply export the SVG text again and these changes will be reflected in your PDF.

Fast, easy, maintainable. Choose all three! :-)

Here is the Ruby code used to generate this:

require 'rubygems'
require 'prawn'
require 'prawn-svg'

Prawn::Document.generate("colors.pdf") do |pdf|
  pdf.svg File.read("Colors.svg"), :at => [0, 600], :width => 500
  pdf.font "Courier", :style => :bold
  pdf.text_box "Hello from Prawn!", :size => 20, :at => [40, 400], :rotate => 30
end


My fork of prawn-svg is here for now, though check whether this code may have been incorporated into the main branch.

OmniGraffle is one of my favourite pieces of software, and I am very excited to be able to incorporate its output into dynamic PDFs. Bearing in mind OmniGraffle’s Applescript capabilities and rapid manual workflows, this is a very powerful combination.

Tips

Switch off text kerning in OmniGraffle before you export to SVG. With kerning switched on, as it is by default, OmniGraffle wraps each word (sometimes each individual letter) in its own tspan block and they don’t always line up perfectly in the resulting output.

If you have a legacy form, maybe even a scan of a paper form, that you need to recreate, a quick way to do this is to import it into OmniGraffle as an image. Stick it in its own locked layer, and just trace over whatever boxes, lines and text you need.

Make use of the ruler and grid in OmniGraffle. Set measurement units to pixels, have minor gridlines at 10px and major ones at 100px. Prawn has its coordinate system origin in the lower left corner of the page, so set the vertical origin in OmniGraffle to a large number so that the coordinates of the bottom left of the page are 0,0 (780px works for me, using A4 paper). You might need to experiment a little with page margins and export resolution to get the correspondence just right, but once you do, you can type a placeholder for any dynamic elements you want to add, and get OmniGraffle to do the work of calculating the coordinates for you.

require 'rubygems'
require 'prawn'
require 'prawn-svg'

Prawn::Document.generate("use-grid.pdf") do |pdf|
  pdf.svg File.read("use-grid.svg"), :at => [0, 780]
  pdf.text_box "goes here", :at => [51, 742]
end



OmniGraffle’s coordinates of 51, 742 are bang-on:

This should work with SVG from other sources, but remember that not all SVG tags are implemented yet by prawn-svg so there may be some idiosyncracies waiting for you.

If you do something fun or interesting with this and blog about it, please leave a comment with a link below!