header and footer in Prawn PDF
Asked Answered
A

8

33

I have read through all relevant posts on Prawn but found no mentioning (even in Prawn's own documentation) of headers and footers.

However, I did see a demo on Prawnto's own website about headers and footers. I copied the entire source of that demo just to see if it works but an error of undefined method "header" is complained about. Am I to understand that Prawn took out header and footer recently in the gem or is there something else I need to do first to use header and footer?

The demo page: http://cracklabs.com/prawnto/code/prawn_demos/source/text/flowing_text_with_header_and_footer

the part of code of concern:

Prawn::Document.generate("flow_with_headers_and_footers.pdf")  do

  header margin_box.top_left do 
      text "Here's My Fancy Header", :size => 25, :align => :center   
  end   

  text "hello world!"
end

And by header, just in case, I mean the snippets of words that appear usually at a corner of every page of a document. Like your account number in your bills pages.

thanks!

Affirmation answered 22/4, 2010 at 22:41 Comment(0)
B
23

@GrantSayer thx for the example, but this will only let you show the current page number, not the total number of pages.

You can also use the number_pages function for the footer:

Prawn::Document.generate("page_with_numbering.pdf") do
  text "Hai"
  start_new_page
  text "bai"
  start_new_page
  text "-- Hai again"
  number_pages "<page> in a total of <total>", [bounds.right - 50, 0]
end

However, in my case I also need to format/style and right align the page numbers to match company style guides. I used go_to_page(k) to create my own header and footer functions, which add the header and footer to each page after all the pages are created. This gives me both styling options and the total number of pages:

Prawn::Document.generate("footer_example.pdf", :skip_page_creation => true) do
  10.times do
    start_new_page
    text "Some filler text for the page"
  end

  # footer
  page_count.times do |i|
    go_to_page(i+1)
    lazy_bounding_box([bounds.right-50, bounds.bottom + 25], :width => 50) {
      text "#{i+1} / #{page_count}"
    }.draw
  end
end
Billionaire answered 27/5, 2010 at 7:45 Comment(2)
I still wonder how you manage, that prawn doesn't write stuff over your footer boxes in this case. I am using the same way and it just doesn't work. The same happens in your example, when you increase the amount of text. Just remove the start_new_page and add a '* 100' to the text line inside of the loop. I usually move the whole doc into a bounding box and substract the sizes of the header/footer boxes to avoid problems like that.Cockatiel
That footer example really saved me hours of misery.Feodor
C
37

The sample you are refering to, from the prawnto plugin, is using an older version of prawn.

Since i also needed header and footer i looked a bit more into this. It seems that that version of prawn had header and footer methods, which were implemented using lazy bounding box. (found by checking the code on github)

In the new prawn version you can do the same thing using repeaters.

Here is the full sample rewritten using the new version:

require "#{File.dirname(__FILE__)}/../example_helper.rb"

Prawn::Document.generate("test.pdf")  do

   repeat :all do
    # header
    bounding_box [bounds.left, bounds.top], :width  => bounds.width do
        font "Helvetica"
        text "Here's My Fancy Header", :align => :center, :size => 25
        stroke_horizontal_rule
    end

    # footer
    bounding_box [bounds.left, bounds.bottom + 25], :width  => bounds.width do
        font "Helvetica"
        stroke_horizontal_rule
        move_down(5)
        text "And here's a sexy footer", :size => 16
    end
  end

  bounding_box([bounds.left, bounds.top - 50], :width  => bounds.width, :height => bounds.height - 100) do                 
   text "this is some flowing text " * 200    

   move_down(20)

   font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
   table [["ὕαλον ϕαγεῖν",    "baaar",             "1" ],
          ["This is","a sample",          "2" ],
          ["Table",  "dont\ncha\nknow?",  "3" ],
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules\nwith an iron fist", "x" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],   
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ],  
          [ "It",    "Rules",             "4" ],     
          [ "It",    "Rules",             "4" ]],     

     :font_size          => 24, 
     :horizontal_padding => 10,
     :vertical_padding   => 3,
     :border_width       => 2,
     :position           => :center,
     :headers            => ["Column A","Column B","#"]

  end
end

you can check the documentation page of repeat for other options which allow you to exactly specify where you want the repeaters.

Cloe answered 7/9, 2010 at 12:11 Comment(0)
B
23

@GrantSayer thx for the example, but this will only let you show the current page number, not the total number of pages.

You can also use the number_pages function for the footer:

