Hackable PDF Typesetting in Ruby with Prawn

Robert Qualls
Robert Qualls

Generating portable documents can be tricky. For the most part, this task has moved from typesetting languages to WYSIWYG editors like Scribus and Adobe InDesign.

But automation has its own benefits. Instead of the kitchen-sink, it’s nice to have a hackable platform that can be easily tweaked to get us what we want.

Prawn is the spiritual successor to PDF::Writer. It is currently well-maintained and, like many other Ruby projects, Prawn is intended as a platform on which tools can be built.


Before we get into Prawn, the elephant deserves mentioning.

TeX was written by Donald Knuth and released in 1978. At that time, digital printing was fairly new and Knuth had just gotten back a galley proof of his second volume of “The Art of Computer Programming.” He was shocked by the quality and decided he could do a better job in a few months. It ultimately took him a decade (source).

TeX is famous for being bug free. Knuth had schemes in which those who found bugs in his documents or programs were paid a fee. The TeX program’s rewards followed the Wheat and Chessboard Problem, starting at $1.28 and reaching $327.68. Typical of Knuth, the version number of TeX is converging to π.

LaTeX (pronounced “lahtehk”) is a macro package for TeX that makes it easier to produce standard documents. These days, TeX is often referred to as LaTeX, and it is the de facto standard for creating scientific or mathematical documents in academia.

Here’s an example of LaTeX:

\title{LaTeX Hello World}
\author{Robert Qualls}
Hello World

Those interested in LaTeX packages would want to check out CTAN, the Comprehensive TeX Archive Network, and the LaTeX rubygems. Bonus points if you recognize the similarity to CPAN: the Comprehensive Perl Archive Network.

Installing Prawn

If you’re running a Rails operation, LaTeX isn’t the most convenient approach for generating documents. There are some gems available, but it’s really nice when the logic and the presentation are in the same language. That’s what Prawn supplies.

First, we need to install the Prawn gem:

gem install prawn

We can verify that Prawn is working with the following test:

require "prawn"

Prawn::Document.generate("hello.pdf") do
  text "Hello World!"

Making Text

Most of Prawn’s commands are fairly minimalistic and what you would expect:

require "prawn"

Prawn::Document.generate("styling_text.pdf") do
  text "Default text styling"
  text "Blue 16pt Helvetica", size: 16, font: "Helvetica", color: "0000FF"
  text "Aligned Center", align: :center

  font_size 12
  font "Courier" do
    text "Size 12 Courier"
    font_size 10 do
      text "Slightly smaller Courier"
  text "Default font size 12"

  font "Helvetica"
  3.times do |i|
    text "Helvetica with leading 10 line #{i}", leading: 10

Consistent with Ruby, Prawn provides multiple ways to accomplish the same thing. Most DSL methods can optionally be scoped with a block.

Moving Around

There are two important geometric locations in Prawn:

  1. the origin
  2. the cursor

The origin, [0,0] is at the bottom-left corner of the document. This can be confusing as the cursor starts in the top-left corner.

“[0,0] what?”, you might ask. The default unit in Prawn is a PDF Point, where one PDF Point is equal to 1/72 of an inch. Because America.

Moving the cursor around is as simple as move_down, move_up, or move_cursor_to:

require "prawn"

Prawn::Document.generate("moving_around.pdf") do
  text "#A At the top, cursor position #{cursor}"

  move_down 50
  text "#B Down 50, cursor position #{cursor}"

  move_cursor_to bounds.bottom + font_size
  text "#C at the bottom, cursor position #{cursor}"

  move_up 50
  text "#D Up 50 from #C, cursor position #{cursor}"

  move_cursor_to bounds.top / 2
  text "#E In the middle, cursor position #{cursor}"

Adding Images

Prawn lets us use either local paths or external URLs with open-uri:

require 'prawn'
require 'open-uri'

Prawn::Document.generate("image.pdf") do
  text "Dog", align: :center, color: "333333", size: 42
  move_down 20
  text "Homo sapiens' best friend", align: :center, color: "555555", size: 26
  url = "https://pixabay.com/static/uploads/photo/2014/03/14/20/13/dog-287420_960_720.jpg"
  image open(url), fit: [500, 500], position: :center

It also supports backgrounds. Unfortunately, Prawn doesn’t seem to offer any way to fit the background when done this way, so you may need to tinker with the size. This can be done with ImageMagick and the mini_magick gem:

$ brew install imagemagick
$ gem install mini_magick

I decided to go with 650×950 for the image I used:

require 'prawn'
require "mini_magick"

