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
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")
Once the code is executed, we have a beautiful PDF file "hello_world.pdf" that indeed contains the wording "Hello World!".
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")
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")
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')
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
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
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
And voilà 🎉
When I access the URL http://localhost:3000/orders.pdf
on my server, a download starts with the file:
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 (2)
I love your posts, you're always doing interesting things. I've never worked with PDFs using Ruby before.
Thanks for your comment 🫶🫶