Prawn::Document.generate("page_with_numbering.pdf") do
  text "Hai"
  start_new_page
  text "bai"
  start_new_page
  text "-- Hai again"
  number_pages "<page> in a total of <total>", [bounds.right - 50, 0]
end

However, in my case I also need to format/style and right align the page numbers to match company style guides. I used go_to_page(k) to create my own header and footer functions, which add the header and footer to each page after all the pages are created. This gives me both styling options and the total number of pages:

Prawn::Document.generate("footer_example.pdf", :skip_page_creation => true) do
  10.times do
    start_new_page
    text "Some filler text for the page"
  end

  # footer
  page_count.times do |i|
    go_to_page(i+1)
    lazy_bounding_box([bounds.right-50, bounds.bottom + 25], :width => 50) {
      text "#{i+1} / #{page_count}"
    }.draw
  end
end
Billionaire answered 27/5, 2010 at 7:45 Comment(2)
I still wonder how you manage, that prawn doesn't write stuff over your footer boxes in this case. I am using the same way and it just doesn't work. The same happens in your example, when you increase the amount of text. Just remove the start_new_page and add a '* 100' to the text line inside of the loop. I usually move the whole doc into a bounding box and substract the sizes of the header/footer boxes to avoid problems like that.Cockatiel
That footer example really saved me hours of misery.Feodor
E
18

It's little bit different with latest version of Prawn you must passe an hash

Prawn::Document.generate("page_with_numbering.pdf") do
  text "Hai"
  start_new_page
  text "bai"
  start_new_page
  text "-- Hai again"
  number_pages "<page> in a total of <total>", { :start_count_at => 0, :page_filter => :all, :at => [bounds.right - 50, 0], :align => :right, :size => 14 }
end
Edelweiss answered 15/7, 2011 at 11:37 Comment(2)
Always a pleasure to helpEdelweiss
Thanks: link to current docs also: rubydoc.info/github/sandal/prawn/…Debbradebby
D
10

If you want a footer that do not write stuff over your text, you have to create the bounding_box below the margin of the document using bounds.bottom.

require 'prawn'

file_name = 'hello.pdf'
random_table = (0..50).map{|i|[*('a'..'z')]} # generate a 2D array for example (~2 pages)

Prawn::Document::generate(file_name) do |pdf|
    pdf.table random_table
    pdf.page_count.times do |i|
        pdf.bounding_box([pdf.bounds.left, pdf.bounds.bottom], :width => pdf.bounds.width, :height => 30) {
        # for each page, count the page number and write it
        pdf.go_to_page i+1
             pdf.move_down 5 # move below the document margin
             pdf.text "#{i+1}/#{pdf.page_count}", :align => :center # write the page number and the total page count
        }
    end
end

It should look like that, you can see that the footer is outside the margin bottom :

example result

Hope it help someone

Darladarlan answered 11/6, 2014 at 12:7 Comment(0)
H
8

START EDIT

This works in prawn >= 0.12

END EDIT

Here is my solution using repeat, canvas and cell. Essentially I'm drawing my bounding boxes at the absolute top and bottom of every page. I'm using cell to have better styling control over it. Hope this is going to be helpful to someone. ( I used slightly annoying colors to better illustrate how you can control styling of header and footer)

 Prawn::Document.generate("headers_and_footers_with_background.pdf") do
  repeat :all do
    # header
    canvas do
      bounding_box([bounds.left, bounds.top], :width => bounds.width) do
        cell :content => 'Header',
             :background_color => 'EEEEEE',
             :width => bounds.width,
             :height => 50,
             :align => :center,
             :text_color => "001B76",
             :borders => [:bottom],
             :border_width => 2,
             :border_color => '00FF00',
             :padding => 12
      end
    end
    # footer
    canvas do
      bounding_box [bounds.left, bounds.bottom + 50], :width  => bounds.width do
        cell :content => 'Footer',
             :background_color => '333333',
             :width => bounds.width,
             :height => 50,
             :align => :center,
             :text_color => "FFFFFF",
             :borders => [:top],
             :border_width => 2,
             :border_color => 'FF0000',
             :padding => 12
      end
    end
  end
  # body
  bounding_box([bounds.left, bounds.top - 25], :width  => bounds.width, :height => bounds.height - 50) do
    100.times do
      text "Some filler text for the page"
    end
  end
end
Hexa answered 22/5, 2013 at 17:42 Comment(1)
Thank you so much, it's exactly what i was looking for.Iodometry
B
3

here's the problem when using the bounding_box for creating the custom footer contents... it is still rendering within the bounds of a margin.

I was looking for something that will write the contents in the margin area together with number_pages.(because a footer usually is set in the bottom margin area)... and it seems that there were none.

