0

Vote down!

How does Prawn's repeaters work?

Prawn is an amazingly configurable and flexible tool for PDF generation. It allows you to create boring financial style PDFs that can almost mimic a (static) Excel spreadsheet, and can also create a convincing party invitation letter.

One of Prawn's many tools to allow this kind of flexibility is the Repeater. It allows you to define a section of your PDF, and reuse it over and over, essentially stamping it on every page (or just on odd pages, or on the first 10 pages, etc, you can also configure that).

Let's create a simple PDF document as an example:

app/pdfs/base_pdf.rb:

class BasePdf < Prawn::Document
    def initialize
      super()
      generate_pdf
    end
    def generate_pdf
      repeat :all do
        text 'Test'
      end
      3.times { start_new_page }
    end
    end

app/controllers/application_controller.rb:

def report
      send_data BasePdf.new.render, type: 'application/pdf', disposition: 'inline'
    end

Now we need to go to localhost:3000/report.pdf (after configuring the route) and the PDF will be displayed, which will contain 3 pages with the same 'Test' text printed on each of them.

Let's now understand how this happens: When that code calls repeat :all, and pass a block to it, it's essentially telling Prawn to:

  1. Create a new Stamp (think of it as an actual stamp).
  2. Repeat this stamp on all pages of the PDF (3 in our example). It also doesn't matter if you called repeat after calling start_new_page before, it will render the repeater on every page.
  3. Render whatever is inside the block into this new stamp (in our case a simple text), so when Prawn "stamps" it it will stamp exactly the same thing over and over.

However, this "stamping" doesn't happen immediately, nor does it happen when a new page is started. Prawn will only "stamp" the repeater after the whole document has been defined, when the method render is called. Usually you want to call render when you want to send the PDF to the user, so it will always be at the end.

The issue with dynamic repeaters

What if you want to change what's inside this Stamp? Let's image a new situation, more akin to real world, where I have a PDF with many pages, and I need to print different information on the header for different pages. If you already know the exact number of pages the PDF will have and which page have which information, you can do something like this:

app/pdfs/base_pdf.rb

def header
      repeat :all, dynamic: true do
        if (page_number < 5)
          text 'One thing'
        else
          text 'Other thing'
        end
      end
    end

This is all fine and dandy, until you get yourself into a situation like this:

app/pdfs/base_pdf.rb

  # I want to print the name of the country of the first city in the current page
      def header_country
        repeat :all, dynamic: true do
          text @current_country
        end
      end
      # We'll assume cities is a hash, where each key is a country and contains an array of cities for each country
      def print_cities_of_the_world(all_cities)
        all_cities.each do |country, cities|
          @current_country = country
          cities.each do |city|
            text city
          end
        end
      end

That's why dynamic repeaters exist. They allow you to change the contents of the stamp for each page.

Related Articles

Please log in to post a comment: