When it comes to generating printable documents in your Rails App a lot of people use prawn to create pdfs. Although prawn is a very powerful gem for this kind of task, things can easily get complicated if you have complex styling . At adeven we use the powerful d3js library to create daily reports for our adjust.io customers. Unfortunately, adding javascript-based content to your pdf is impossible with prawn, so we turned to PhantomJS.
Generate pdf from html with PhantomJS
PhantomJS is a headless WebKit with JavaScript API. It’s well known for headless website testing in CI environments - check out Poltergeist to learn more about Testing JavaScript with PhantomJS. However, PhantomJS can also be used for screen-capturing as well as generating pdf documents.
Shrimp
Our shrimp gem is a simple wrapper around PhantomJS’s pdf-rendering capabilities. You can download and install PhantomJS from http://phantomjs.org/download.html or simply do a
brew install phantomjs
if you are on MacOS using homebrew.
To install shrimp just type
gem install shrimp
1 2 3 4 |
|
Et voilá! A rendered pdf of your website.
Shrimp comes with plenty of options that you can pass to the Phantom Object. However, you can also configure shrimp to your needs with a config file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Most of the options are self explanatory. Note that if you have very complex pages with a lot of javascript that needs to be executed after document ready, you might want to increase the rendering_time. For example with our d3js graphs we need 3 seconds to get good results.
Keep in mind, that the rendering_timeout should be higher than the rendering_time.
The Phantom Class come with three different rendering options:
1 2 3 4 5 6 7 8 9 10 11 |
|
Shit’s being weird
If some error occurs you will still get a result - an empty file. This is necessary to let some asynchronous rendering like Shrimp::Middleware know about it. However you still can check the error response.
1 2 3 4 5 |
|
To make sure the resulting pdf has the expected content, phantom does not follow redirects or render weird 500 status pages. So everything other than a 200 response results in an empty output file.
If you prefer bang methods each of the rendering options comes with a bang!
1 2 3 |
|
Shrimp::Middleware
The shrimp gem comes with a rack-aware Middleware that allows users to get a pdf view of any page on your site by appending .pdf to the URL.
Non-Rails Rack apps
# in config.ru
require 'shrimp'
use Shrimp::Middleware
Rails apps
1 2 3 |
|
With Shrimp options
1 2 |
|
With conditions to limit routes that can be generated in pdf
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Polling
To avoid deadlocks, Shrimp::Middleware renders the pdf in a separate process retuning a 503 Retry-After response Header. you can setup the polling interval and the polling offset in seconds.
config.middleware.use Shrimp::Middleware, :polling_interval => 1, :polling_offset => 5
Caching
To avoid rendering the page on each request you can setup some the cache ttl in seconds
config.middleware.use Shrimp::Middleware, :cache_ttl => 3600, :out_path => "/my/pdf/store"
Cookies
If you use Rack::Session::Cookie
in your RackApp the user cookie is passed to PhantomJS. Thus you don’t need to worry about Login Credentials or other session based content.
However, as we also send pdf reports to our customers we want to render resources without being logged in. Since we use devise for user handling in our Rails App, things get easy with our own devise SignInInterceptor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
With this setup we can add a to_pdf
method to our resource
1 2 3 4 5 6 7 8 |
|
Fancy Ajax
The middleware return three different status codes based on the rendering status.
503 Retry-After # as long as the rendering is still in progress
504 # if rendering took longer than request_timeout
200 Content-Type application/pdf # delivering the pdf file if rendering is finished
if request was HTTP_X_REQUESTED_WITH (Ajax)
200 Content-Type text/html # delivering html with the link to the pdf file
To include some fancy Ajax stuff with jquery you can do
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
CSS Styling
The good thing about PhantomJS is that you only need to take care of webkit’s css implementation. To implement manual page breaks you can do:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
TL;DR
You don’t always have to fight the fat prawn when a lightweight shrimp can do.