so instead, I used text_box and place the coordinates outside my main bounding box like so:

repeat :all do
      text_box "My custom footer", size: 7, align: :center, :at => [bounds.left, bounds.bottom], :height => 100, :width => bounds.width
end

take note that the repeat :all , will render this footer text to every page.

Bushelman answered 12/3, 2018 at 5:55 Comment(1)
Thanks, for bounding box in prawn 2.4 caused an an extra page to be created. Text box was nice.Hydnocarpate
S
1

The only way I've found to get a repeating item on a page is to use the Prawn::Document::LazyBoundingBox method. Basically this allows you to define a bounding box that is only rendered once you call it. So the usual pseudo-code steps are

  1. Define lazy bounding box element assign to somevar
  2. On each new page call the element.

The example from the source shows how

file = "lazy_bounding_boxes.pdf"
Prawn::Document.generate(file, :skip_page_creation => true) do
  point = [bounds.right-50, bounds.bottom + 25]
  page_counter = lazy_bounding_box(point, :width => 50) do
    text "Page: #{page_count}"
  end 

  10.times do
    start_new_page
    text "Some filler text for the page"  
    page_counter.draw
  end
end

This gives you a Page-count text output on each new page. What would be ideal is if this could be applied in the setup of a page template that is reused without the manual call to render the element. Combined with text flow this would give the traditional solution of headers and footers.

Suboxide answered 26/5, 2010 at 4:21 Comment(0)
N
0

I did this slightly differently but was heavily influenced by @konung. I have a PdfGenerator service module and my base class has a series of shared elements like the footer/header:

module PdfGenerator
  class Base
    HEADER_HEIGHT = 30
    FOOTER_HEIGHT = 30
    MARGIN_FACTOR = 1.2

    def initialize(_ = nil)
      @pdf = Prawn::Document.new
    end

    def apply_header(header = "Custom Builder Select")
      @pdf.page_count.times do |i|
        next if i == 0
        @pdf.go_to_page(i + 1)
        @pdf.bounding_box([@pdf.bounds.left, @pdf.bounds.top], width: @pdf.bounds.width, height: HEADER_HEIGHT) do
          @pdf.fill_color "44403c"
          @pdf.fill_rectangle [0, @pdf.bounds.top], @pdf.bounds.width, HEADER_HEIGHT
          @pdf.fill_color "FFFFFF"
          @pdf.move_down HEADER_HEIGHT / 2 - 6
          @pdf.text header, align: :center, size: 12
        end
      end
    end

    def apply_footer
      @pdf.page_count.times do |i|
        next if i == 0
        @pdf.go_to_page(i + 1)
        @pdf.bounding_box([@pdf.bounds.left, @pdf.bounds.bottom + FOOTER_HEIGHT], width: @pdf.bounds.width, height: FOOTER_HEIGHT) do
          @pdf.fill_color "44403c"
          @pdf.fill_rectangle [0, @pdf.bounds.top], @pdf.bounds.width, FOOTER_HEIGHT
          @pdf.fill_color "FFFFFF"
          @pdf.move_down FOOTER_HEIGHT / 2 - 5
          @pdf.text "#{i + 1} / #{@pdf.page_count}", align: :center, size: 10
        end
      end
    end

    def apply_body
      @pdf.bounding_box(
        [@pdf.bounds.left, @pdf.bounds.top - HEADER_HEIGHT * MARGIN_FACTOR],
        width: @pdf.bounds.width,
        height: @pdf.bounds.height - (HEADER_HEIGHT + FOOTER_HEIGHT) * MARGIN_FACTOR
      ) do
        yield if block_given?
      end
    end

    def render
      @pdf.render
    end
  end
end

Then, when I want to use this in a specific type of PDF generator I do this:

module PdfGenerator
  class YourPdfClass < Base
    def initialize(thing)
      super
      @thing = thing
    end

    def render
      cover_page # A method for your cover page

      apply_body do
        things_pages # A method where you build the content of this things pages
      end

      apply_header(@thing.name)
      apply_footer

      @pdf.render
    end
  end
end

The main difference here is that I'm storing the dimensions of my headers and footers so that I can consistently space those and then the body of my page is within a box that is defined by those sizes. If for some reason I want a taller header, I can change that constant and it will adjust my header, the point where the body starts, and the point where the body ends/footer starts.

The margin factor is to leave a little padding between my body and header/footer. You could also put your font size in a constant to calculate the vertical centering of your text if you wished.

Navar answered 6/2 at 19:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.