我使用从本次讨论中收集的代码和想法构建了一个报告生成器,其中包含可点击的目录。这是代码的相关部分,以防其他人需要这样做。
它能做什么:
- 包括Prawn::View以使用 Prawn 的方法,而不必在它们前面加上前缀
pdf
- 插入将显示目录的空白页
- 添加文档内容,使用 h1 和 h2 助手作为标题
- h1 和 h2 助手存储文档中标题的位置
- 倒带并生成实际的目录
- 在目录中缩进小节
- 右对齐 toc 条目和页码之间的点以保持视觉一致性
- 如果表格不适合一页,它会添加新页面并增加相关页码
- 添加带有分节和小节标题的 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