Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prawn: Table of content with page numbers

I need to create a table of contents with Prawn. I have add_dest function calls in my code and the right links in the table of content:

add_dest('Komplett', dest_fit(page_count - 1))

and

text "* <link anchor='Komplett'> Vollstaendiges Mitgliederverzeichnis </link>", :inline_format = true

This works and I get clickable links which forward me to the right pages. However, I need to have page numbers in the table of content. How do I get it printed out?

like image 578
Dirk Beerbohm Avatar asked Oct 16 '12 09:10

Dirk Beerbohm


3 Answers

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
like image 173
Naysawn Avatar answered Oct 08 '22 01:10

Naysawn


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   
like image 3
saihgala Avatar answered Oct 08 '22 00:10

saihgala


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
like image 3
riebeekn Avatar answered Oct 08 '22 01:10

riebeekn