url = "https://pixabay.com/static/uploads/photo/2015/11/19/08/12/milky-way-1050526_960_720.jpg"
filename = "fitted_background.jpg"
image = MiniMagick::Image.open(url)
image.resize "650x950"
image.write filename
background = filename

Prawn::Document.generate("background.pdf", background: background) do
  options = { align: :center, valign: :center, leading: 25, color: "C1C1C1" }
  text "\"Somewhere, something incredible is waiting to be known\"", options.merge({ size: 20 })
  text "- Carl Sagan", options.merge({ size: 18 })

Book Cover

Here is the source for the fake book cover.

require "prawn"

Prawn::Document.generate("oruby_cover.pdf") do
  move_down 60

  image "shrimp.png", fit: [500, 400], position: :center

  move_cursor_to bounds.top

  shape_color = "008888"
  font "Times-Roman"

  fill_color shape_color
  fill_rectangle [0, bounds.top], bounds.width, 20

  move_down 25

  fill_color "000000"
  text "Because InDesign is for scrubs", :size => 20, :style => :italic, :align => :center

  bounding_box([0, bounds.top - 50], :width => bounds.width, :height => bounds.height) do
    move_down 380
    text "using", :size => 40, :style => :italic
    move_up 380

    fill_color shape_color
    fill_rectangle [0, 300], 550, 200

    fill_color "FFFFFF"
    move_down 410
    font "Times-Roman"
    text "Prawn", :size => 165, :align => :center

    fill_color "000000"
    font "Helvetica"
    draw_text "O'RUBY", :at => [0, bounds.bottom + 50], :size => 30

    move_to [0, bounds.bottom + 100]
    font "Times-Roman"
    draw_text "Robert Qualls", :at => [bounds.right - 100, bounds.bottom + 50], :size => 20, :style => :italic

Image courtesy of Pearson Scott Foresman(Source)


If you want to learn more about Prawn, the Prawn team has a nice manual.

Prawn’s DSL approach isn’t for everybody nor the best solution for all situations. In fact, HTML+CSS to PDF is probably the future of programmatic typesetting. Hardcoding instead of flexible design feels very 20th century, but Prawn is fairly popular, so there’s a good chance you will encounter it from time to time. Thankfully, we have wkhtmltopdf wrapped by the PDFKit gem. If you want to see a comparison, be sure to read PDF Generation in Rails.

Frequently Asked Questions (FAQs) about Prawn PDF Typesetting in Ruby

What is Prawn in Ruby and why is it used?

Prawn is a pure Ruby PDF generation library that enables developers to create complex PDF documents. It’s used because it provides a lot of flexibility and control over how the PDFs are generated. With Prawn, you can add images, graphics, formatted text, and more to your PDFs. It’s also very efficient and can generate large PDFs quickly.

How do I install Prawn in Ruby?

To install Prawn, you need to add the gem to your Gemfile. You can do this by adding the line gem 'prawn' to your Gemfile and then running bundle install. This will install the Prawn gem and make it available for use in your Ruby application.

How do I generate a PDF with Prawn?

Generating a PDF with Prawn involves creating a new Prawn::Document and then using methods like text, image, and stroke to add content to the PDF. Once you’ve added all your content, you can save the PDF to a file with the render_file method.

Can I add images to my PDFs with Prawn?

Yes, Prawn supports adding images to PDFs. You can use the image method to add an image, specifying the path to the image file and the position where you want the image to appear in the PDF.

How do I add formatted text to a PDF with Prawn?

Prawn provides several methods for adding formatted text to a PDF. You can use the text method to add plain text, and the formatted_text method to add text with various styles and formats. You can also use the font method to change the font used for the text.

Can I draw shapes and lines in my PDFs with Prawn?

Yes, Prawn includes methods for drawing a variety of shapes and lines in your PDFs. You can use methods like stroke_horizontal_line, stroke_rectangle, and fill_circle to draw lines, rectangles, and circles, respectively.

How do I save my generated PDF to a file with Prawn?

Once you’ve added all your content to your Prawn::Document, you can save it to a file with the render_file method. You just need to pass the path where you want the file to be saved as an argument to this method.

Can I generate multi-page PDFs with Prawn?

Yes, Prawn supports generating multi-page PDFs. You can use the start_new_page method to start a new page in your PDF.

How do I add a table to my PDF with Prawn?

Prawn includes a Table class that you can use to add tables to your PDFs. You can create a new Prawn::Table, add rows to it with the rows method, and then draw it in your PDF with the draw method.

Are there any known vulnerabilities with Prawn?

As with any software, there may be potential vulnerabilities with Prawn. It’s always a good idea to keep your software up to date and to follow best practices for secure coding. You can check for known vulnerabilities on websites like Snyk.