PDF Generation in Rails

Share this article

pdf

The ability to download data in pdf format is a common requirement that you will encounter when building web applications. There are different ways that this can be achieved in Rails. We are going to look at the two major ways used to generate pdf documents: with Ruby using a DSL for defining and styling the documents, or by using a library that will convert your HTML to PDF.

There are three popular gems we’ll focus on today:

HTML to PDF or Ruby Generation?

The answer to this usually depends on preference and project requirements. HTML to PDF can be faster, especially if you already have a view that displays the content you want in your PDF. In this case, you won’t have to write that much more code to generate a PDF file. However, this method can make it harder to control the layout of the document, especially when dealing with multi-page documents. Content will tend to be cut off and split between pages. It’s true that, with some CSS styling, you can have some control over the page breaks. However, for more complicated PDF documents that span several pages and contain variable length content, headers and footers, it will be difficult to control how each page is rendered. In these cases, it might make more sense to use Prawn.

Using a library like Prawn, you have to do all the content styling and positioning on your own using Prawn’s DSL. The advantage here is more control over how things are displayed and where pages break.

We are going to generate a PDF file for the webpage shown below which contains some static text, an image and a table of some database records.

pdf_webpage_image

Prawn

To use Prawn, include the gem in your Gemfile and run bundle install

gem 'prawn'

Register the PDF mime type in the config/initializers/mime_types.rb file.

Mime::Type.register "application/pdf", :pdf

Now we need to set up the controller action to respond to requests for PDF format.

For my Products controller, I have an index action which I’m going to modify as shown.

class ProductsController < ApplicationController
  def index
    @products = Product.all

    respond_to do |format|
      format.html
      format.pdf do
        pdf = Prawn::Document.new
        send_data pdf.render, filename: 'report.pdf', type: 'application/pdf'
      end
    end

  end
end

The above will generate a PDF file with no content when .pdf is appended to the end of the particular url. In my case http://localhost:3000/products.pdf.

To separate out the pdf generation code from the controller, I created an app/pdfs directory and added a new class in the file app/pdfs/report_pdf.rb.

I changed the controller code to use the new class.

class ProductsController < ApplicationController
  def index
    @products = Product.all

    respond_to do |format|
      format.html
      format.pdf do
        pdf = ReportPdf.new(@products)
        send_data pdf.render, filename: 'report.pdf', type: 'application/pdf'
      end
    end

  end
end

The code below shows how to generate a PDF of the webpage shown above. I have commented it to show what I’m doing.

class ReportPdf < Prawn::Document
  def initialize(products)
    super()
    @products = products
    header
    text_content
    table_content
  end

  def header
    #This inserts an image in the pdf file and sets the size of the image
    image "#{Rails.root}/app/assets/images/header.png", width: 530, height: 150
  end

  def text_content
    # The cursor for inserting content starts on the top left of the page. Here we move it down a little to create more space between the text and the image inserted above
    y_position = cursor - 50

    # The bounding_box takes the x and y coordinates for positioning its content and some options to style it
    bounding_box([0, y_position], :width => 270, :height => 300) do
      text "Lorem ipsum", size: 15, style: :bold
      text "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse interdum semper placerat. Aenean mattis fringilla risus ut fermentum. Fusce posuere dictum venenatis. Aliquam id tincidunt ante, eu pretium eros. Sed eget risus a nisl aliquet scelerisque sit amet id nisi. Praesent porta molestie ipsum, ac commodo erat hendrerit nec. Nullam interdum ipsum a quam euismod, at consequat libero bibendum. Nam at nulla fermentum, congue lectus ut, pulvinar nisl. Curabitur consectetur quis libero id laoreet. Fusce dictum metus et orci pretium, vel imperdiet est viverra. Morbi vitae libero in tortor mattis commodo. Ut sodales libero erat, at gravida enim rhoncus ut."
    end

    bounding_box([300, y_position], :width => 270, :height => 300) do
      text "Duis vel", size: 15, style: :bold
      text "Duis vel tortor elementum, ultrices tortor vel, accumsan dui. Nullam in dolor rutrum, gravida turpis eu, vestibulum lectus. Pellentesque aliquet dignissim justo ut fringilla. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut venenatis massa non eros venenatis aliquet. Suspendisse potenti. Mauris sed tincidunt mauris, et vulputate risus. Aliquam eget nibh at erat dignissim aliquam non et risus. Fusce mattis neque id diam pulvinar, fermentum luctus enim porttitor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."
    end

  end

  def table_content
    # This makes a call to product_rows and gets back an array of data that will populate the columns and rows of a table
    # I then included some styling to include a header and make its text bold. I made the row background colors alternate between grey and white
    # Then I set the table column widths
    table product_rows do
      row(0).font_style = :bold
      self.header = true
      self.row_colors = ['DDDDDD', 'FFFFFF']
      self.column_widths = [40, 300, 200]
    end
  end

  def product_rows
    [['#', 'Name', 'Price']] +
      @products.map do |product|
      [product.id, product.name, product.price]
    end
  end
end

For more about the PDF formatting rules available, check out the Prawn manual.

