HTML / CSS to PDF using Ruby on Rails

Ever tried to save a web page and send it to someone? What about printing a web page? Both pretty much suck.

A lot of people are talking about printing with HTML and CSS these days but what they don’t tell you is the following:

With CSS you can’t…
  • Determine where page breaks happen.
  • Set page size or type (landscape or portrait).
  • Print background colors…some browsers don’t even print images.
  • Set page footers.

Wow, that’s lame.

Ever tried sending someone a web page? Not a link, but the actual web page. It doesn’t work well at all.

A little history

I wanted to be able to print or send estimates and invoices from our Ruby on Rails application, Cashboard. A good majority of our users felt the same way.

Just sending a link to someone to view a HTML document online doesn’t cut it. What if you want to snail mail that document? Shouldn’t it look the same on the page as it does on your screen?

When I used to create invoices manually I’d always save PDF files for my clients and email them.

PDF handled printing well, was portable, and my clients couldn’t edit the file.

We needed PDF for Cashboard.

The Prince of PDFs

I spent a good week just researching all of the PDF libraries and programs out there.

The ones I found for Ruby wanted me to re-create my documents in some PDF-specific layout language. That wasn’t going to cut it. See, in Cashboard we already had these documents designed in HTML. On top of that, we allow users to customize the colors and upload their logo for invoices. Duplicating the layout wasn’t an option.

After exhausting those possibilities I started looking into HTML to PDF programs. I found a few, but none that were any good at converting HTML and CSS to PDF.

This all changed when I ran across Prince XML. Not only is Prince great at converting HTML and CSS to PDF, it even passed the Acid2 test. They had a ton of demos available on their web site, and provide their program free for evaluation purposes.

Making it happen

Prince is a command line program, available for whatever platform you’re probably running.

It can take a variety of inputs including files on disk, or even HTTP urls. We run a pretty tight ship on Cashboard, and everything is password protected.

Luckilly Prince also can take input from standard in, and can even pass its output back to standard out. This means if you don’t want to mess with saving files and dealing with cleaning them up you don’t have to.

prince.rb, pdf_helper.rb

I cooked up a very simple Ruby library to call Prince and a helper module to include on my Rails controllers. I store them both in the lib folder of my Rails application.

Here’s the full helper, slightly modified for simplicity:

# We use this chunk of controller code all over to generate PDF files.
#
# To stay DRY we placed it here instead of repeating it all over the place.
#
module PdfHelper
  require 'prince'

  private
    # Makes a pdf, returns it as data...
    def make_pdf(template_path, pdf_name, landscape=false)
      prince = Prince.new()
      # Sets style sheets on PDF renderer.
      prince.add_style_sheets(
        "#{RAILS_ROOT}/public/stylesheets/application.css",
        "#{RAILS_ROOT}/public/stylesheets/print.css",
        "#{RAILS_ROOT}/public/stylesheets/prince.css"
      )
      prince.add_style_sheets("#{RAILS_ROOT}/public/stylesheets/prince_landscape.css") if landscape
      # Render the estimate to a big html string.
      # Set RAILS_ASSET_ID to blank string or rails appends some time after
      # to prevent file caching, fucking up local - disk requests.
      ENV["RAILS_ASSET_ID"] = ''
      html_string = render_to_string(:template => template_path, :layout => 'document')
      # Make all paths relative, on disk paths...
      html_string.gsub!("src=\"", "src=\"#{RAILS_ROOT}/public")
      # Send the generated PDF file from our html string.
      return prince.pdf_from_string(html_string)
    end

    # Makes and sends a pdf to the browser
    #
    def make_and_send_pdf(template_path, pdf_name, landscape=false)
      send_data(
        make_pdf(template_path, pdf_name, landscape),
        :filename => pdf_name,
        :type => 'application/pdf'
      ) 
    end
end

This simple module has two methods. Both take an ERB template path as an argument, then a pdf file name.

Make_pdf renders the template to a string, then does some modifications to make all requests within local. I’m passing in all CSS files locally as well, so I don’t have to deal with authentication.

I’ve created some special CSS files for printing, and even can pass in if I’d like the page to be laid out in a portrait or landscape format.

When it’s done it returns the PDF file as data. Nothing is rendered to disk. This method is useful for not only sending the PDF file to the client (as in make_and_send_pdf), but when generating PDF files for email attachments.

Creating PDF files from the controller

Both of these files make creating a PDF file dead easy.

Here’s a slimmed down version of Cashboard’s estimate controller.

