Object
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.
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.
: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 or n, where n 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 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 122 122: def initialize(data, document, options={}) 123: unless data.all? { |e| Array === e } 124: raise Prawn::Errors::InvalidTableData, 125: "data must be a two dimensional array of Prawn::Cells or strings" 126: end 127: 128: @data = data 129: @document = document 130: 131: Prawn.verify_options [:font_size,:border_style, :border_width, 132: :position, :headers, :row_colors, :align, :align_headers, 133: :header_text_color, :border_color, :horizontal_padding, 134: :vertical_padding, :padding, :column_widths, :width, :header_color ], 135: options 136: 137: configuration.update(options) 138: 139: if padding = options[:padding] 140: C(:horizontal_padding => padding, :vertical_padding => padding) 141: end 142: 143: if options[:row_colors] == :pdf_writer 144: C(:row_colors => ["ffffff","cccccc"]) 145: end 146: 147: if options[:row_colors] 148: C(:original_row_colors => C(:row_colors)) 149: end 150: 151: calculate_column_widths(options[:column_widths], options[:width]) 152: end
Draws the table onto the PDF document
# File lib/prawn/table.rb, line 164 164: def draw 165: @parent_bounds = @document.bounds 166: case C(:position) 167: when :center 168: x = (@document.bounds.width - width) / 2.0 169: dy = @document.bounds.absolute_top - @document.y 170: final_pos = nil 171: 172: @document.bounding_box [x, @parent_bounds.top], :width => width do 173: @document.move_down(dy) 174: generate_table 175: final_pos = @document.y 176: end 177: 178: @document.y = final_pos 179: when Numeric 180: x, y = C(:position), @document.cursor 181: final_pos = nil 182: 183: @document.bounding_box([x,y], :width => width) do 184: generate_table 185: final_pos = @document.y 186: end 187: 188: @document.y = final_pos 189: else 190: generate_table 191: end 192: end
# File lib/prawn/table.rb, line 204 204: def calculate_column_widths(manual_widths=nil, width=nil) 205: @column_widths = [0] * @data[0].inject(0){ |acc, e| 206: acc += (e.is_a?(Hash) && e.has_key?(:colspan)) ? e[:colspan] : 1 } 207: 208: renderable_data.each do |row| 209: colspan = 0 210: row.each_with_index do |cell,i| 211: cell_text = cell.is_a?(Hash) ? cell[:text] : cell.to_s 212: length = cell_text.lines.map { |e| 213: @document.width_of(e, :size => C(:font_size)) }.max.to_f + 214: 2*C(:horizontal_padding) 215: if length > @column_widths[i+colspan] 216: @column_widths[i+colspan] = length.ceil 217: end 218: 219: if cell.is_a?(Hash) && cell[:colspan] 220: colspan += cell[:colspan] - 1 221: end 222: end 223: end 224: 225: fit_within_bounds(manual_widths, width) 226: end
# File lib/prawn/table.rb, line 196 196: def default_configuration 197: { :font_size => 12, 198: :border_width => 1, 199: :position => :left, 200: :horizontal_padding => 5, 201: :vertical_padding => 5 } 202: end
# File lib/prawn/table.rb, line 361 361: def draw_page(contents) 362: return if contents.empty? 363: 364: if C(:border_style) == :underline_header 365: contents.each { |e| e.border_style = :none } 366: contents.first.border_style = :bottom_only if C(:headers) 367: elsif C(:border_style) == :grid || contents.length == 1 368: contents.each { |e| e.border_style = :all } 369: else 370: contents.first.border_style = C(:headers) ? :all : :no_bottom 371: contents.last.border_style = :no_top 372: end 373: 374: if C(:headers) 375: contents.first.cells.each_with_index do |e,i| 376: if C(:align_headers) 377: case C(:align_headers) 378: when Hash 379: align = C(:align_headers)[i] 380: else 381: align = C(:align_headers) 382: end 383: end 384: e.align = align if align 385: e.text_color = C(:header_text_color) if C(:header_text_color) 386: e.background_color = C(:header_color) if C(:header_color) 387: end 388: end 389: 390: contents.each do |x| 391: unless x.background_color 392: x.background_color = next_row_color if C(:row_colors) 393: end 394: x.border_color = C(:border_color) if C(:border_color) 395: 396: x.draw 397: end 398: 399: reset_row_colors 400: end
# File lib/prawn/table.rb, line 228 228: def fit_within_bounds(manual_widths, width) 229: manual_width = 0 230: manual_widths.each { |k,v| 231: @column_widths[k] = v; manual_width += v } if manual_widths 232: 233: #Ensures that the maximum width of the document is not exceeded 234: #Takes into consideration the manual widths specified (With full manual 235: # widths specified, the width can exceed the document width as manual 236: # widths are taken as gospel) 237: max_width = width || @document.margin_box.width 238: calculated_width = @column_widths.inject {|sum,e| sum += e } 239: 240: if calculated_width > max_width 241: shrink_by = (max_width - manual_width).to_f / 242: (calculated_width - manual_width) 243: @column_widths.each_with_index { |c,i| 244: @column_widths[i] = c * shrink_by if manual_widths.nil? || 245: manual_widths[i].nil? 246: } 247: elsif width && calculated_width < width 248: grow_by = (width - manual_width).to_f / 249: (calculated_width - manual_width) 250: @column_widths.each_with_index { |c,i| 251: @column_widths[i] = c * grow_by if manual_widths.nil? || 252: manual_widths[i].nil? 253: } 254: end 255: end
# File lib/prawn/table.rb, line 262 262: def generate_table 263: page_contents = [] 264: y_pos = @document.y 265: 266: @document.font_size C(:font_size) do 267: renderable_data.each_with_index do |row,index| 268: c = Prawn::Table::CellBlock.new(@document) 269: 270: if C(:row_colors).is_a?(Hash) 271: real_index = index 272: real_index -= 1 if C(:headers) 273: color = C(:row_colors)[real_index] 274: c.background_color = color if color 275: end 276: 277: col_index = 0 278: row.each do |e| 279: case C(:align) 280: when Hash 281: align = C(:align)[col_index] 282: else 283: align = C(:align) 284: end 285: 286: 287: align ||= e.to_s =~ NUMBER_PATTERN ? :right : :left 288: 289: case e 290: when Prawn::Table::Cell 291: e.document = @document 292: e.width = @column_widths[col_index] 293: e.horizontal_padding = C(:horizontal_padding) 294: e.vertical_padding = C(:vertical_padding) 295: e.border_width = C(:border_width) 296: e.border_style = :sides 297: e.align = align 298: c << e 299: else 300: text = e.is_a?(Hash) ? e[:text] : e.to_s 301: width = if e.is_a?(Hash) && e.has_key?(:colspan) 302: @column_widths.slice(col_index, e[:colspan]).inject { 303: |sum, width| sum + width } 304: else 305: @column_widths[col_index] 306: end 307: 308: cell_options = { 309: :document => @document, 310: :text => text, 311: :width => width, 312: :horizontal_padding => C(:horizontal_padding), 313: :vertical_padding => C(:vertical_padding), 314: :border_width => C(:border_width), 315: :border_style => :sides, 316: :align => align 317: } 318: 319: if e.is_a?(Hash) 320: opts = e.dup 321: opts.delete(:colspan) 322: cell_options.update(opts) 323: end 324: 325: c << Prawn::Table::Cell.new(cell_options) 326: end 327: 328: col_index += (e.is_a?(Hash) && e.has_key?(:colspan)) ? e[:colspan] : 1 329: end 330: 331: bbox = @parent_bounds.stretchy? ? @document.margin_box : @parent_bounds 332: if c.height > y_pos - bbox.absolute_bottom 333: if C(:headers) && page_contents.length == 1 334: @parent_bounds.move_past_bottom 335: y_pos = @document.y 336: else 337: draw_page(page_contents) 338: @parent_bounds.move_past_bottom 339: if C(:headers) && page_contents.any? 340: page_contents = [page_contents[0]] 341: y_pos = @document.y - page_contents[0].height 342: else 343: page_contents = [] 344: y_pos = @document.y 345: end 346: end 347: end 348: 349: page_contents << c 350: 351: y_pos -= c.height 352: 353: if index == renderable_data.length - 1 354: draw_page(page_contents) 355: end 356: 357: end 358: end 359: end
# File lib/prawn/table.rb, line 403 403: def next_row_color 404: return if C(:row_colors).is_a?(Hash) 405: 406: color = C(:row_colors).shift 407: C(:row_colors).push(color) 408: color 409: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.