Prawn: Table of content with page numbers

you should read the chapter on Outline in this document http://prawn.majesticseacreature.com/manual.pdf, p.96. It explains with examples on how to create TOC.

UPDATE

destinations, page_references = {}, {}

page_count.downto(1).each {|num| page_references[num] = state.store.object_id_for_page(num)}

dests.data.to_hash.each_value do |values|
    values.each do |value|
        value_array             = value.to_s.split(":")
        dest_name               = value_array[0]
        dest_id                 = value_array[1].split[0]
        destinations[dest_name] = Integer(dest_id)
    end 
end 

state.store.each do |reference| 
    if !(dest_name = destinations.key(reference.identifier)).nil?
        puts "Destination - #{dest_name} is on Page #{page_references.key(Integer(reference.data[0].to_s.split[0]))}"
    end 
end   

I also needed to create a dynamic TOC. I put together a quick spike that needs some clean-up but does pretty much what I want. I didn't include click-able links but they could easily be added. The example also assumes the TOC is being placed on the 2nd page of the document.

The basic strategy I used was to store the TOC in a hash. Each time I add a new section to the document that I want to appear in the TOC I add it to the hash, i.e.

@toc[pdf.page_count] = "the toc text for this section"

Then prior to adding the page numbers to the document I iterate thru the hash:

number_of_toc_entries_per_page = 10
offset = (@toc.count.to_f / number_of_toc_entries_per_page).ceil
@toc.each_with_index do |(key, value), index| 
  pdf.start_new_page if index % number_of_toc_entries_per_page == 0
  pdf.text "#{value}.... page #{key + offset}", size: 38
end

Anyway, the full example is below, hope it helps.

require 'prawn'

class TocTest
  def self.create
    @toc = Hash.new
    @current_section_header_number = 0 # used to fake up section header's
    pdf = Prawn::Document.new

    add_title_page(pdf)
    21.times { add_a_content_page(pdf) }

    fill_in_toc(pdf)

    add_page_numbers(pdf)

    pdf.render_file './output/test.pdf'
  end

  def self.add_title_page(pdf)
    pdf.move_down 200
    pdf.text "This is my title page", size: 38, style: :bold, align: :center
  end

  def self.fill_in_toc(pdf)
    pdf.go_to_page(1)

    number_of_toc_entries_per_page = 10
    offset = (@toc.count.to_f / number_of_toc_entries_per_page).ceil
    @toc.each_with_index do |(key, value), index| 
      pdf.start_new_page if index % number_of_toc_entries_per_page == 0
      pdf.text "#{value}.... page #{key + offset}", size: 38
    end
  end

  def self.add_a_content_page(pdf)
    pdf.start_new_page
    toc_heading = grab_some_section_header_text

    @toc[pdf.page_count] = toc_heading

    pdf.text toc_heading, size: 38, style: :bold
    pdf.text "Here is the content for this section"
    # randomly span a section over 2 pages
    if [true, false].sample
      pdf.start_new_page
      pdf.text "The content for this section spans 2 pages"
    end
  end

  def self.add_page_numbers(pdf)
    page_number_string = 'page <page> of <total>'
    options = {
      at: [pdf.bounds.right - 175, 9], 
      width: 150, 
      align: :right, 
      size: 10,
      page_filter: lambda { |pg| pg > 1 }, 
      start_count_at: 2,
    }
    pdf.number_pages(page_number_string, options)
  end

  def self.grab_some_section_header_text
    "Section #{@current_section_header_number += 1}"
  end
end

I would suggest a much simpler solution.

  1. Use pdf.page_number to store the page number of all your sections in a hash as you populate the pages

  2. In the code, output the table of contents after populating the rest of your pages. Insert the TOC into the doc in the right spot by navigating in the PDF pdf.go_to_page(page_num).

For example:

render "pdf/frontpage", p: p
toc.merge!(p.page_number => "Section_Title")

p.start_new_page
toc.merge!(p.page_number => "Section_Title")
render "pdf/calendar"

p.start_new_page
toc.merge!(p.page_number => "Section_Title")
render "pdf/another_section"

p.go_to_page(1)
p.start_new_page
toc.merge!(p.page_number => "Table of Contents")
render "pdf/table_of_contents", table_of_contents: toc