PDFKit

For PDFKit, first include the gem in your Gemfile

gem 'pdfkit'

and run bundle install

You can generate PDF documents by pointing to a html file or website as shown below.

# From https://github.com/pdfkit/pdfkit#usage

# PDFKit.new takes the HTML and any options for wkhtmltopdf
# run `wkhtmltopdf --extended-help` for a full list of options
kit = PDFKit.new(html, :page_size => 'Letter')
kit.stylesheets << '/path/to/css/file'

# Get an inline PDF
pdf = kit.to_pdf

# Save the PDF to a file
file = kit.to_file('/path/to/save/pdf')

# PDFKit.new can optionally accept a URL or a File.
# Stylesheets can not be added when source is provided as a URL of File.
kit = PDFKit.new('http://google.com')
kit = PDFKit.new(File.new('/path/to/html'))

# Add any kind of option through meta tags
PDFKit.new('<html><head><meta name="pdfkit-page_size" content="Letter"')
PDFKit.new('<html><head><meta name="pdfkit-cookie cookie_name1" content="cookie_value1"')
PDFKit.new('<html><head><meta name="pdfkit-cookie cookie_name2" content="cookie_value2"')

You could also use a middleware solution that allows users to generate PDFs of any page on the website by appending .pdf to the end of the URL. That is what we are going to use here.

To add the middleware, include the following in the /config/application.rb file (This is for Rails version 3 and above).

module RailsPdf
  class Application < Rails::Application
      config.middleware.use PDFKit::Middleware
      .
      .
      .
  end
end

Restart the server, navigate to a page, and add .pdf to the end of the URL. You will get a PDF version of the webpage.

It’s also possible to use a link to download the page as a PDF file. In haml:

= link_to 'Download Report', products_path(format: 'pdf')

To exclude the link in the pdf file, add an id or class name to the tag and set a display property for it in CSS.

@media print {
  .pdf_exclude {
        display: none;
  }
}

Some Things To Note

I

If wkhtmltopdf is not installed on your system, then you will get a similar error as shown below

PDFKit::NoExecutableError in ProductsController#index

No wkhtmltopdf executable found at >> Please install wkhtmltopdf - https://github.com/pdfkit/PDFKit/wiki/Installing-WKHTMLTOPDF

For instructions on how to install wkhtmltopdf check out this wiki page.

Another option for installing the wkhtmltopdf binaries is through the gem wkhtmltopdf-binary. Add it to your Gemfile and run bundle install

gem 'wkhtmltopdf-binary'

II

If content is getting cut off where you don’t want it to, for example a table being split into two pages, you can specify a page break before the table is rendered so that it appears on its own page.

@media print {
  .page-break { 
    display: block; 
    page-break-before: always; 
  }
}

III

wkhtmltopdf doesn’t play well with relative URLs of any external files (images, stylesheets, javascript) that you might be using. If you use the regular tags like stylesheet_link_tag and try to generate a document, wkhtmltopdf will hang when loading the assets. Using absolute paths (either file paths or urls including the domain) for assets solves this problem.

Another possible solution is to use inline styles. For example instead of the stylesheet_link_tag tag you could use:

<style type="text/css">
    <%= Rails.application.assets.find_asset('application.css').to_s.html_safe %>
</style>

IV

For any content that appears on the webpage but you don’t want displayed in the pdf document (e.g. the ‘Download PDF’ link in the above example), just mark it up and hide it with CSS.

@media print {
    .hide_in_pdf {
        display:none;
    }
}

Wicked PDF

To use Wicked PDF, first install wkhtmltopdf. Alternatively, you could use the wkhtmltopdf-binary gem by including it in your Gemfile.

Add the wicked_pdf to your Gemfile and run bundle install.

gem 'wicked_pdf'

Register the PDF mime type in config/initializers/mime_types.rb

Mime::Type.register "application/pdf", :pdf

Like PDFKit, Wicked PDF comes with a middleware that allows users to get a PDF view of any page on your site by appending .pdf to the URL. This is achieved by adding config.middleware.use WickedPdf::Middleware to the /config/application.rb file. I won’t be using the middleware here. Instead, I will create template and layout files for the PDF, and modify my controller action to handle PDF-format requests.

I modified the ProductsController as shown. Here, I am specifying the name of the PDF file and the layout used to generate it. For more information on the available options you can use, look at the README file.

class ProductsController < ApplicationController
  def index
    @products = Product.all
    respond_to do |format|
      format.html
      format.pdf do
        render :pdf => "report", :layout => 'pdf.html.haml'
      end
    end
  end
end

Here is the layout file app/views/layouts/pdf.html.haml.

!!!
%html
  %head
  %title RailsWickedPdf
    = wicked_pdf_stylesheet_link_tag    "application", :media => "all"
    = wicked_pdf_javascript_include_tag "application"
    = csrf_meta_tags
  %body
    = yield

And the template file app/views/products/index.pdf.haml.

