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.