class Provider::EstimatesController < Provider::BaseController
  include PdfHelper
    # Sends pdf of an estimate out...
    #
    def pdf
      # @estimate is set with a before_filter and isn't relevant for this how-to ;)
      make_and_send_pdf('/client/estimates/show', @estimate.pdf_name)
    end
end

Does it get any easier than that? Check out an example of a PDF file generated with Prince, straight from Cashboard. (Right click and save to your disk…)

Download

Here again are the libraries I’ve created for using Prince XML with Ruby on Rails:

They’re free to use, download em and check em out.

seth, Tue, 29 May 2007 21:37:00 GMT
10 comments

Cashboard ALPHA goes live!

You heard right…..our first hosted application just launched!

If you’re managing estimates, invoices, payments, and projects any other way – you need this software.

Cashboard Invoice Screen

Check out the announcement or – get Cashboard now!

seth, Wed, 04 Apr 2007 23:44:00 GMT
no comments

Flying Toasters reborn for OS X

oldschool toasters

Feeling nostalgic for old-school Flying Toasters today I went looking for screen savers for OS X and found a faithful re-creation of the original.

Takes me back to the After Dark days when I had my old Mac LCII and IIsi.

Too bad the originals haven’t been open-sourced by Berkeley yet.

For something with a new-school twist I also found this pack of x11 savers that have been ported to OS X.

My favorite out of the bunch so far is the barcode module which generates random words and displays the corresponding barcode for each.

seth, Thu, 11 Jan 2007 16:59:00 GMT
no comments

Firefox 2.0, why do you hate me?

I was really glad when Firefox came out. It was a blessing to finally be free from the grips of IE for day-to-day browsing tasks.

I was even more glad when a nice set of web development debugging tools came out for it.

Firefox helped make my transition to from Windows XP to OS X more smooth. I didn’t have to switch browsers or use Safari. Firefox ran on OS X.

But why must Firefox lock up on me at least twice daily while I’m browsing the web? It’s not even a full on crash. Firefox just simply stops responding and refuses to load pages until I force quit it.

I understand browsers have a lot of moving parts that all must work in unison, but damn, you’d think someone could get the browser right.

IE has its numerous faults and security flaws. Safari has shit for debugging tools, and I refuse to run Opera since older versions of it couldn’t even float an iFrame over another div.

Please, someone fix this.

seth, Wed, 10 Jan 2007 20:41:00 GMT
4 comments

Diagramming the night away

What are we calling ourselves this month? UI designer, information architect, usability specialist? I’m not sure the correct term anymore.

Regardless of title, tonight I’ve come to terms with one of the tasks I dread usually…creating usage scenarios.

In the past after coming up with my personas and getting into their heads I would write out a little story as suggested in The Inmates Are Running The Asylum.

One of the problems I’ve found with that approach is that it doesn’t break down the process into nice chunks that are easy to digest when designing or coding.

Full blown process diagrams are equally dreadful, and only useful mostly to programmers, which I only play a third of the time (the other 2/3 being shared equally between entrepreneur and designer).

Doing a little searching I came across this interaction modeling article which I found pretty interesting and easy to understand. (Definitely a plus when running on a few hours of sleep…)

Unfortunately I didn’t have a program like Visio on my laptop to craft such a diagram as described in the article…Not like I’m a huge fan of the program in the first place.

Enter OmniGraffle...For me, this is definitely the way to go when faced with the challenge of diagramming something. (OS X only, sorry XP people…)

Everything seems to be laid out logically and nothing is too complicated, as I’ve complained Visio can be at times.

Cashboard usage scenarios

Yes, I relentlessly bit their demo style for my Cashboard scenario diagram, complete with gradients and all. But look it’s so shiny, so easy on the eyes.

For me, looking at something like this engages me more than a boring black and white diagram or a page full of text. On top of that, it’s extremely easy to pick out items I need to take action on during the design and coding phases of a project.

I’ve broken it down into five categories…
  • Actions by a user (blue gradient rectangles)
  • Actions that map to another diagram (green rectangles)
  • Actions the system takes (aqua rectangles with white text)
  • Decisions (diamonds)
  • Thought processes or actions that don’t happen in the system (rounded rectangles)

If I’m wearing my designer or coder hat it’s easy to pick out the things that matter to me, while still maintaining an overall view of the process in question.

Of course this isn’t as detailed as it could be, but I use this to create larger task lists later on. Just going through the process of creating a document like this always seems to crystallize my thoughts on a specific part of an application’s design.

Take that user stories…

seth, Sat, 30 Dec 2006 10:44:00 GMT
no comments
Older posts: 1 2 3