.container
  .row
    = wicked_pdf_image_tag('header.png')
  .row
    .col-xs-6
      %h3
        Lorem ipsum
      %p
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse interdum semper placerat. Aenean mattis fringilla risus ut fermentum. Fusce posuere dictum venenatis. Aliquam id tincidunt ante, eu pretium eros. Sed eget risus a nisl aliquet scelerisque sit amet id nisi. Praesent porta molestie ipsum, ac commodo erat hendrerit nec. Nullam interdum ipsum a quam euismod, at consequat libero bibendum. Nam at nulla fermentum, congue lectus ut, pulvinar nisl. Curabitur consectetur quis libero id laoreet. Fusce dictum metus et orci pretium, vel imperdiet est viverra. Morbi vitae libero in tortor mattis commodo. Ut sodales libero erat, at gravida enim rhoncus ut.
    .col-xs-6
      %h3
        Duis vel
      %p
        Duis vel tortor elementum, ultrices tortor vel, accumsan dui. Nullam in dolor rutrum, gravida turpis eu, vestibulum lectus. Pellentesque aliquet dignissim justo ut fringilla. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut venenatis massa non eros venenatis aliquet. Suspendisse potenti. Mauris sed tincidunt mauris, et vulputate risus. Aliquam eget nibh at erat dignissim aliquam non et risus. Fusce mattis neque id diam pulvinar, fermentum luctus enim porttitor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
  .row
    %table.table.table-striped
      %thead
        %th #
        %th Product
        %th Price
      %tbody
        - @products.each do |product|
          %tr
            %td
              = product.id
            %td
              = product.name
            %td
              = product.price

You’ll notice in the above two files, I’m using the following Wicked helpers.

wicked_pdf_stylesheet_link_tag
wicked_pdf_javascript_include_tag
wicked_pdf_image_tag

These are necessary if you are using external files. wkhtmltopdf is run outside of the Rails application, therefore any file you link to must be included with an absolute address.

Using the regular stylesheet_link_tag and javascript_include_tag tags will cause the application to hang when a PDF is requested. Also, the image will not be rendered if the regular img tag is used.

Conclusion

We have looked at three different approaches to PDF generation. Deciding on which to use depends on a variety of factors, including your prefered language (HTML or DSL), ease of accomplishing a task with one library over the other, complexity of the format of the PDF document, among other considerations. I hope this article helps you make that decision.

Frequently Asked Questions on PDF Generation in Rails

What are the best libraries for generating PDFs in Rails?

There are several libraries available for generating PDFs in Rails. Some of the most popular ones include Prawn, Wicked PDF, and PDFKit. Prawn is a pure Ruby PDF generation library that provides a lot of flexibility and control over the PDF content. Wicked PDF and PDFKit, on the other hand, are wrappers for the wkhtmltopdf utility, which converts HTML to PDF using WebKit. The choice of library depends on your specific needs and requirements.

How can I add images to my PDFs in Rails?

Adding images to your PDFs in Rails can be done using the image method provided by the Prawn library. You just need to specify the path to the image file and the position where you want to place the image in the PDF. You can also adjust the size of the image using the :scale or :fit option.

Can I generate PDFs from HTML in Rails?

Yes, you can generate PDFs from HTML in Rails using libraries like Wicked PDF or PDFKit. These libraries use the wkhtmltopdf utility to convert HTML to PDF. You just need to provide the HTML content, and the library will take care of the rest.

How can I style my PDFs in Rails?

Styling your PDFs in Rails can be done using CSS when you’re generating PDFs from HTML. Libraries like Wicked PDF and PDFKit support this feature. However, if you’re using Prawn, you’ll need to use the methods and options provided by the library to style your PDF content.

Can I generate PDFs with dynamic content in Rails?

Yes, you can generate PDFs with dynamic content in Rails. You can use ERB templates to generate the HTML content, which can then be converted to PDF using libraries like Wicked PDF or PDFKit. If you’re using Prawn, you can generate the PDF content dynamically using Ruby code.

How can I add page numbers to my PDFs in Rails?

Adding page numbers to your PDFs in Rails can be done using the number_pages method provided by the Prawn library. You can specify the position, alignment, and style of the page numbers.

Can I generate PDFs in different languages in Rails?

Yes, you can generate PDFs in different languages in Rails. You just need to ensure that the font you’re using supports the characters of the language. Prawn, for example, supports UTF-8 encoding, which covers a wide range of languages.

How can I secure my PDFs in Rails?

Securing your PDFs in Rails can be done using the encrypt_document method provided by the Prawn library. You can specify a password to restrict access to the PDF.

Can I generate PDFs with tables in Rails?

Yes, you can generate PDFs with tables in Rails. Libraries like Prawn provide methods for creating tables with a lot of flexibility and control over the table structure and style.

How can I test PDF generation in Rails?

Testing PDF generation in Rails can be done using libraries like PDF::Inspector, which allows you to analyze the content of the generated PDF. You can also use Capybara and PhantomJS to test the HTML content before it’s converted to PDF.

Joyce EchessaJoyce Echessa
View Author

I am a web developer who dabbles in mobile development from time to time. You can find me on Twitter @joyceechessa to see what I’m up to.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week