PDFKit.new('http://google.com').to_file('google.pdf')
. You can then send the PDF using send_file 'google.pdf'
.PDFKit.new
. Paths to the stylesheets must be passed separately before calling to_file
.PDFKit::Middleware
and all your Rails routes automagically respond to the .pdf
format. This is awesome to get started fast, but details like setting the content disposition (download / inline) or download filename is awkward.Configure PDFKit in an initializer:
PDFKit.configure do |config|
config.default_options = {
# print_media_type: true,
# page_size: 'A4',
# margin_top: '2cm',
# margin_right: '2cm',
# margin_left: '2cm',
# margin_bottom: '2cm',
quiet: true, # No output during PDF generation
load_error_handling: 'abort', # Crash early
load_media_error_handling: 'abort', # Crash early
no_outline: true, # Disable the default outline
# disable_smart_shrinking: true, # Enable to keep the pixel/dpi ratio linear
}
config.wkhtmltopdf = Rails.root.join('vendor/wkhtmltopdf/linux-trusty-amd64/wkhtmltopdf').to_s
end
Most options are forwarded to wkhtmltopdf
(see below). You can get a list of supported options by running man wkhtmltopdf
. However, you should always have quiet: true
to keep your test output and logs clean.
There are concepts and formattings that only make sense on paper, so the question is how to implement them if you only have CSS:
page-break-before:always; page-break-after:always; page-break-inside
{
header_html: 'app/views/foo/bar/header.html',
footer_html: 'app/views/foo/bar/footer.html',
margin_top: '200px', # Height of the header, can be px or mm
margin_bottom: '150px', # Height of the footer, can be px or mm
header_spacing: 52.197, # margin_top converted from px => mm (You can see it as "margin-top: -200px")
footer_spacing: 0, # The top left edge of the footer is already at the right position
replace: { # You can pass custom data to your JavaScript this way
custom: {
:foo => 'bar',
}.to_json
}
Both files are independent DOM trees and share nothing. This means that styles, fonts and scripts must be included in each of these files.
I ended up in-lining style
and script
tags, since relative paths do not work when parsed from the wkhtmltopdf binary. With something like %style= Rails.application.assets.find_asset('pdf.sass').body.html_safe
in your layout you still split the CSS in two files.
Use the following JavaScript function to access variables such as the page number or the custom JSON encoded hash:
function allQueryInformation() {
var pdfInfo = {};
var queryStrings = document.location.search.substring(1).split('&');
for (var query in queryStrings) {
if (queryStrings.hasOwnProperty(query)) {
var keyValuePair = queryStrings[query].split('=', 2);
var key = keyValuePair[0];
var value = keyValuePair[1];
pdfInfo[key] = decodeURI(value);
}
}
return pdfInfo;
}
wkhtmltopdf
(0.12+).PDFKit is only a thin wrapper around the wkhtmltopdf
binary. Unfortunately old versions wkhtmltopdf
have many, many issues and your package sources don't usually come with a recent version. You should have at least 0.12.1, which you may obtain
from here
Show archive.org snapshot
. Bundle it with your application and tell PDFKit where to find the bundled binary like so:
PDFKit.configure do |config|
config.wkhtmltopdf = "#{Rails.root}/vendor/wkhtmltopdf/linux-precise-amd64/wkhtmltopdf"
end
When using version 0.12.6
and above, you'll need to add the following command line switch to your PDFKit
configuration to avoid crashes with cryptic error messages like PDFKit::ImproperWkhtmltopdfExitStatus
:
PDFKit.configure do |config|
config.default_options = {
...,
enable_local_file_access: true,
}
end
When using the PDFKit middleware on your development, you might experience that your application "locks up" whenever you request a .pdf
route.
This behavior is caused by a deadlock:
The easiest fix for this is to use Passenger Standalone for development, which can spawn multiple worker processes. However, Passenger does not allow to use debugger
or byebug
.
If you don't want to use Passenger you can also do this:
config/environments/development.rb
set config.allow_concurrency = true
(default in Rails 4)Note that this allows concurrent requests served from the same process using threads. This might cause unexpected behavior if your application or dependencies are not thread-safe. If you don't know what that means, your application probably isn't thread-safe.