# encoding: utf-8
module Prawn
class Document
# Builds and renders a Document::Table object from raw data.
# For details on the options that can be passed, see
# Document::Table.new
#
# data = [["Gregory","Brown"],["James","Healy"],["Jia","Wu"]]
#
# Prawn::Document.generate("table.pdf") do
#
# # Default table, without headers
# table(data)
#
# # Default table with headers
# table data, :headers => ["First Name", "Last Name"]
#
# # Very close to PDF::Writer's default SimpleTable output
# table data, :headers => ["First Name", "Last Name"],
# :font_size => 10,
# :vertical_padding => 2,
# :horizontal_padding => 5,
# :position => :center,
# :row_colors => :pdf_writer,
#
# # Grid border style with explicit column widths.
# table data, :border_style => :grid,
# :widths => { 0 => 100, 1 => 150 }
#
# end
#
def table(data,options={})
Prawn::Document::Table.new(data,self,options).draw
end
# This class implements simple PDF table generation.
#
# Prawn tables have the following features:
#
# * Can be generated with or without headers
# * Can tweak horizontal and vertical padding of text
# * Minimal styling support (borders / row background colors)
# * Can be positioned by bounding boxes (left/center aligned) or an
# absolute x position
# * Automated page-breaking as needed
# * Column widths can be calculated automatically or defined explictly on a
# column by column basis
#
# The current implementation is a bit barebones, but covers most of the
# basic needs for PDF table generation. If you have feature requests,
# please share them at: http://groups.google.com/group/prawn-ruby
#
# Tables will be revisited before the end of the Ruby Mendicant project and
# the most commonly needed functionality will likely be added.
#
class Table
attr_reader :col_widths # :nodoc:
# Creates a new Document::Table object. This is generally called
# indirectly through Document#table but can also be used explictly.
#
# The data argument is a two dimensional array of strings,
# organized by row, e.g. [["r1-col1","r1-col2"],["r2-col2","r2-col2"]].
# As with all Prawn text drawing operations, strings must be UTF-8 encoded.
#
# The following options are available for customizing your tables, with
# defaults shown in [] at the end of each description.
#
# :font_size:: The font size for the text cells . [12]
# :horizontal_padding:: The horizontal cell padding in PDF points [5]
# :vertical_padding:: The vertical cell padding in PDF points [5]
# :padding:: Horizontal and vertical cell padding (overrides both)
# :border:: With of border lines in PDF points [1]
# :border_style:: If set to :grid, fills in all borders. Otherwise, borders are drawn on columns only, not rows
# :position:: One of :left, :center or n, where n is an x-offset from the left edge of the current bounding box
# :widths: A hash of indices and widths in PDF points. E.g. { 0 => 50, 1 => 100 }
# :row_colors:: An array of row background colors which are used cyclicly.
# :align:: Alignment of text in columns [:left]
#
# Row colors are specified as html encoded values, e.g.
# ["ffffff","aaaaaa","ccaaff"]. You can also specify
# :row_colors => :pdf_writer if you wish to use the default color
# scheme from the PDF::Writer library.
#
# See Document#table for typical usage, as directly using this class is
# not recommended unless you know why you want to do it.
#
def initialize(data, document,options={})
@data = data
@document = document
@font_size = options[:font_size] || 12
@border_style = options[:border_style]
@border = options[:border] || 1
@position = options[:position] || :left
@headers = options[:headers]
@row_colors = options[:row_colors]
@align = options[:align]
@horizontal_padding = options[:horizontal_padding] || 5
@vertical_padding = options[:vertical_padding] || 5
if options[:padding]
@horizontal_padding = @vertical_padding = options[:padding]
end
@row_colors = ["ffffff","cccccc"] if @row_colors == :pdf_writer
@original_row_colors = @row_colors.dup if @row_colors
calculate_column_widths(options[:widths])
end
# Width of the table in PDF points
#
def width
@col_widths.inject(0) { |s,r| s + r }
end
# Draws the table onto the PDF document
#
def draw
case(@position)
when :center
x = (@document.bounds.width - width) / 2.0
y = @document.y - @document.bounds.absolute_bottom
@document.bounding_box [x, y], :width => width do
generate_table
end
when Numeric
x = @position
y = @document.y - @document.bounds.absolute_bottom
@document.bounding_box [x,y], :width => width do
generate_table
end
else
generate_table
end
end
private
def calculate_column_widths(manual_widths=nil)
@col_widths = [0] * @data[0].length
renderable_data.each do |row|
row.each_with_index do |cell,i|
length = cell.to_s.lines.map { |e|
@document.font_metrics.string_width(e,@font_size) }.max.to_f +
2*@horizontal_padding
@col_widths[i] = length if length > @col_widths[i]
end
end
# TODO: Could optimize here
manual_widths.each { |k,v| @col_widths[k] = v } if manual_widths
end
def renderable_data
if @headers
[@headers] + @data
else
@data
end
end
def generate_table
page_contents = []
y_pos = @document.y
@document.font_size(@font_size) do
renderable_data.each_with_index do |row,index|
c = Prawn::Graphics::CellBlock.new(@document)
row.each_with_index do |e,i|
case(e)
when Prawn::Graphics::Cell
e.document = @document
e.width = @col_widths[i]
e.horizontal_padding = @horizontal_padding
e.vertical_padding = @vertical_padding
e.border = @border
e.border_style = :sides
e.align = @align
c << e
else
c << Prawn::Graphics::Cell.new(
:document => @document,
:text => e.to_s,
:width => @col_widths[i],
:horizontal_padding => @horizontal_padding,
:vertical_padding => @vertical_padding,
:border => @border,
:border_style => :sides,
:align => @align )
end
end
if c.height > y_pos - @document.margin_box.absolute_bottom
draw_page(page_contents)
@document.start_new_page
if @headers
page_contents = [page_contents[0]]
y_pos = @document.y - page_contents[0].height
else
page_contents = []
y_pos = @document.y
end
end
page_contents << c
y_pos -= c.height
if index == renderable_data.length - 1
draw_page(page_contents)
end
end
@document.y -= @vertical_padding
end
end
def draw_page(contents)
return if contents.empty?
if @border_style == :grid || contents.length == 1
contents.each { |e| e.border_style = :all }
else
contents.first.border_style = @headers ? :all : :no_bottom
contents.last.border_style = :no_top
end
contents.each do |x|
x.background_color = next_row_color if @row_colors
x.draw
end
reset_row_colors
end
def next_row_color
@row_colors.unshift(@row_colors.pop).last
end
def reset_row_colors
@row_colors = @original_row_colors.dup if @row_colors
end
end
end
end