Prawn:包含页码的内容表

时间:2012-10-16 09:36:52

标签: ruby-on-rails ruby pdf-generation prawn

我需要用Prawn创建一个目录。我的代码中有add_dest个函数调用 内容表中的右侧链接:

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

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

这是有效的,我得到可点击的链接,将我转发到正确的页面。但是,我需要在内容表中包含页码。如何打印出来?

4 个答案:

答案 0 :(得分:3)

您应该阅读本文档http://prawn.majesticseacreature.com/manual.pdf中的大纲章节,第96页。它解释了如何创建TOC的示例。

更新

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   

答案 1 :(得分:3)

我还需要创建一个动态TOC。我把一个快速的尖峰放在一起,需要一些清理,但几乎可以做我想要的。我没有包含可点击链接,但可以很容易地添加它们。该示例还假定TOC被放置在文档的第2页上。

我使用的基本策略是将TOC存储在哈希中。每次我在文档中添加一个我希望出现在TOC中的新部分时,我都会将其添加到哈希中,即

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

然后,在将页码添加到文档之前,我通过哈希迭代:

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

无论如何,完整的例子如下,希望它有所帮助。

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

答案 2 :(得分:2)

我建议一个更简单的解决方案。

  1. 在填充页面时,使用pdf.page_number将所有部分的页码存储在哈希中

  2. 在代码中,填充其余页面后输出目录。通过导航PDF pdf.go_to_page(page_num)将TOC插入到正确位置的文档中。

  3. 例如:

    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
    

答案 3 :(得分:0)

我使用从本次讨论中收集的代码和想法构建了一个报表生成器,其中包含可单击的目录。如果其他人需要这样做,这是代码的相关部分。

它的作用:

  • 包括Prawn::View以使用Prawn的方法,而不必在它们前加上pdf
  • 插入一个空白页,其中将显示目录
  • 使用h1和h2辅助标题来添加文档内容
  • h1和h2助手在文档中存储标题的位置
  • 倒带并生成实际目录
  • 缩进目录中的小节
  • 将目录条目和页码之间的点右对齐以保持视觉一致性
  • 如果表格不适合一页,它将添加新页面并增加相关页码
  • 添加带有分节标题的PDF大纲,以获得加分。

享受!

PDF生成器

class ReportPdf
  include Prawn::View
  COLOR_GRAY = 'BBBBBB' # Color used for the dots in the table of contents

  def initialize(report)
    @toc = []
    @report = report
    generate_report
  end

  private

  def generate_report
    add_table_of_contents
    add_contents
    update_table_of_contents
    add_outline
  end

  def add_table_of_contents
    # Insert a blank page, which will be filled in later using update_table_of_contents
    start_new_page
  end

  def add_contents
    @report.sections.each do |section|
      h1(section.title, section.anchor)
      section.subsections.each do |subsection|
        h2(subsection.title, subsection.anchor)
        # subsection contents
      end
    end
  end

  def update_table_of_contents
    go_to_page(1) # Rewind to where the table needs to be displayed
    text 'Table of contents', styles_for(:toc_title)
    move_down 20

    added_pages = 0
    @toc.each do |entry|
      unless fits_on_current_page?(entry[:name])
        added_pages += 1
        start_new_page
      end
      entry[:page] += added_pages
      add_toc_line(entry)
      entry[:subsections].each do |subsection_entry|
        unless fits_on_current_page?(subsection_entry[:name])
          added_pages += 1
          start_new_page
        end
        subsection_entry[:page] += added_pages
        add_toc_line(subsection_entry, true)
      end
    end
  end

  def add_outline
    outline.section 'Table of contents', destination: 2
    @toc.each do |entry|
      outline.section entry[:name], destination: entry[:page] do
        entry[:subsections].each do |subsection|
          outline.page title: subsection[:name], destination: subsection[:page]
        end
      end
    end
  end

  def h1(name, anchor)
    add_anchor(anchor, name)
    text name, styles_for(:h1)
  end

  def h2(name, anchor)
    add_anchor(anchor, name, true)
    text name, styles_for(:h2)
  end

  def styles_for(element = :p)
    case element
    when :toc_title then { size: 24, align: :center }
    when :h1 then { size: 20, align: :left }
    when :h2 then { size: 16, align: :left }
    when :p  then { size: 12, align: :justify }
    end
  end

  def add_anchor(name, anchor, is_subsection = false)
    add_dest anchor, dest_xyz(bounds.absolute_left, y + 20)
    if is_subsection
      @toc.last[:subsections] << { anchor: anchor, name: name, page: page_count }
    else
      @toc << { anchor: anchor, name: name, page: page_count, subsections: [] }
    end
  end

  def add_toc_line(entry, is_subsection = false)
    anchor = entry[:anchor]
    name = entry[:name]
    name = "#{Prawn::Text::NBSP * 5}#{name}" if is_subsection
    page_number = entry[:page].to_s
    dots_info = dots_for(name + ' ' + page_number)

    float do
      text "<link anchor='#{anchor}'>#{name}</link>", inline_format: true
    end
    float do
      indent(dots_info[:dots_start], dots_info[:right_margin]) do
        text "<color rgb='#{COLOR_GRAY}'>#{dots_info[:dots]}</color>", inline_format: true, align: :right
      end
    end
    indent(dots_info[:dots_end]) do
      text "<link anchor='#{anchor}'>#{page_number}</link>", inline_format: true, align: :right
    end
  end

  def dots_for(text)
    dot_width  = text_width('.')
    dots_start = text_width(text)
    right_margin = text_width(' ') * 6

    space_for_dots = bounds.width - dots_start - right_margin
    dots = space_for_dots.negative? ? '' : '.' * (space_for_dots / dot_width)
    dots_end = space_for_dots - right_margin
    {
      dots: dots,
      dots_start: dots_start,
      dots_end: dots_end,
      right_margin: right_margin
    }
  end

  def fits_on_current_page?(str)
    remaining_height = bounds.top - bounds.absolute_top + y
    height_of(str) < remaining_height
  end

  def text_width(str, size = 12)
    font(current_font).compute_width_of(str, size: size)
  end

  def current_font
    @current_font ||= font.inspect.split('<')[1].split(':')[0].strip
  end
end

使用发电机

使用Rails,我使用以下代码从报告生成PDF:

# app/models/report.rb
class Report < ApplicationRecord
  # Additional methods
  def pdf
    @pdf ||= ReportPdf.new(self)
  end
end

# app/controllers/reports_controller.rb
class ReportsController < ApplicationController
  def show
    respond_to do |format|
      format.html
      format.pdf do
        doc = @report.pdf
        send_data doc.render, filename: doc.filename, disposition: :inline, type: Mime::Type.lookup_by_extension(:pdf)
      end
    end
end