Working with PDFs in Ruby
Tags: ruby, pdf
Published: 2023-10-24 21:55:48

*This article was originally written by [Aestimo Kirina](https://www.honeybadger.io/blog/authors/aestimonjuguna/) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/ruby-pdfs/).*

We live in a world of documents. In almost every aspect of business and life, there's a document involved. Whether you are working with invoices and contracts at your workplace or enjoying your favorite mystery thriller, in one way or another, a document is involved. The file format that forms the basis of most of these documents is the versatile portable document file format, more commonly referred to as PDF.

In the context of a Ruby or Rails app, a PDF file can be produced by converting existing HTML/CSS files into PDF documents or by using a domain-specific language (DSL) specifically built for PDF generation. In this article, we will explain how to use these two methods to generate PDFs using three different gems: PDFkit and WickedPDF, which use the HTML/CSS-to-PDF method, and the Prawn gem, which uses a powerful DSL instead.

We will also discuss how to style your PDF files to improve their user-friendliness, including how to embed images into them. Then, to wrap up, we'll outline how you can use Action mailer to send emails with PDF attachments.

Let's get into it.

## Prerequisites

To follow along with the examples in this article, you'll need a few basics covered:

- Ruby 2.0+ installed
- Bundler installed
- Rails 6.0+ installed
- A basic to intermediate command of Ruby (and Rails)

## Brief Intro to the PDF File Format

PDF stands for "portable document format", a file format developed by Adobe in the early 90s to make it a breeze to present documents without relying too much on the underlying system software, hardware, or operating system.

So, let's see how we can use this popular file format in the context of a Rails app. To do that, we'll check out three popular gems for generating PDFs.

## WickedPDF

We’ll start with the [WickedPDF](https://github.com/mileszs/wicked_pdf) gem, which is powered by the [wkhtmltopdf](https://github.com/wkhtmltopdf/wkhtmltopdf) command-line library.

This gem works by converting HTML/CSS documents into their PDF equivalents.

### When to Use WickedPDF

If you'd rather use the HTML/CSS skills you already have instead of learning a new DSL to generate PDFs, then the WickedPDF gem is a good choice.

### Installing and Using WickedPDF

Add the gem (include the wkhtmltopdf gem since it powers WickedPDF) to your app's Gemfile, and then run ```bundle install```:

```ruby
gem 'wicked_pdf'
gem 'wkhtmltopdf-binary'
```

After that's done, go ahead and run the generator to get the WickedPDF initializer file:

```bash
rails generate wicked_pdf
```

Then, we'll need to tell our Rails app about the PDF file extension by editing the _mime_types.rb_ initializer file found in the ```../config/initializers``` directory.

_Tip: In very simple terms, a mime type is an identifier of file formats and how such formats should be transmitted across the Web._

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

With that, our Rails app knows how to respond to the PDF file type.

Finally, you'll need to edit your controller file to respond to the PDF file format (in my case, I'll work with the show action of an orders controller, but you can use this in whatever controller your project is using where you need PDF file generation):

```ruby
class OrdersController < ApplicationController
    
    def show
    respond_to do |format|
      format.html
      format.pdf do
    #   It's important to include a corresponding view template; otherwise, you'll get a missing template error.
        render pdf: "#{@order.id}", template: 'orders/show.html.erb'
      end
    end
end
```

Now, if you append ".pdf" to your show view, ```localhost:3000/orders/1.pdf```, you'll be greeted by the corresponding PDF view. Yay!

So far, our app can generate order PDFs. However, what happens when a colleague in logistics requests these order documents to be sent over as email attachments? How would we go about doing that?

## PDF Email Attachments Via WickedPDF And ActionMailer

Extending our order app example, let's assume a colleague in logistics needs the details of each order that comes in as an email attachment.

First, we create a relevant mailer:

```bash
rails generate mailer OrderMailer new_order 
```

Edit the generated mailer to include the PDF file attachment:

```ruby
class OrderMailer < ApplicationMailer
  def new_order
    @order = Order.first
    # The attachment...
    attachments["Order-#{@order.id}.pdf"] = WickedPdf.new.pdf_from_string(
      render_to_string(template: 'orders/show.html.erb', pdf: "Order-#{@order.id}")
    )

    mail to: "to@example.org"
  end
end
```

With that, visit ```http://localhost:3000/rails/mailers/order_mailer/new_order``` to view the email preview (note the PDF attachment included in the email):

![Email preview with attachment](https://www.honeybadger.io/images/blog/posts/ruby-pdfs/email-preview.png)

## Prawn PDF

[Prawn PDF](https://github.com/prawnpdf/prawn) is a pure Ruby PDF- generation library that comes packed with features, such as PNG and JPG image embeds, generated file encryption, right-to-left text rendering, a way to incorporate outlines for easy document navigation, and a lot more.

Prawn comes with its own DSL, which drives its powerful PDF generation abilities.

### When to Use Prawn

Although it's not a fully featured report generation library like the well-known [Jasper Reports](https://community.jaspersoft.com/), with a bit of work using it's powerful DSL, you can generate some really cool and rather complex PDF documents with Prawn.

Even so, it's important to note that Prawn isn't everything. If you want to generate PDFs from HTML, then you should look elsewhere, as the gem provides very limited support for inline styling, something of a hurdle if you're working with rich HTML documents.

### Installing and Using Prawn

To get started with Prawn, install it with this command:

```bash
gem install prawn
```

Or, if you have an existing Rails app, edit the Gemfile with the following:

```ruby
gem 'prawn'
```

Then, run _bundle_ to have it installed for your app.

With the gem successfully installed, just like we did with the WickedPDF gem, you will need to make your application aware of the "PDF" file type by registering it as a mime type.

Open the ```mime_types.rb``` file and add the following line:

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

Now, to see the gem in action, go to the controller you want to respond with a PDF (I’m using the same example as I did for the WickedPDF code example):

```ruby
class OrdersController < ApplicationController

  def index
    @orders = Order.all
  end

  def show
  @order = Order.find(params[:id])
    respond_to do |format|
        format.html 
        # Here, we define our new PDF file format
        format.pdf do
        # ...instatiate a new Prawn document
          pdf = Prawn::Document.new
        #   ...then specify the data to be included in the PDF document
          pdf.text "This is order no: #{@order.id}"
        #   ..and finally render the PDF file
          send_data pdf.render,
            filename: "#{@order.id}.pdf",
            type: 'application/pdf',
            disposition: 'inline'
        end
    end
  end

  def new
    @order = Order.new
  end
end
```

The Prawn gem's DSL can also be used to draw complex vector images, as well as embed images into the generated PDF documents (for the purposes of this article, we'll only show the image embed functionality).

### Adding Images to PDFs Using Prawn

Hopefully, you now have a basic idea of how to style PDF documents in your Rails app. However, what if you want to add images?

Using our very simplified Prawn gem example from earlier, this is how you would do it (only the show action is shown):

```ruby
def show
    respond_to do |format|
        format.html
        format.pdf do
          pdf = Prawn::Document.new
          pdf.text "This is order no: #{@order.id}"
          # Adding an image to a PDF file...
          pdf.image Rails.root.join("public", "images", '1.png').to_s, width: 450
          send_data pdf.render,
            filename: "#{@order.id}.pdf",
            type: 'application/pdf',
            disposition: 'inline'
        end
    end
  end
```

Obviously, the gem packs more punch than what we've shown so far. To delve deeper, head over to the [Prawn website](https://github.com/prawnpdf/prawn).

## PDFkit Gem

Like the WickedPDF gem, the [PDFkit gem](https://github.com/pdfkit/pdfkit) uses wkhtmltopdf on the backend to convert plain HTML and CSS files into PDF documents.

### When to Use PDFkit

If you need to do a lot of HTML to PDF conversion, then this gem would make a good choice.

### Using PDFkit

Add the gem:

```bash
gem install pdfkit
```

Remember to install the wkhtmltopdf library. Just follow the instructions found [here](https://github.com/pdfkit/pdfkit/wiki/Installing-WKHTMLTOPDF) to do so.

Once that is done, go ahead and create a simple Ruby file in your favorite folder (mine's called "testing-pdfkit.rb", but you can call yours whatever you want.)

Then, edit your file to look like the one below:

```ruby
require "pdfkit"
 
kit = PDFKit.new(<<-HTML)
  <h1>My PDFkit Invoice</h1>
 
  <table>
    <thead>
        <tr>
          <th>Item</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>
          <tr>
            <td>Item #1</td>
            <td>$27.00</td>
          </tr>
          <tr>
            <td>Item #2</td>
            <td>$100.00</td>
          </tr>
      </tbody>
  </table>
HTML
 
kit.to_file("pdfkit_invoice.pdf")
```

Run ```ruby testing-pdfkit.rb``` to generate a PDF file in the root folder of your script.

The example we've shown will produce a very plain PDF document. However, with some basic styles applied, we can make it look much better.

## Applying Basic Styles to PDFs Using PDFkit

There's a good chance the PDF documents you interact with in your daily life, whatever they may be, such as a favorite ebook or a company report, will look polished and professional.

Next, let's add some styling to our code example using the PDFkit gem:

```ruby
require "pdfkit"
 
kit = PDFKit.new(<<-HTML)
  # CSS styles added...
  <style>
    table {
      background-color: #ff33cc;
    }

    tbody tr:nth-child(odd) {
      background-color: #ff33cc;
    }

    tbody tr:nth-child(even) {
      background-color: #e495e4;
    }

    h1 {
      text-align: center;
      color: black;
      margin-bottom: 100px;
    }
    .notes {
      margin-top: 100px;
    }

    table {
      width: 100%;
    }
    th {
      text-align: left;
      color: black;
      padding-bottom: 15px;
    }
  </style>
 
  <h1>My PDFkit Invoice</h1>
 
  <table>
    <thead>
        <tr>
          <th>Item</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>
          <tr>
            <td>Item #1</td>
            <td>$27.00</td>
          </tr>
          <tr>
            <td>Item #2</td>
            <td>$100.00</td>
          </tr>
      </tbody>
  </table>
HTML
 
kit.to_file("pdfkit_invoice.pdf")
```

_Tip: Using a more advanced PDF library like the Prawn gem will give you even more styling options. See the [Prawn manual](https://prawnpdf.org/manual.pdf) to explore the gem's full capabilities._

## Wrapping Up

In this article, we've highlighted how to generate PDF documents using three gems. We've also explored how to use the different gems to do things like add images to PDFs, send PDF attachments via email, and add basic styles to our documents.

This article is more of an outline than a deep dive. You're more than welcome to explore each gem's unique style of PDF generation, as well as how to do more complex documents than what we've covered here.

Happy coding!