homeASCIIcasts

220: PDFkit 

(view original Railscast)

Other translations: It Es

There are a number of good libraries available for generating PDF files in Ruby. One of the most popular is Prawn which is an excellent way to generate PDFs from scratch in Ruby and which was covered back in Episode 153 [watch, read]. In some situations, though, it can be easier to generate a PDF from an existing HTML document and that’s what we’ll demonstrate in this episode.

Creating PDFs from HTML is not a new idea but until now most of the solutions available have cost money. There has been a lot of activity on this subject recently due to a solution presented by Jared Pace using his new PDFKit gem. This gem depends on wkhtmltopdf, a tool that uses the WebKit rendering engine to generate PDF documents, so to install PDFkit you may first need to install wkhtmltopdf. PDFKit comes bundled with wkhtmltopdf so, depending on your environment, this may not be necessary.

The PDFKit gem is installed in the usual way:

$ gem install pdfkit

After it’s installed PDFKit can generate a PDF file by pointing it to a given file or website. Alternatively, the gem also comes with a rack middleware solution that can be used to generate PDFs from any page on a site by appending .pdf to the URL and we’ll be using the middleware approach in our application.

Creating PDF Invoices

The application we’ll be using to demonstrate PDFKit is the same one we used in the episode on Prawn. Shown below is the order page from a simple e-commerce application. We want to add a link to this page that will allow a PDF version of the order to be downloaded and we’ll use PDFKit to do this.

The page for an order.

The first thing we’ll need do is add a reference to PDFkit in our application. As this is a Rails 3 application we can modify the Gemfile to do this.

/Gemfile

gem "pdfkit"

Then, to make sure that the gem is installed we’ll run

$ bundle install

Next we’ll need to add the middleware. In a Rails 3 application this is done in the /config/application.rb file. (If we were doing this with a Rails 2 app this would go in the environment configuration file.) We’ll modify the file so that the application uses the PDFKit middleware.

/config/application.rb

require File.expand_path('../boot', __FILE__)

require 'rails/all'

# Auto-require default libraries and those for the current Rails environment.
Bundler.require :default, Rails.env

module Store
  class Application < Rails::Application
    config.secret_token = '6f22fa632e18b338b4babfa5fca632f5454fc97317cb52f372fa0f0fdd7f4d5bd95a060ff412c7230627b5c17906c9762c09208624bc1ab97f8d5344d8d4f467'
    config.filter_parameters << :password
    config.middleware.use "PDFKit::Middleware"
  end
end

To see what middleware our application is using we can run rake middleware and we’ll do that now do make sure that our PDFKit middleware is listed.

$ rake middleware
(in /Users/eifion/rails/apps_for_asciicasts/ep220/store)
use ActionDispatch::Static
use Rack::Lock
use ActiveSupport::Cache::Strategy::LocalCache
use Rack::Runtime
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::RemoteIp
use Rack::Sendfile
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::MethodOverride
use ActionDispatch::Head
use PDFKit::Middleware
run Store::Application.routes

PDFKit::Middleware is listed above so we know we’re ready to continue. We can now add .pdf to any of our application’s URLs to get a PDF version of that page. If we do this to the page for one of the orders in the application we’ll see the PDF version.

The PDF version of the invoice.

And there it is. For nearly no work at all we’ve created a PDF version of the order. It could look a little better but we’ve got a good base to work from.

Improving The Look of the PDF Invoice

The first change we’ll make to the invoice is to remove the blue background from the PDF version. We’ll need to be able to distinguish the PDF version so that we can apply different stylesheet rules to it and we can do this by modifying the call to the middleware so that PDFKit will use the print media type.

/config/application.rb

config.middleware.use "PDFKit::Middleware", :print_media_type => true

In our application’s layout file we have a stylesheet_link_tag that includes an application.css tag. By default this stylesheet has a media type of screen which means that given the change we’ve just made to the PDFKit middleware none of the CSS in that stylesheet will be applied to the PDF version. We’ll modify the stylesheet_link_tag and set its media type to all so that the styles apply to both the screen (HTML) and print (PDF) versions of the page.

/application/views/layouts/application.html.erb

<%= stylesheet_link_tag "application", :media => 'all' %>

We could have created a completely separate stylesheet for the print media type, but instead we’re going to include the print media rules in the same stylesheet as the rest of the CSS. At the bottom of the CSS file we can add a media type rule and any rules defined within that section will only apply to the PDF view. To remove the blue background from our print view we’ll add the following CSS to the bottom of our application’s stylesheet.

/public/stylesheets/application.css

@media print {
	body { background-color: #FFF; }
  #container {
		width: auto;
		margin: 0;
		padding: 0;
		border: none;
	}
}

When we reload the PDF view of our order now the blue background will be gone.

The blue background has now been removed.

This PDF is now very similar to the one we created in the episode on Prawn, but we’ve had to write far less code to achieve the same result than we would have done with Prawn. Prawn still has advantages, though, as it gives us far more control over the way the PDF file is generated, especially when trying to create multiple-page documents. If the order table stretched over several pages then we’d run into difficulty in controlling the way the headers and footers are rendered on each page.

Controlling Page Breaks

That said, PDFkit does give us some control over page breaks. If we add a few paragraphs of text at the top of the order so that the table is pushed down we’ll see that table has been split over two pages.

The table has been split across two pages.

We can work around this by modifying the print CSS so that it places a page break just before the table so that it always appears at the top of a page. We’ll use the page-break-before rule to achieve this.

/public/stylesheets/application.css

@media print {
	body { background-color: #FFF; }
  #container {
		width: auto;
		margin: 0;
		padding: 0;
		border: none;
	}
	
	#line_items {
		page-break-before: always;
	}
}

When we reload the PDF document again the table will appear on its own page.

A page break now ensures that the table isn't split.

So, we do have some control over the page breaks with PDFkit, but for more complicated documents then it may well be better to stick with Prawn.

There is one final change we want to make to the order page to finish off this episode. We’ll add a link at the bottom of the HTML invoice to the PDF version. First we’ll add a link to the PDF file at the bottom of the page.

/app/views/orders/show.html.erb

<p><%= link_to "Download Invoice (PDF)", order_path(@order, :format => "pdf") %></p>

This gives us a link from the HTML invoice to the PDF one, but that link will also appear in the PDF file which we don’t want. We can hide the link in the PDF by giving the paragraph element that contains the link an id:

/app/views/orders/show.html.erb

<p id="pdf_link"><%= link_to "Download Invoice (PDF)", order_path(@order, :format => "pdf") %></p>

And then modifying the application’s CSS file to hide the link in the PDF invoice.

/public/stylesheets/application.css

@media print {
	body { background-color: #FFF; }
  #container {
		width: auto;
		margin: 0;
		padding: 0;
		border: none;
	}
	
	#line_items {
		page-break-before: always;
	}
	
	#pdf_link {
		display: none;
	}
}

Now the HTML version of the invoice will have a link to the PDF version, but the link won’t appear in the PDF.

An Alternative

Before we end this episode we’ll mention an alternative to PDFkit, Wicked PDF. This plugin takes a different approach from PDFkit, even though it still uses wkhtmltopdf. Wicked PDF isn’t a middleware, but instead uses a renderer in Rails. Wicked PDF gives you more control over which actions can be rendered as PDFs so it’s well work considering.

That’s it for this episode. We’ve now covered two quite different ways to generate PDF documents in Rails applications, each with its own advantages and disadvantages. While PDFkit gives you the ease of creating PDFs from HTML, Prawn gives you more control over the generated files. Both approaches are worth considering.