class Prawn::Table
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 * Text alignment can be set for the whole table or by column
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: 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.
Public Class Methods
Creates a new Document::Table object. This is generally called indirectly through Prawn::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”],]. 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.
:headers
-
An array of table headers, either strings or Cells. [Empty]
:align_headers
-
Alignment of header text. Specify for entire header (
:left
) or by column ({ 0 => :right, 1 => :left}
). If omitted, the header alignment is the same as the column alignment. :header_text_color
-
Sets the text color of the headers
:header_color
-
Manually sets the header color
: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_width
-
With of border lines in PDF points [1]
:border_style
-
If set to :grid, fills in all borders. If set to :underline_header, underline header only. Otherwise, borders are drawn on columns only, not rows
:border_color
-
Sets the color of the borders.
:position
-
One of
:left
,:center
orn
, wheren
is an x-offset from the left edge of the current bounding box :width
-
A set width for the table, defaults to the sum of all column widths
:column_widths
-
A hash of indices and widths in PDF points. E.g.
{ 0 => 50, 1 => 100 }
:row_colors
-
Used to specify background colors for rows. See below for usage.
:align
-
Alignment of text in columns, for entire table (
:center
) or by column ({ 0 => :left, 1 => :center}
)
Row colors (:row_colors
) are specified as HTML hex color
values, e.g., “ccaaff”. They can take several forms:
-
An array of colors, used cyclically to “zebra stripe” the table:
['ffffff', 'cccccc', '336699']
. -
A hash taking 0-based row numbers to colors:
{ 0 => 'ffffff', 2 => 'cccccc'}
. -
The symbol
:pdf_writer
, for PDF::Writer's default color scheme.
See Prawn::Document#table for typical usage, as directly using this class is not recommended unless you know why you want to do it.
# File lib/prawn/table.rb, line 121 def initialize(data, document, options={}) unless data.all? { |e| Array === e } raise Prawn::Errors::InvalidTableData, "data must be a two dimensional array of Prawn::Cells or strings" end @data = data @document = document Prawn.verify_options [:font_size,:border_style, :border_width, :position, :headers, :row_colors, :align, :align_headers, :header_text_color, :border_color, :horizontal_padding, :vertical_padding, :padding, :column_widths, :width, :header_color ], options configuration.update(options) if padding = options[:padding] C(:horizontal_padding => padding, :vertical_padding => padding) end if options[:row_colors] == :pdf_writer C(:row_colors => ["ffffff","cccccc"]) end if options[:row_colors] C(:original_row_colors => C(:row_colors)) end calculate_column_widths(options[:column_widths], options[:width]) end
Public Instance Methods
Draws the table onto the PDF document
# File lib/prawn/table.rb, line 163 def draw @parent_bounds = @document.bounds case C(:position) when :center x = (@document.bounds.width - width) / 2.0 dy = @document.bounds.absolute_top - @document.y final_pos = nil @document.bounding_box [x, @parent_bounds.top], :width => width do @document.move_down(dy) generate_table final_pos = @document.y end @document.y = final_pos when Numeric x, y = C(:position), @document.cursor final_pos = nil @document.bounding_box([x,y], :width => width) do generate_table final_pos = @document.y end @document.y = final_pos else generate_table end end
Width of the table in PDF points
# File lib/prawn/table.rb, line 157 def width @column_widths.inject(0) { |s,r| s + r } end
Private Instance Methods
# File lib/prawn/table.rb, line 203 def calculate_column_widths(manual_widths=nil, width=nil) @column_widths = [0] * @data[0].inject(0){ |acc, e| acc += (e.is_a?(Hash) && e.has_key?(:colspan)) ? e[:colspan] : 1 } renderable_data.each do |row| colspan = 0 row.each_with_index do |cell,i| cell_text = cell.is_a?(Hash) ? cell[:text] : cell.to_s length = cell_text.lines.map { |e| @document.width_of(e, :size => C(:font_size)) }.max.to_f + 2*C(:horizontal_padding) if length > @column_widths[i+colspan] @column_widths[i+colspan] = length.ceil end if cell.is_a?(Hash) && cell[:colspan] colspan += cell[:colspan] - 1 end end end fit_within_bounds(manual_widths, width) end
# File lib/prawn/table.rb, line 195 def default_configuration { :font_size => 12, :border_width => 1, :position => :left, :horizontal_padding => 5, :vertical_padding => 5 } end
# File lib/prawn/table.rb, line 360 def draw_page(contents) return if contents.empty? if C(:border_style) == :underline_header contents.each { |e| e.border_style = :none } contents.first.border_style = :bottom_only if C(:headers) elsif C(:border_style) == :grid || contents.length == 1 contents.each { |e| e.border_style = :all } else contents.first.border_style = C(:headers) ? :all : :no_bottom contents.last.border_style = :no_top end if C(:headers) contents.first.cells.each_with_index do |e,i| if C(:align_headers) case C(:align_headers) when Hash align = C(:align_headers)[i] else align = C(:align_headers) end end e.align = align if align e.text_color = C(:header_text_color) if C(:header_text_color) e.background_color = C(:header_color) if C(:header_color) end end contents.each do |x| unless x.background_color x.background_color = next_row_color if C(:row_colors) end x.border_color = C(:border_color) if C(:border_color) x.draw end reset_row_colors end
# File lib/prawn/table.rb, line 227 def fit_within_bounds(manual_widths, width) manual_width = 0 manual_widths.each { |k,v| @column_widths[k] = v; manual_width += v } if manual_widths #Ensures that the maximum width of the document is not exceeded #Takes into consideration the manual widths specified (With full manual # widths specified, the width can exceed the document width as manual # widths are taken as gospel) max_width = width || @document.margin_box.width calculated_width = @column_widths.inject {|sum,e| sum += e } if calculated_width > max_width shrink_by = (max_width - manual_width).to_f / (calculated_width - manual_width) @column_widths.each_with_index { |c,i| @column_widths[i] = c * shrink_by if manual_widths.nil? || manual_widths[i].nil? } elsif width && calculated_width < width grow_by = (width - manual_width).to_f / (calculated_width - manual_width) @column_widths.each_with_index { |c,i| @column_widths[i] = c * grow_by if manual_widths.nil? || manual_widths[i].nil? } end end
# File lib/prawn/table.rb, line 261 def generate_table page_contents = [] y_pos = @document.y @document.font_size C(:font_size) do renderable_data.each_with_index do |row,index| c = Prawn::Table::CellBlock.new(@document) if C(:row_colors).is_a?(Hash) real_index = index real_index -= 1 if C(:headers) color = C(:row_colors)[real_index] c.background_color = color if color end col_index = 0 row.each do |e| case C(:align) when Hash align = C(:align)[col_index] else align = C(:align) end align ||= e.to_s =~ NUMBER_PATTERN ? :right : :left case e when Prawn::Table::Cell e.document = @document e.width = @column_widths[col_index] e.horizontal_padding = C(:horizontal_padding) e.vertical_padding = C(:vertical_padding) e.border_width = C(:border_width) e.border_style = :sides e.align = align c << e else text = e.is_a?(Hash) ? e[:text] : e.to_s width = if e.is_a?(Hash) && e.has_key?(:colspan) @column_widths.slice(col_index, e[:colspan]).inject { |sum, width| sum + width } else @column_widths[col_index] end cell_options = { :document => @document, :text => text, :width => width, :horizontal_padding => C(:horizontal_padding), :vertical_padding => C(:vertical_padding), :border_width => C(:border_width), :border_style => :sides, :align => align } if e.is_a?(Hash) opts = e.dup opts.delete(:colspan) cell_options.update(opts) end c << Prawn::Table::Cell.new(cell_options) end col_index += (e.is_a?(Hash) && e.has_key?(:colspan)) ? e[:colspan] : 1 end bbox = @parent_bounds.stretchy? ? @document.margin_box : @parent_bounds if c.height > y_pos - bbox.absolute_bottom if C(:headers) && page_contents.length == 1 @parent_bounds.move_past_bottom y_pos = @document.y else draw_page(page_contents) @parent_bounds.move_past_bottom if C(:headers) && page_contents.any? page_contents = [page_contents[0]] y_pos = @document.y - page_contents[0].height else page_contents = [] y_pos = @document.y end end end page_contents << c y_pos -= c.height if index == renderable_data.length - 1 draw_page(page_contents) end end end end
# File lib/prawn/table.rb, line 402 def next_row_color return if C(:row_colors).is_a?(Hash) color = C(:row_colors).shift C(:row_colors).push(color) color end
# File lib/prawn/table.rb, line 257 def renderable_data C(:headers) ? [C(:headers)] + @data : @data end
# File lib/prawn/table.rb, line 410 def reset_row_colors C(:row_colors => C(:original_row_colors).dup) if C(:row_colors) end