The Prawn PDF library contains an option for adding arbitrary images as background onto each page of a PDF. Unfortunately this option cannot be customized further leading to a couple of limitations that we worked around at my company. The letters and PDFs sent out always have different page layouts. The first page contains a much more detailed background image containing things like addresses, bank information and other information required by German law. The second and all following pages only contain a stripped-down version of that background image. As the first page also contains the recipient’s address the area containing the actual text differs from one page to another.
Achieving this with Prawn is rather easy if you know that all of Prawn’s own rendering functions like text
or table
use the start_new_page
method whenever the current page is full. As start_new_page
accepts an options hash for modifying the page layout of the new page it obvious that this is the place we have to hook in to. Here’s the code that lets us do that:
module Prawn
class Document
alias_method :orig_start_new_page, :start_new_page unless method_defined? :orig_start_new_page
def run_before_new_page(&block)
@run_before_new_page_blocks ||= Array.new
@run_before_new_page_blocks << Proc.new(&block)
end
def run_after_new_page(&block)
@run_after_new_page_blocks ||= Array.new
@run_after_new_page_blocks << Proc.new(&block)
end
def start_new_page(options = {})
run_blocks @run_before_new_page_blocks, options
self.orig_start_new_page options
run_blocks @run_after_new_page_blocks
end
protected
def run_blocks(blocks, *args)
return if !blocks || blocks.empty?
blocks.each { |block| block.arity == 0 ? self.instance_eval(&block) : block.call(self, *args) }
end
end
end
Here's a short explanation:
- In line 3 we rename Prawn's original
start_new_page
toorig_start_new_page
because we want to use it later on in our own version ofstart_new_page
. - Two new methods
run_before_new_page
andrun_after_new_page
provide the hooks to the users of thePrawn::Document
class. The first hook is run before the originalstart_new_page
method is called while the second hook is run afterwards. The before hook can be used to modify theoptions
hash passed tostart_new_method
(this is were the page's bounding box can be changed) and the after hook is used for displaying pictures.
But how is this used for actual content? That's easy:
Prawn::Document.generate "start_new_page_demo.pdf", :page_size => "A4" do
run_before_new_page do |pdf, options|
options[:top_margin] = cm2pt(4.0)
end
run_after_new_page do
image "background_page2_200dpi.png",
:at => [-bounds.absolute_left, Prawn::Document::SIZES["A4"][1] - bounds.absolute_bottom],
:fit => Prawn::Document::SIZES["A4"]
end
# This image only appears on the first page.
image "background_page1_200dpi.png",
:at => [-bounds.absolute_left, Prawn::Document::SIZES["A4"][1] - bounds.absolute_bottom],
:fit => Prawn::Document::SIZES["A4"]
# Draw enough text to ensure that a second page is created.
text "Hello, page 1!\n\n"
text "lorem ipsum" * 400
text "\nAnd now we're on another page."
end
You'll notice several things:
- The blocks passed to
run_before_new_page
andrun_after_new_page
can be used with or without local parameters. If you want to modify theoptions
hash then you have to use local variables forrun_before_new_page
, but you usually don't need them forrun_after_new_page
. The latter also only takes a single argument, thePrawn::Document
instance usually named "pdf". - Both block types are not called for the very first page. The reason is that
Prawn::Document
'sinitialize
method callsstart_new_page
before the hooks are registered themselves. However, this is fortunate for us because it makes switching background images easier. - I'm letting Prawn scale the images to the page size. This is done because the images themselves have a very high resolution so that zooming into the PDF will not make them look bad even at low zoom levels.
If you're curious what the result looks like then download the resulting PDF. It was generated with a more elaborate version of the code pasted above which in turn can be downloaded here as well. Please note that the images have been smudged on purpose for this demonstration -- the originals look just fine.
A final note if you want to use this in a Rails project: simply put the extension to the Prawn::Document
class into a file in the config/initializers directory, e.g. config/initializers/prawn_extensions.rb. Don't forget to restart the server afterwards or it won't pick up the new file.
Thank you so much for this! You saved me hours of sloppy hacks.