prawn-table-continued 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -0
  3. data/GPLv2 +340 -0
  4. data/GPLv3 +674 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +56 -0
  7. data/lib/prawn/table/cell/image.rb +69 -0
  8. data/lib/prawn/table/cell/in_table.rb +33 -0
  9. data/lib/prawn/table/cell/span_dummy.rb +93 -0
  10. data/lib/prawn/table/cell/subtable.rb +66 -0
  11. data/lib/prawn/table/cell/text.rb +155 -0
  12. data/lib/prawn/table/cell.rb +787 -0
  13. data/lib/prawn/table/cells.rb +261 -0
  14. data/lib/prawn/table/column_width_calculator.rb +182 -0
  15. data/lib/prawn/table/version.rb +5 -0
  16. data/lib/prawn/table.rb +711 -0
  17. data/manual/contents.rb +13 -0
  18. data/manual/example_helper.rb +8 -0
  19. data/manual/images/prawn.png +0 -0
  20. data/manual/images/stef.jpg +0 -0
  21. data/manual/table/basic_block.rb +53 -0
  22. data/manual/table/before_rendering_page.rb +26 -0
  23. data/manual/table/cell_border_lines.rb +24 -0
  24. data/manual/table/cell_borders_and_bg.rb +31 -0
  25. data/manual/table/cell_dimensions.rb +36 -0
  26. data/manual/table/cell_text.rb +38 -0
  27. data/manual/table/column_widths.rb +30 -0
  28. data/manual/table/content_and_subtables.rb +39 -0
  29. data/manual/table/creation.rb +27 -0
  30. data/manual/table/filtering.rb +36 -0
  31. data/manual/table/flow_and_header.rb +17 -0
  32. data/manual/table/image_cells.rb +33 -0
  33. data/manual/table/position.rb +29 -0
  34. data/manual/table/row_colors.rb +20 -0
  35. data/manual/table/span.rb +30 -0
  36. data/manual/table/style.rb +33 -0
  37. data/manual/table/table.rb +52 -0
  38. data/manual/table/width.rb +27 -0
  39. data/prawn-table.gemspec +36 -0
  40. data/spec/cell_spec.rb +652 -0
  41. data/spec/extensions/encoding_helpers.rb +11 -0
  42. data/spec/extensions/file_fixture_helper.rb +15 -0
  43. data/spec/fixtures/files/prawn.png +0 -0
  44. data/spec/spec_helper.rb +50 -0
  45. data/spec/table/span_dummy_spec.rb +26 -0
  46. data/spec/table_spec.rb +1626 -0
  47. metadata +234 -0
