DEV Community

Cover image for The easiest guide to generate PDF📜 using Prawn in your Rails project
Pimp My Ruby
Pimp My Ruby

Posted on • Updated on

The easiest guide to generate PDF📜 using Prawn in your Rails project

By the end of this guide, you will be able to generate a PDF on demand using the Prawn gem in your Ruby on Rails project!

PDF generation is a feature that I have often been asked for when developing Ruby on Rails projects. Let's see how I like to implement it in my projects.

Table of Contents

 1. What is Prawn?
    1.1. Managing the Cursor
    1.2. Managing Tables
 2. Let’s build something together!
 3. Pros and Cons of Using Prawn
    3.3. Pros
    3.4. Cons
 4. Conclusion

What is Prawn?

Prawn is a fast and lightweight Ruby library for generating PDFs. It has no external dependencies, which means you only need to install the gem in your project:

$ bundle add prawn
Enter fullscreen mode Exit fullscreen mode

Generating a PDF file is very simple. Prawn exposes a very clear API for that:

pdf = Prawn::Document.new
pdf.text "Hello World!", style: :bold, align: :center, size: 18
pdf.render_file("hello_world.pdf")
Enter fullscreen mode Exit fullscreen mode

Once the code is executed, we have a beautiful PDF file "hello_world.pdf" that indeed contains the wording "Hello World!".

Hello World pdf

Writing text in a PDF is great, but you can do much more with Prawn.

Managing the Cursor

When we create a PDF document, Prawn exposes us "Cursor". It's our guide to know where we are in the document we are creating.

As you saw in the example above, the text naturally appeared at the top of my page.

If I add other text blocks, they will follow each other, skipping a line:

pdf = Prawn::Document.new
pdf.text "Hello World!", style: :bold, align: :center, size: 18
pdf.text "Hello World!", style: :bold, align: :center, size: 18
pdf.text "Hello World!", style: :bold, align: :center, size: 18
pdf.text "Hello World!", style: :bold, align: :center, size: 18
pdf.render_file("hello_world.pdf")
Enter fullscreen mode Exit fullscreen mode

4 lines of Hello World! in a PDF

Thanks to this notion of cursor, we can change the layout of the elements using all the cursor methods:

pdf = Prawn::Document.new
pdf.text "Hello World!", style: :bold, align: :center, size: 18

pdf.stroke_horizontal_rule
pdf.pad 20 do
  pdf.text "Full padding", style: :bold, align: :center, size: 18
end
pdf.stroke_horizontal_rule

pdf.move_down 100

pdf.stroke_horizontal_rule
pdf.pad_bottom 20 do
  pdf.text "Padding Bottom", style: :bold, align: :center, size: 18
end
pdf.stroke_horizontal_rule

pdf.move_up 80
pdf.text "Last text but display before", style: :bold, align: :center, size: 18

pdf.move_cursor_to pdf.bounds.bottom + 20
pdf.text "Origin of the document", style: :bold, size: 18

pdf.render_file("hello_world.pdf")
Enter fullscreen mode Exit fullscreen mode

Rendered PDF file of the code below

A few additional explanations:

  • stroke_horizontal_rule: allows you to draw a horizontal line. Useful to see the impact of padding.
  • pad: Short for padding, allows you to add space above and below.
  • pad_bottom: Short for padding-bottom, allows you to add space below.
  • move_up: Moves the cursor up from its position.
  • move_cursor_to: Moves the cursor exactly to the specified line.

All these helpers are very practical for placing our content on the page.

Managing Tables

Creating tables is a very common use case when it comes to PDF generation. Let's see how to create a basic table:

# Prawn supports basic HTML tags. I use them to style my table headers
headers = %w[Name Age City].map { |header| "<font size='12'><b>#{header}</b></font>" }

data = [
  ['John', 25, 'New York'],
  ['Jane', 30, 'London'],
  ['Bob', 20, 'Paris'],
]

pdf = Prawn::Document.new
pdf.table([headers, *data], width: pdf.bounds.width, header: true,
          cell_style: {
            borders: %i[top bottom left right], padding: 5,
            size: 10,
            inline_format: true
          })

