220: PDFkit
(view original Railscast)
Other translations:
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 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.
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.
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.
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.
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.