@@ -0,0 +1,711 @@
1
+ # encoding: utf-8
2
+ #
3
+ # table.rb: Table drawing functionality.
4
+ #
5
+ # Copyright December 2009, Brad Ediger. All rights reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+
10
+ require 'prawn'
11
+ require_relative 'table/column_width_calculator'
12
+ require_relative 'table/cell'
13
+ require_relative 'table/cells'
14
+ require_relative 'table/cell/in_table'
15
+ require_relative 'table/cell/text'
16
+ require_relative 'table/cell/subtable'
17
+ require_relative 'table/cell/image'
18
+ require_relative 'table/cell/span_dummy'
19
+
20
+ module Prawn
21
+ module Errors
22
+ # This error is raised when table data is malformed
23
+ #
24
+ InvalidTableData = Class.new(StandardError)
25
+
26
+ # This error is raised when an empty or nil table is rendered
27
+ #
28
+ EmptyTable = Class.new(StandardError)
29
+
30
+ # Raised when unrecognized content is provided for a table cell.
31
+ #
32
+ UnrecognizedTableContent = Class.new(StandardError) unless defined?(::Prawn::Errors::UnrecognizedTableContent)
33
+ end
34
+
35
+ # Next-generation table drawing for Prawn.
36
+ #
37
+ # = Data
38
+ #
39
+ # Data, for a Prawn table, is a two-dimensional array of objects that can be
40
+ # converted to cells ("cellable" objects). Cellable objects can be:
41
+ #
42
+ # String::
43
+ # Produces a text cell. This is the most common usage.
44
+ # Prawn::Table::Cell::
45
+ # If you have already built a Cell or have a custom subclass of Cell you
46
+ # want to use in a table, you can pass through Cell objects.
47
+ # Prawn::Table::
48
+ # Creates a subtable (a table within a cell). You can use
49
+ # Prawn::Document#make_table to create a table for use as a subtable
50
+ # without immediately drawing it.
51
+ # Array::
52
+ # Creates a simple subtable. Create a Table object using make_table (see
53
+ # above) if you need more control over the subtable's styling.
54
+ #
55
+ # = Options
56
+ #
57
+ # Prawn/Layout provides many options to control style and layout of your
58
+ # table. These options are implemented with a uniform interface: the +:foo+
59
+ # option always sets the +foo=+ accessor. See the accessor and method
60
+ # documentation for full details on the options you can pass. Some
61
+ # highlights:
62
+ #
63
+ # +cell_style+::
64
+ # A hash of style options to style all cells. See the documentation on
65
+ # Prawn::Table::Cell for all cell style options.
66
+ # +header+::
67
+ # If set to +true+, the first row will be repeated on every page. If set
68
+ # to an Integer, the first +x+ rows will be repeated on every page. Row
69
+ # numbering (for styling and other row-specific options) always indexes
70
+ # based on your data array. Whether or not you have a header, row(n) always
71
+ # refers to the nth element (starting from 0) of the +data+ array.
72
+ # +column_widths+::
73
+ # Sets widths for individual columns. Manually setting widths can give
74
+ # better results than letting Prawn guess at them, as Prawn's algorithm
75
+ # for defaulting widths is currently pretty boneheaded. If you experience
76
+ # problems like weird column widths or CannotFit errors, try manually
77
+ # setting widths on more columns.
78
+ # +position+::
79
+ # Either :left (the default), :center, :right, or a number. Specifies the
80
+ # horizontal position of the table within its bounding box. If a number is
81
+ # provided, it specifies the distance in points from the left edge.
82
+ #
83
+ # = Initializer Block
84
+ #
85
+ # If a block is passed to methods that initialize a table
86
+ # (Prawn::Table.new, Prawn::Document#table, Prawn::Document#make_table), it
87
+ # will be called after cell setup but before layout. This is a very flexible
88
+ # way to specify styling and layout constraints. This code sets up a table
89
+ # where the second through the fourth rows (1-3, indexed from 0) are each one
90
+ # inch (72 pt) wide:
91
+ #
92
+ # pdf.table(data) do |table|
93
+ # table.rows(1..3).width = 72
94
+ # end
95
+ #
96
+ # As with Prawn::Document#initialize, if the block has no arguments, it will
97
+ # be evaluated in the context of the object itself. The above code could be
98
+ # rewritten as:
99
+ #
100
+ # pdf.table(data) do
101
+ # rows(1..3).width = 72
102
+ # end
103
+ #
104
+ class Table
105
+ module Interface
106
+ # @group Experimental API
107
+
108
+ # Set up and draw a table on this document. A block can be given, which will
109
+ # be run after cell setup but before layout and drawing.
110
+ #
111
+ # See the documentation on Prawn::Table for details on the arguments.
112
+ #
113
+ def table(data, options={}, &block)
114
+ t = Table.new(data, self, options, &block)
115
+ t.draw
116
+ t
117
+ end
118
+
119
+ # Set up, but do not draw, a table. Useful for creating subtables to be
120
+ # inserted into another Table. Call +draw+ on the resulting Table to ink it.
121
+ #
122
+ # See the documentation on Prawn::Table for details on the arguments.
123
+ #
124
+ def make_table(data, options={}, &block)
125
+ Table.new(data, self, options, &block)
126
+ end
127
+ end
128
+
129
+ # Set up a table on the given document. Arguments:
130
+ #
131
+ # +data+::
132
+ # A two-dimensional array of cell-like objects. See the "Data" section
133
+ # above for the types of objects that can be put in a table.
134
+ # +document+::
135
+ # The Prawn::Document instance on which to draw the table.
136
+ # +options+::
137
+ # A hash of attributes and values for the table. See the "Options" block
138
+ # above for details on available options.
139
+ #
140
+ def initialize(data, document, options={}, &block)
141
+ table_opts = options.dup
142
+ @pdf = document
143
+ @cells = make_cells(data, table_opts.delete(:cell_style) || {})
144
+ @header = false
145
+ table_opts.each do |k, v|
146
+ send("#{k}=", v) if respond_to?("#{k}=")
147
+ end
148
+
149
+ if block
150
+ block.arity < 1 ? instance_eval(&block) : block[self]
151
+ end
152
+
153
+ set_column_widths
154
+ set_row_heights
155
+ position_cells
156
+ end
157
+
158
+ # Number of rows in the table.
159
+ #
160
+ attr_reader :row_length
161
+
162
+ # Number of columns in the table.
163
+ #
164
+ attr_reader :column_length
165
+
166
+ # Manually set the width of the table.
167
+ #
168
+ attr_writer :width
169
+
170
+ # Position (:left, :right, :center, or a number indicating distance in
171
+ # points from the left edge) of the table within its parent bounds.
172
+ #
173
+ attr_writer :position
174
+
175
+ # Returns a Prawn::Table::Cells object representing all of the cells in
176
+ # this table.
177
+ #
178
+ attr_reader :cells
179
+
180
+ # Specify a callback to be called before each page of cells is rendered.
181
+ # The block is passed a Cells object containing all cells to be rendered on
182
+ # that page. You can change styling of the cells in this block, but keep in
183
+ # mind that the cells have already been positioned and sized.
184
+ #
185
+ def before_rendering_page(&block)
186
+ @before_rendering_page = block
187
+ end
188
+
189
+ # Returns the width of the table in PDF points.
190
+ #
191
+ def width
192
+ @width ||= [natural_width, @pdf.bounds.width].min
193
+ end
194
+
195
+ # Sets column widths for the table. The argument can be one of the following
196
+ # types:
197
+ #
198
+ # +Array+::
199
+ # <tt>[w0, w1, w2, ...]</tt> (specify a width for each column)
200
+ # +Hash+::
201
+ # <tt>{0 => w0, 1 => w1, ...}</tt> (keys are column names, values are
202
+ # widths)
203
+ # +Numeric+::
204
+ # +72+ (sets width for all columns)
205
+ #
206
+ def column_widths=(widths)
207
+ case widths
208
+ when Array
209
+ widths.each_with_index { |w, i| column(i).width = w }
210
+ when Hash
211
+ widths.each { |i, w| column(i).width = w }
212
+ when Numeric
213
+ cells.width = widths
214
+ else
215
+ raise ArgumentError, "cannot interpret column widths"
216
+ end
217
+ end
218
+
219
+ # Returns the height of the table in PDF points.
220
+ #
221
+ def height
222
+ cells.height
223
+ end
224
+
225
+ # If +true+, designates the first row as a header row to be repeated on
226
+ # every page. If an integer, designates the number of rows to be treated
227
+ # as a header Does not change row numbering -- row numbers always index
228
+ # into the data array provided, with no modification.
229
+ #
230
+ attr_writer :header
231
+
232
+ # Accepts an Array of alternating row colors to stripe the table.
233
+ #
234
+ attr_writer :row_colors
235
+
236
+ # Sets styles for all cells.
237
+ #
238
+ # pdf.table(data, :cell_style => { :borders => [:left, :right] })
239
+ #
240
+ def cell_style=(style_hash)
241
+ cells.style(style_hash)
242
+ end
243
+
244
+ # Allows generic stylable content. This is an alternate syntax that some
245
+ # prefer to the attribute-based syntax. This code using style:
246
+ #
247
+ # pdf.table(data) do
248
+ # style(row(0), :background_color => 'ff00ff')
249
+ # style(column(0)) { |c| c.border_width += 1 }
250
+ # end
251
+ #
252
+ # is equivalent to:
253
+ #
254
+ # pdf.table(data) do
255
+ # row(0).style :background_color => 'ff00ff'
256
+ # column(0).style { |c| c.border_width += 1 }
257
+ # end
258
+ #
259
+ def style(stylable, style_hash={}, &block)
260
+ stylable.style(style_hash, &block)
261
+ end
262
+
263
+ # Draws the table onto the document at the document's current y-position.
264
+ #
265
+ def draw
266
+ with_position do
267
+ # Reference bounds are the non-stretchy bounds used to decide when to
268
+ # flow to a new column / page.
269
+ ref_bounds = @pdf.reference_bounds
270
+
271
+ # Determine whether we're at the top of the current bounds (margin box or
272
+ # bounding box). If we're at the top, we couldn't gain any more room by
273
+ # breaking to the next page -- this means, in particular, that if the
274
+ # first row is taller than the margin box, we will only move to the next
275
+ # page if we're below the top. Some floating-point tolerance is added to
276
+ # the calculation.
277
+ #
278
+ # Note that we use the actual bounds, not the reference bounds. This is
279
+ # because even if we are in a stretchy bounding box, flowing to the next
280
+ # page will not buy us any space if we are at the top.
281
+ #
282
+ # initial_row_on_initial_page may return 0 (already at the top OR created
283
+ # a new page) or -1 (enough space)
284
+ started_new_page_at_row = initial_row_on_initial_page
285
+
286
+ # The cell y-positions are based on an infinitely long canvas. The offset
287
+ # keeps track of how much we have to add to the original, theoretical
288
+ # y-position to get to the actual position on the current page.
289
+ offset = @pdf.y
290
+
291
+ # Duplicate each cell of the header row into @header_row so it can be
292
+ # modified in before_rendering_page callbacks.
293
+ @header_row = header_rows if @header
294
+
295
+ # Track cells to be drawn on this page. They will all be drawn when this
296
+ # page is finished.
297
+ cells_this_page = []
298
+
299
+ @cells.each do |cell|
300
+ if start_new_page?(cell, offset, ref_bounds)
301
+ # draw cells on the current page and then start a new one
302
+ # this will also add a header to the new page if a header is set
303
+ # reset array of cells for the new page
304
+ cells_this_page, offset = ink_and_draw_cells_and_start_new_page(cells_this_page, cell)
305
+
306
+ # remember the current row for background coloring
307
+ started_new_page_at_row = cell.row
308
+ end
309
+
310
+ # Set background color, if any.
311
+ cell = set_background_color(cell, started_new_page_at_row)
312
+
313
+ # add the current cell to the cells array for the current page
314
+ cells_this_page << [cell, [cell.relative_x, cell.relative_y(offset)]]
315
+ end
316
+
317
+ # Draw the last page of cells
318
+ ink_and_draw_cells(cells_this_page)
319
+
320
+ @pdf.move_cursor_to(@cells.last.relative_y(offset) - @cells.last.height)
321
+ end
322
+ end
323
+
324
+ # Calculate and return the constrained column widths, taking into account
325
+ # each cell's min_width, max_width, and any user-specified constraints on
326
+ # the table or column size.
327
+ #
328
+ # Because the natural widths can be silly, this does not always work so well
329
+ # at guessing a good size for columns that have vastly different content. If
330
+ # you see weird problems like CannotFit errors or shockingly bad column
331
+ # sizes, you should specify more column widths manually.
332
+ #
333
+ def column_widths
334
+ @column_widths ||= begin
335
+ if width - cells.min_width < -Prawn::FLOAT_PRECISION
336
+ raise Errors::CannotFit,
337
+ "Table's width was set too small to contain its contents " +
338
+ "(min width #{cells.min_width}, requested #{width})"
339
+ end
340
+
341
+ if width - cells.max_width > Prawn::FLOAT_PRECISION
342
+ raise Errors::CannotFit,
343
+ "Table's width was set larger than its contents' maximum width " +
344
+ "(max width #{cells.max_width}, requested #{width})"
345
+ end
346
+
347
+ if width - natural_width < -Prawn::FLOAT_PRECISION
348
+ # Shrink the table to fit the requested width.
349
+ f = (width - cells.min_width).to_f / (natural_width - cells.min_width)
350
+
351
+ (0...column_length).map do |c|
352
+ min, nat = column(c).min_width, natural_column_widths[c]
353
+ (f * (nat - min)) + min
354
+ end
355
+ elsif width - natural_width > Prawn::FLOAT_PRECISION
356
+ # Expand the table to fit the requested width.
357
+ f = (width - cells.width).to_f / (cells.max_width - cells.width)
358
+
359
+ (0...column_length).map do |c|
360
+ nat, max = natural_column_widths[c], column(c).max_width
361
+ (f * (max - nat)) + nat
362
+ end
363
+ else
364
+ natural_column_widths
365
+ end
366
+ end
367
+ end
368
+
369
+ # Returns an array with the height of each row.
370
+ #
371
+ def row_heights
372
+ @natural_row_heights ||=
373
+ begin
374
+ heights_by_row = Hash.new(0)
375
+ cells.each do |cell|
376
+ next if cell.is_a?(Cell::SpanDummy)
377
+
378
+ # Split the height of row-spanned cells evenly by rows
379
+ height_per_row = cell.height.to_f / cell.rowspan
380
+ cell.rowspan.times do |i|
381
+ heights_by_row[cell.row + i] =
382
+ [heights_by_row[cell.row + i], height_per_row].max
383
+ end
384
+ end
385
+ heights_by_row.sort_by { |row, _| row }.map { |_, h| h }
386
+ end
387
+ end
388
+
389
+ protected
390
+
391
+ # sets the background color (if necessary) for the given cell
392
+ def set_background_color(cell, started_new_page_at_row)
393
+ if defined?(@row_colors) && @row_colors && (!@header || cell.row > 0)
394
+ # Ensure coloring restarts on every page (to make sure the header
395
+ # and first row of a page are not colored the same way).
396
+ rows = number_of_header_rows
397
+
398
+ index = cell.row - [started_new_page_at_row, rows].max
399
+
400
+ cell.background_color ||= @row_colors[index % @row_colors.length]
401
+ end
402
+ cell
403
+ end
404
+
405
+ # number of rows of the header
406
+ # @return [Integer] the number of rows of the header
407
+ def number_of_header_rows
408
+ # header may be set to any integer value -> number of rows
409
+ if @header.is_a? Integer
410
+ return @header
411
+ # header may be set to true -> first row is repeated
412
+ elsif @header
413
+ return 1
414
+ end
415
+ # defaults to 0 header rows
416
+ 0
417
+ end
418
+
419
+ # should we start a new page? (does the current row fail to fit on this page)
420
+ def start_new_page?(cell, offset, ref_bounds)
421
+ # we only need to run this test on the first cell in a row
422
+ # check if the rows height fails to fit on the page
423
+ # check if the row is not the first on that page (wouldn't make sense to go to next page in this case)
424
+ (cell.column == 0 && cell.row > 0 &&
425
+ !row(cell.row).fits_on_current_page?(offset, ref_bounds))
426
+ end
427
+
428
+ # ink cells and then draw them
429
+ def ink_and_draw_cells(cells_this_page, draw_cells = true)
430
+ ink_cells(cells_this_page)
431
+ Cell.draw_cells(cells_this_page) if draw_cells
432
+ end
433
+
434
+ # ink and draw cells, then start a new page
435
+ def ink_and_draw_cells_and_start_new_page(cells_this_page, cell)
436
+ # don't draw only a header
437
+ draw_cells = (@header_row.nil? || cells_this_page.size > @header_row.size)
438
+
439
+ ink_and_draw_cells(cells_this_page, draw_cells)
440
+
441
+ # start a new page or column
442
+ @pdf.bounds.move_past_bottom
443
+
444
+ offset = (@pdf.y - cell.y)
445
+
446
+ cells_next_page = []
447
+
448
+ header_height = add_header(cell.row, cells_next_page)
449
+
450
+ # account for header height in newly generated offset
451
+ offset -= header_height
452
+
453
+ # reset cells_this_page in calling function and return new offset
454
+ return cells_next_page, offset
455
+ end
456
+
457
+ # Ink all cells on the current page
458
+ def ink_cells(cells_this_page)
459
+ if defined?(@before_rendering_page) && @before_rendering_page
460
+ c = Cells.new(cells_this_page.map { |ci, _| ci })
461
+ @before_rendering_page.call(c)
462
+ end
463
+ end
464
+
465
+ # Determine whether we're at the top of the current bounds (margin box or
466
+ # bounding box). If we're at the top, we couldn't gain any more room by
467
+ # breaking to the next page -- this means, in particular, that if the
468
+ # first row is taller than the margin box, we will only move to the next
469
+ # page if we're below the top. Some floating-point tolerance is added to
470
+ # the calculation.
471
+ #
472
+ # Note that we use the actual bounds, not the reference bounds. This is
473
+ # because even if we are in a stretchy bounding box, flowing to the next
474
+ # page will not buy us any space if we are at the top.
475
+ # @return [Integer] 0 (already at the top OR created a new page) or -1 (enough space)
476
+ def initial_row_on_initial_page
477
+ # we're at the top of our bounds
478
+ return 0 if fits_on_page?(@pdf.bounds.height)
479
+
480
+ needed_height = row(0..number_of_header_rows).height
481
+
482
+ # have we got enough room to fit the first row (including header row(s))
483
+ use_reference_bounds = true
484
+ return -1 if fits_on_page?(needed_height, use_reference_bounds)
485
+
486
+ # If there isn't enough room left on the page to fit the first data row
487
+ # (including the header), start the table on the next page.
488
+ @pdf.bounds.move_past_bottom
489
+
490
+ # we are at the top of a new page
491
+ 0
492
+ end
493
+
494
+ # do we have enough room to fit a given height on to the current page?
495
+ def fits_on_page?(needed_height, use_reference_bounds = false)
496
+ if use_reference_bounds
497
+ bounds = @pdf.reference_bounds
498
+ else
499
+ bounds = @pdf.bounds
500
+ end
501
+ needed_height < @pdf.y - (bounds.absolute_bottom - Prawn::FLOAT_PRECISION)
502
+ end
503
+
504
+ # return the header rows
505
+ # @api private
506
+ def header_rows
507
+ header_rows = Cells.new
508
+ number_of_header_rows.times do |r|
509
+ row(r).each { |cell| header_rows[cell.row, cell.column] = cell.dup }
510
+ end
511
+ header_rows
512
+ end
513
+
514
+ # Converts the array of cellable objects given into instances of
515
+ # Prawn::Table::Cell, and sets up their in-table properties so that they
516
+ # know their own position in the table.
517
+ #
518
+ def make_cells(data, cell_style = {})
519
+ assert_proper_table_data(data)
520
+
521
+ cells = Cells.new
522
+
523
+ row_number = 0
524
+ data.each do |row_cells|
525
+ column_number = 0
526
+ row_cells.each do |cell_data|
527
+ # If we landed on a spanned cell (from a rowspan above), continue
528
+ # until we find an empty spot.
529
+ column_number += 1 until cells[row_number, column_number].nil?
530
+
531
+ # Build the cell and store it in the Cells collection.
532
+ cell = if cell_data.is_a?(Hash)
533
+ Cell.make(@pdf, cell_style.merge(cell_data))
534
+ else
535
+ Cell.make(@pdf, cell_data, cell_style)
536
+ end
537
+ cells[row_number, column_number] = cell
538
+
539
+ # Add dummy cells for the rest of the cells in the span group. This
540
+ # allows Prawn to keep track of the horizontal and vertical space
541
+ # occupied in each column and row spanned by this cell, while still
542
+ # leaving the master (top left) cell in the group responsible for
543
+ # drawing. Dummy cells do not put ink on the page.
544
+ cell.rowspan.times do |i|
545
+ cell.colspan.times do |j|
546
+ next if i == 0 && j == 0
547
+
548
+ # It is an error to specify spans that overlap; catch this here
549
+ if cells[row_number + i, column_number + j]
550
+ raise Prawn::Errors::InvalidTableSpan,
551
+ "Spans overlap at row #{row_number + i}, " +
552
+ "column #{column_number + j}."
553
+ end
554
+
555
+ dummy = Cell::SpanDummy.new(@pdf, cell)
556
+ cells[row_number + i, column_number + j] = dummy
557
+ cell.dummy_cells << dummy
558
+ end
559
+ end
560
+
561
+ column_number += cell.colspan
562
+ end
563
+
564
+ row_number += 1
565
+ end
566
+
567
+ # Calculate the number of rows and columns in the table, taking into
568
+ # account that some cells may span past the end of the physical cells we
569
+ # have.
570
+ @row_length = cells.map do |cell|
571
+ cell.row + cell.rowspan
572
+ end.max
573
+
574
+ @column_length = cells.map do |cell|
575
+ cell.column + cell.colspan
576
+ end.max
577
+
578
+ cells
579
+ end
580
+
581
+ def add_header(row_number, cells_this_page)
582
+ x_offset = @pdf.bounds.left_side - @pdf.bounds.absolute_left
583
+ header_height = 0
584
+ if row_number > 0 && @header
585
+ y_coord = @pdf.cursor
586
+ number_of_header_rows.times do |h|
587
+ additional_header_height = add_one_header_row(cells_this_page, x_offset, y_coord-header_height, row_number-1, h)
588
+ header_height += additional_header_height
589
+ end
590
+ end
591
+ header_height
592
+ end
593
+
594
+ # Add the header row(s) to the given array of cells at the given y-position.
595
+ # Number the row with the given +row+ index, so that the header appears (in
596
+ # any Cells built for this page) immediately prior to the first data row on
597
+ # this page.
598
+ #
599
+ # Return the height of the header.
600
+ #
601
+ def add_one_header_row(page_of_cells, x_offset, y, row, row_of_header=nil)
602
+ rows_to_operate_on = @header_row
603
+ rows_to_operate_on = @header_row.rows(row_of_header) if row_of_header
604
+ rows_to_operate_on.each do |cell|
605
+ cell.row = row
606
+ cell.dummy_cells.each {|c|
607
+ if cell.rowspan > 1
608
+ # be sure to account for cells that span multiple rows
609
+ # in this case you need multiple row numbers
610
+ c.row += row
611
+ else
612
+ c.row = row
613
+ end
614
+ }
615
+ page_of_cells << [cell, [cell.x + x_offset, y]]
616
+ end
617
+ rows_to_operate_on.height
618
+ end
619
+
620
+ # Raises an error if the data provided cannot be converted into a valid
621
+ # table.
622
+ #
623
+ def assert_proper_table_data(data)
624
+ if data.nil? || data.empty?
625
+ raise Prawn::Errors::EmptyTable,
626
+ "data must be a non-empty, non-nil, two dimensional array " +
627
+ "of cell-convertible objects"
628
+ end
629
+
630
+ unless data.all? { |e| Array === e }
631
+ raise Prawn::Errors::InvalidTableData,
632
+ "data must be a two dimensional array of cellable objects"
633
+ end
634
+ end
635
+
636
+ # Returns an array of each column's natural (unconstrained) width.
637
+ #
638
+ def natural_column_widths
639
+ @natural_column_widths ||= ColumnWidthCalculator.new(cells).natural_widths
640
+ end
641
+
642
+ # Returns the "natural" (unconstrained) width of the table. This may be
643
+ # extremely silly; for example, the unconstrained width of a paragraph of
644
+ # text is the width it would assume if it were not wrapped at all. Could be
645
+ # a mile long.
646
+ #
647
+ def natural_width
648
+ @natural_width ||= natural_column_widths.inject(0, &:+)
649
+ end
650
+
651
+ # Assigns the calculated column widths to each cell. This ensures that each
652
+ # cell in a column is the same width. After this method is called,
653
+ # subsequent calls to column_widths and width should return the finalized
654
+ # values that will be used to ink the table.
655
+ #
656
+ def set_column_widths
657
+ column_widths.each_with_index do |w, col_num|
658
+ column(col_num).width = w
659
+ end
660
+ end
661
+
662
+ # Assigns the row heights to each cell. This ensures that every cell in a
663
+ # row is the same height.
664
+ #
665
+ def set_row_heights
666
+ row_heights.each_with_index { |h, row_num| row(row_num).height = h }
667
+ end
668
+
669
+ # Set each cell's position based on the widths and heights of cells
670
+ # preceding it.
671
+ #
672
+ def position_cells
673
+ # Calculate x- and y-positions as running sums of widths / heights.
674
+ x_positions = column_widths.inject([0]) { |ary, x|
675
+ ary << (ary.last + x); ary }[0..-2]
676
+ x_positions.each_with_index { |x, i| column(i).x = x }
677
+
678
+ # y-positions assume an infinitely long canvas starting at zero -- this
679
+ # is corrected for in Table#draw, and page breaks are properly inserted.
680
+ y_positions = row_heights.inject([0]) { |ary, y|
681
+ ary << (ary.last - y); ary}[0..-2]
682
+ y_positions.each_with_index { |y, i| row(i).y = y }
683
+ end
684
+
685
+ # Sets up a bounding box to position the table according to the specified
686
+ # :position option, and yields.
687
+ #
688
+ def with_position
689
+ x = case defined?(@position) && @position || :left
690
+ when :left then return yield
691
+ when :center then (@pdf.bounds.width - width) / 2.0
692
+ when :right then @pdf.bounds.width - width
693
+ when Numeric then @position
694
+ else raise ArgumentError, "unknown position #{@position.inspect}"
695
+ end
696
+ dy = @pdf.bounds.absolute_top - @pdf.y
697
+ final_y = nil
698
+
699
+ @pdf.bounding_box([x, @pdf.bounds.top], :width => width) do
700
+ @pdf.move_down dy
701
+ yield
702
+ final_y = @pdf.y
703
+ end
704
+
705
+ @pdf.y = final_y
706
+ end
707
+
708
+ end
709
+ end
710
+
711
+ Prawn::Document.extensions << Prawn::Table::Interface