pdf.render_file('hello_world.pdf')
Enter fullscreen mode Exit fullscreen mode

Rendered PDF file of the code below

Cool, now we’re enough stuffed to build something real !

Let’s build something together!

Now that you know almost everything you need to know before getting started with PDF creation, let’s build.

I propose we integrate a feature that I have already been asked for: export data from my database to a PDF file.

Imagine: We are an e-commerce shop, and we already display a page containing all the orders information to our administrators.

We already expose an HTML view that reports the different orders as follows:

class OrdersController < ApplicationController
  def index
    @orders = Order.limit(25)
  end
end
Enter fullscreen mode Exit fullscreen mode

But you understand, HTML is old school; now we want to render PDFs.

For this, we will use Prawn!

In general, I like to create a fairly generic method in ApplicationController in order to dynamically use it in my OrdersController:

class ApplicationController < ActionController::Base
    private

    # collection: ActiveRecord_Relation
    # column_names: Array of symbols or strings that represent the attributes I want to extract
  def create_pdf_from(collection, column_names)
    headers = column_names.map { |header| "<font size='12'><b>#{header}</b></font>" }
    attributes = collection.pluck(column_names)

    pdf = Prawn::Document.new
    # The title of the document is the name of the model
    pdf.text collection.klass.name.humanize, align: :center, size: 24

    pdf.table([headers, *attributes], width: pdf.bounds.width, header: true,
              cell_style: {
                borders: %i[top bottom left right], padding: 5,
                size: 10,
                inline_format: true
              })

    pdf.move_down 10
    pdf.text "Rendered #{collection.size} records", align: :right, size: 12
    pdf
  end
end

Enter fullscreen mode Exit fullscreen mode

To render a PDF file, we will use respond_to to respond based on the requested format:

class OrdersController < ApplicationController
  def index
    @orders = Order.limit(25)
    respond_to do |format|
      format.html
      format.pdf do
        pdf = create_pdf_from(@orders, %i[id price username shipping_address])
        send_data pdf.render, filename: 'orders.pdf', type: 'application/pdf'
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

And voilà 🎉

When I access the URL http://localhost:3000/orders.pdf on my server, a download starts with the file:

Rendered PDF file of the code below


Pros and Cons of Using Prawn

Prawn is IMO the best tool to create PDF on the fly. I already used WickedPDF to generate PDF from HTML views, but it does not support well very complex views.

Pros

  • Lightweight and fast: Prawn is designed to be performant and uses minimal resources. This makes it an excellent choice for applications where PDF generation needs to be quick and efficient.
  • Flexible: Prawn offers great flexibility for creating complex PDF documents with various graphic and textual elements. You can easily add text, images, tables, shapes, and even draw directly on the PDF.
  • Self-contained: Prawn requires no external dependencies, which simplifies its installation and usage. You can easily integrate it into your Ruby on Rails project!
  • Rich documentation: Prawn has comprehensive documentation, making it easy to find solutions and learn best practices. The documentation is provided in PDF form… quite amusing.

Cons

  • DSL: It is important to note that despite its simplicity, it requires a little adaptation time. Indeed, like all libraries that manipulate graphics (I think of TKinter or SDL), Prawn has its own rules. In my opinion, you need to spend some time on the documentation to really get the hang of the tool.
  • Limited HTML integration: Prawn is not a gem that converts HTML views to PDF. However, it is important to note that HTML tags are supported in a limited way. You have a good example of this in how I style my table headers. If you are looking to render HTML views in PDF, use WickedPDF.

Conclusion

By following this guide, you have learned to generate PDFs on demand using the Prawn gem for your Ruby on Rails projects.

Prawn stands out for its lightness, speed, and flexibility, making PDF generation both powerful and easy to integrate. Although it has some limitations, its advantages make it a solid option for most PDF generation needs.

I encourage you to continue exploring the many possibilities offered by Prawn and to experiment with its various features to meet the needs of your projects. Feel free to share your own experiences and tips in the comments, I would love to read them!

To make sure you don't miss any of my upcoming articles on Ruby and Rails, subscribe.

Top comments (0)