jerryvos-prawn-layout 0.2.0.3 → 0.2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,515 @@
1
+ # encoding: utf-8
2
+ #
3
+ # table.rb : Simple table drawing functionality
4
+ #
5
+ # Copyright June 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require "prawn/table/cell"
10
+
11
+ module Prawn
12
+ class Document
13
+
14
+ # Builds and renders a Document::Table object from raw data.
15
+ # For details on the options that can be passed, see
16
+ # Document::Table.new
17
+ #
18
+ # data = [["Gregory","Brown"],["James","Healy"],["Jia","Wu"]]
19
+ #
20
+ # Prawn::Document.generate("table.pdf") do
21
+ #
22
+ # # Default table, without headers
23
+ # table(data)
24
+ #
25
+ # # Default table with headers
26
+ # table data, :headers => ["First Name", "Last Name"]
27
+ #
28
+ # # Very close to PDF::Writer's default SimpleTable output
29
+ # table data, :headers => ["First Name", "Last Name"],
30
+ # :font_size => 10,
31
+ # :vertical_padding => 2,
32
+ # :horizontal_padding => 5,
33
+ # :position => :center,
34
+ # :row_colors => :pdf_writer,
35
+ #
36
+ # # Grid border style with explicit column widths.
37
+ # table data, :border_style => :grid,
38
+ # :column_widths => { 0 => 100, 1 => 150 }
39
+ #
40
+ # end
41
+ #
42
+ # Will raise <tt>Prawn::Errors::EmptyTable</tt> given
43
+ # a nil or empty <tt>data</tt> paramater.
44
+ #
45
+ def table(data, options={})
46
+ if data.nil? || data.empty?
47
+ raise Prawn::Errors::EmptyTable,
48
+ "data must be a non-empty, non-nil, two dimensional array of Prawn::Cells or strings"
49
+ end
50
+ Prawn::Table.new(data,self,options).draw
51
+ end
52
+ end
53
+
54
+
55
+ # This class implements simple PDF table generation.
56
+ #
57
+ # Prawn tables have the following features:
58
+ #
59
+ # * Can be generated with or without headers
60
+ # * Can tweak horizontal and vertical padding of text
61
+ # * Minimal styling support (borders / row background colors)
62
+ # * Can be positioned by bounding boxes (left/center aligned) or an
63
+ # absolute x position
64
+ # * Automated page-breaking as needed
65
+ # * Column widths can be calculated automatically or defined explictly on a
66
+ # column by column basis
67
+ # * Text alignment can be set for the whole table or by column
68
+ #
69
+ # The current implementation is a bit barebones, but covers most of the
70
+ # basic needs for PDF table generation. If you have feature requests,
71
+ # please share them at: http://groups.google.com/group/prawn-ruby
72
+ #
73
+ # Tables will be revisited before the end of the Ruby Mendicant project and
74
+ # the most commonly needed functionality will likely be added.
75
+ #
76
+ class Table
77
+
78
+ include Prawn::Configurable
79
+
80
+ attr_reader :column_widths # :nodoc:
81
+
82
+ NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+(?:[eE][+-]?\d+)?)?$/ #:nodoc:
83
+
84
+ # Creates a new Document::Table object. This is generally called
85
+ # indirectly through Document#table but can also be used explictly.
86
+ #
87
+ # The <tt>data</tt> argument is a two dimensional array of strings,
88
+ # organized by row, e.g. [["r1-col1","r1-col2"],["r2-col2","r2-col2"]].
89
+ # As with all Prawn text drawing operations, strings must be UTF-8 encoded.
90
+ #
91
+ # The following options are available for customizing your tables, with
92
+ # defaults shown in [] at the end of each description.
93
+ #
94
+ # <tt>:headers</tt>:: An array of table headers, either strings or Cells. [Empty]
95
+ # <tt>:align_headers</tt>:: Alignment of header text. Specify for entire header (<tt>:left</tt>) or by column (<tt>{ 0 => :right, 1 => :left}</tt>). If omitted, the header alignment is the same as the column alignment.
96
+ # <tt>:header_text_color</tt>:: Sets the text color of the headers
97
+ # <tt>:header_color</tt>:: Manually sets the header color
98
+ # <tt>:font_size</tt>:: The font size for the text cells . [12]
99
+ # <tt>:horizontal_padding</tt>:: The horizontal cell padding in PDF points [5]
100
+ # <tt>:vertical_padding</tt>:: The vertical cell padding in PDF points [5]
101
+ # <tt>:padding</tt>:: Horizontal and vertical cell padding (overrides both)
102
+ # <tt>:border_width</tt>:: With of border lines in PDF points [1]
103
+ # <tt>:border_style</tt>:: 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
104
+ # <tt>:border_color</tt>:: Sets the color of the borders.
105
+ # <tt>:position</tt>:: One of <tt>:left</tt>, <tt>:center</tt> or <tt>n</tt>, where <tt>n</tt> is an x-offset from the left edge of the current bounding box
106
+ # <tt>:width:</tt> A set width for the table, defaults to the sum of all column widths
107
+ # <tt>:max_width:</tt> The maximum width of the table, defaults to the width or the document's margin box width.
108
+ # <tt>:column_widths:</tt> A hash of indices and widths in PDF points. E.g. <tt>{ 0 => 50, 1 => 100 }</tt>
109
+ # <tt>:row_colors</tt>:: An array of row background colors which are used cyclicly.
110
+ # <tt>:align</tt>:: Alignment of text in columns, for entire table (<tt>:center</tt>) or by column (<tt>{ 0 => :left, 1 => :center}</tt>)
111
+ #
112
+ # Row colors are specified as html encoded values, e.g.
113
+ # ["ffffff","aaaaaa","ccaaff"]. You can also specify
114
+ # <tt>:row_colors => :pdf_writer</tt> if you wish to use the default color
115
+ # scheme from the PDF::Writer library.
116
+ #
117
+ # See Document#table for typical usage, as directly using this class is
118
+ # not recommended unless you know why you want to do it.
119
+ #
120
+ def initialize(data, document, options={})
121
+ unless data.all? { |e| Array === e }
122
+ raise Prawn::Errors::InvalidTableData,
123
+ "data must be a two dimensional array of Prawn::Cells or strings"
124
+ end
125
+
126
+ @data = data
127
+ @document = document
128
+
129
+ Prawn.verify_options [:font_size,:border_style, :border_width,
130
+ :position, :headers, :row_colors, :align, :align_headers,
131
+ :header_text_color, :border_color, :horizontal_padding,
132
+ :vertical_padding, :padding, :column_widths, :width, :max_width, :header_color ],
133
+ options
134
+
135
+ configuration.update(options)
136
+
137
+ if padding = options[:padding]
138
+ C(:horizontal_padding => padding, :vertical_padding => padding)
139
+ end
140
+
141
+ if options[:row_colors] == :pdf_writer
142
+ C(:row_colors => ["ffffff","cccccc"])
143
+ end
144
+
145
+ if options[:row_colors]
146
+ C(:original_row_colors => C(:row_colors))
147
+ end
148
+
149
+ # Once we have all configuration setted...
150
+ normalize_data
151
+ check_rows_lengths
152
+ calculate_column_widths(options[:column_widths], options[:width], options[:max_width])
153
+ end
154
+
155
+ attr_reader :column_widths #:nodoc:
156
+
157
+ # Width of the table in PDF points
158
+ #
159
+ def width
160
+ @column_widths.inject(0) { |s,r| s + r }
161
+ end
162
+
163
+ # Draws the table onto the PDF document
164
+ #
165
+ def draw
166
+ @parent_bounds = @document.bounds
167
+ case C(:position)
168
+ when :center
169
+ x = (@document.bounds.width - width) / 2.0
170
+ dy = @document.bounds.absolute_top - @document.y
171
+ @document.bounding_box [x, @parent_bounds.top], :width => width do
172
+ @document.move_down(dy)
173
+ generate_table
174
+ end
175
+ when Numeric
176
+ x, y = C(:position), @document.y - @document.bounds.absolute_bottom
177
+ @document.bounding_box([x,y], :width => width) { generate_table }
178
+ else
179
+ generate_table
180
+ end
181
+ end
182
+
183
+ private
184
+
185
+ def default_configuration
186
+ { :font_size => 12,
187
+ :border_width => 1,
188
+ :position => :left,
189
+ :horizontal_padding => 5,
190
+ :vertical_padding => 5 }
191
+ end
192
+
193
+ # Check that all rows are well formed with the same length.
194
+ #
195
+ # Will raise an <tt>Prawn::Errors::InvalidTableData</tt> exception
196
+ # in case that a bad formed row is found
197
+ def check_rows_lengths
198
+ tables_width = nil
199
+ actual_row = 0
200
+ old_index = -1
201
+ check_last_row = lambda {
202
+ tables_width ||= old_index # only setted the first time
203
+ if tables_width != nil && tables_width != old_index
204
+ raise Prawn::Errors::InvalidTableData
205
+ "The row #{actual_row} has a length of #{old_index + 1}, " +
206
+ "it should be of #{tables_width + 1} according to the previous rows"
207
+ end
208
+ }
209
+ each_cell_with_index do |cell, i, n_row|
210
+ if actual_row != n_row # is new row
211
+ check_last_row.call
212
+ actual_row = n_row
213
+ end
214
+ old_index = i + cell.colspan - 1
215
+ end
216
+ check_last_row.call
217
+ end
218
+
219
+ # An iterator method around renderable_data method.
220
+ #
221
+ # The issue using renderable_data is that in each iteration you don't know
222
+ # the real index for that cell, due to colspan & rowspan values of the
223
+ # previous cells.
224
+ #
225
+ # So this method yields every cell (Prawn::Table::Cell) with its column
226
+ # index.
227
+ #
228
+ # Example:
229
+ # +-----------+
230
+ # | A | B |
231
+ # +-------+---+
232
+ # | C | D | E |
233
+ # +---+---+---+
234
+ # The values in each iteration will be:
235
+ # * Cell A, 0, 0
236
+ # * Cell B, 2, 0
237
+ # * Cell C, 0, 1
238
+ # * Cell D, 1, 1
239
+ # * Cell E, 2, 1
240
+ #
241
+ def each_cell_with_index
242
+ rowspan_cells = {}
243
+ n_row = 0
244
+ renderable_data.each do |row|
245
+ index = 0
246
+ rowspan_cells.each_value { |v| v[:rowspan] -= 1 }
247
+ rowspan_cells.delete_if { |k, v| v[:rowspan] == 0 }
248
+ row.each do |cell|
249
+ while rowspan_cells[ index ] do
250
+ index += rowspan_cells[ index ][:colspan]
251
+ end
252
+
253
+ yield cell, index, n_row
254
+
255
+ if cell.rowspan > 1
256
+ rowspan_cells[ index ] = { :rowspan => cell.rowspan,
257
+ :colspan => cell.colspan }
258
+ end
259
+ index += cell.colspan
260
+ end # row.each
261
+ n_row += 1
262
+ end # renderable_data.each
263
+ end
264
+
265
+ def cells_width( cell )
266
+ width = 2 * C(:horizontal_padding) + cell.to_s.lines.map do |e|
267
+ @document.width_of(e, :size => C(:font_size))
268
+ end.max.to_f
269
+ width.ceil
270
+ end
271
+
272
+ def calculate_column_widths(manual_widths=nil, width=nil, max_width=nil)
273
+ @column_widths = [0] * @data[0].inject(0){ |total, e| total + e.colspan }
274
+
275
+ # Firstly, calculate column widths for cells without colspan attribute
276
+ colspan_cell_to_proccess = []
277
+ each_cell_with_index do |cell, index|
278
+ if cell.colspan <= 1
279
+ length = cells_width( cell )
280
+ @column_widths[ index ] = length if length > @column_widths[ index ]
281
+ else
282
+ colspan_cell_to_proccess << [ cell, index ]
283
+ end
284
+ end
285
+
286
+ # Secondly, calculate column width for cells with colspan attribute
287
+ # and update @column_widths properly
288
+ colspan_cell_to_proccess.each do |cell, index|
289
+ current_colspan = cell.colspan
290
+ calculate_width = @column_widths.slice( index, current_colspan ).
291
+ inject( 0 ) { |t, w| t + w }
292
+ length = cells_width( cell )
293
+ if length > calculate_width
294
+ # This is a little tricky, we have to increase each column
295
+ # that the actual colspan cell use, by a proportional part
296
+ # so the sum of these widths will be equal to the actual width
297
+ # of our colspan cell
298
+ difference = length - calculate_width
299
+ increase = ( difference / current_colspan ).floor
300
+ increase_by = [ increase ] * current_colspan
301
+ # it's important to sum, in total, the difference, so if
302
+ # difference is, e.g., 3 and current_colspan is 2, increase_by
303
+ # will be [ 1, 1 ], but actually we want to be [ 2, 1 ]
304
+ extra_dif = difference - increase * current_colspan
305
+ extra_dif.times { |n| increase_by[n] += 1 }
306
+ current_colspan.times do |j|
307
+ @column_widths[ index + j ] += increase_by[j]
308
+ end
309
+ end
310
+ end
311
+
312
+ # Thridly, establish manual column widths
313
+ manual_width = 0
314
+ manual_widths.each { |k,v|
315
+ @column_widths[k] = v; manual_width += v } if manual_widths
316
+
317
+ # Finally, ensures that the maximum width of the document is not exceeded.
318
+ # Takes into consideration the manual widths specified (With full manual
319
+ # widths specified, the width can exceed the document width as manual
320
+ # widths are taken as gospel)
321
+ max_width ||= width || @document.margin_box.width
322
+ calculated_width = @column_widths.inject {|sum,e| sum += e }
323
+
324
+ if calculated_width > max_width
325
+ shrink_by = (max_width - manual_width).to_f /
326
+ (calculated_width - manual_width)
327
+ @column_widths.each_with_index { |c,i|
328
+ @column_widths[i] = c * shrink_by if manual_widths.nil? ||
329
+ manual_widths[i].nil?
330
+ }
331
+ elsif width && calculated_width < width
332
+ grow_by = (width - manual_width).to_f /
333
+ (calculated_width - manual_width)
334
+ @column_widths.each_with_index { |c,i|
335
+ @column_widths[i] = c * grow_by if manual_widths.nil? ||
336
+ manual_widths[i].nil?
337
+ }
338
+ end
339
+ end
340
+
341
+
342
+ def renderable_data
343
+ C(:headers) ? C(:headers) + @data : @data
344
+ end
345
+
346
+ # Transform all items from @data into Prawn::Table::Cell objects
347
+ def normalize_data
348
+ normalize = lambda { |data|
349
+ data.map do |row|
350
+ row.map do |cell|
351
+ unless cell.is_a?( Hash ) || cell.is_a?( Prawn::Table::Cell )
352
+ cell = { :text => cell.to_s }
353
+ end
354
+ if cell.is_a?( Hash )
355
+ cell = Prawn::Table::Cell.new( cell )
356
+ end
357
+ cell.document = @document
358
+ cell
359
+ end
360
+ end
361
+ }
362
+ @data = normalize.call( @data )
363
+ # C is an alias to configuration method, which is a wrapper around @config
364
+ @config[:headers] = normalize.call( [ C(:headers) ] ) if C(:headers)
365
+
366
+ end
367
+
368
+ def generate_table
369
+ page_contents = []
370
+ y_pos = @document.y
371
+ rowspan_cells = {}
372
+
373
+ @document.font_size C(:font_size) do
374
+ renderable_data.each_with_index do |row, index|
375
+ c = Prawn::Table::CellBlock.new(@document)
376
+
377
+ rowspan_cells.each_value { |v| v[:rowspan] -= 1 }
378
+ rowspan_cells.delete_if { |k, v| v[:rowspan] == 0 }
379
+
380
+ col_index = 0
381
+ row.each do |e|
382
+ align = case C(:align)
383
+ when Hash
384
+ C(:align)[ col_index ]
385
+ else
386
+ C(:align)
387
+ end
388
+ align ||= e.to_s =~ NUMBER_PATTERN ? :right : :left
389
+
390
+ while rowspan_cells[ col_index ] do
391
+ c << rowspan_cells[ col_index ][:cell_fake]
392
+ col_index += rowspan_cells[ col_index ][:colspan]
393
+ end
394
+
395
+ colspan = e.colspan
396
+ rowspan = e.rowspan
397
+
398
+ width = @column_widths.
399
+ slice( col_index, colspan ).
400
+ inject { |sum, width| sum + width }
401
+
402
+ e.width = width
403
+ e.horizontal_padding = C(:horizontal_padding)
404
+ e.vertical_padding = C(:vertical_padding)
405
+ e.border_width = C(:border_width)
406
+ e.align ||= align
407
+
408
+ if rowspan > 1
409
+ cell_fake = Prawn::Table::CellFake.new( :width => width )
410
+ rowspan_cells[ col_index ] = {
411
+ :rowspan => rowspan,
412
+ :colspan => colspan,
413
+ :cell_fake => cell_fake
414
+ }
415
+ end
416
+ c << e
417
+ col_index += colspan
418
+ end # row.each do |e|
419
+
420
+ bbox = @parent_bounds.stretchy? ? @document.margin_box : @parent_bounds
421
+ fit_in_current_page = c.height <= y_pos - bbox.absolute_bottom
422
+ if ! fit_in_current_page then
423
+ if C(:headers) && page_contents.length == 1
424
+ @document.start_new_page
425
+ y_pos = @document.y
426
+ else
427
+ draw_page(page_contents)
428
+ @document.start_new_page
429
+ if C(:headers) && page_contents.any?
430
+ page_contents = [page_contents[0]]
431
+ y_pos = @document.y - page_contents[0].height
432
+ else
433
+ page_contents = []
434
+ y_pos = @document.y
435
+ end
436
+ end
437
+ end
438
+
439
+ page_contents << c
440
+
441
+ y_pos -= c.height
442
+
443
+ if index == renderable_data.length - 1
444
+ draw_page(page_contents)
445
+ end
446
+
447
+ end
448
+ end
449
+ end
450
+
451
+ def draw_page(contents)
452
+ return if contents.empty?
453
+
454
+ if C(:border_style) == :underline_header
455
+ contents.each { |e| e.border_style = :none }
456
+ contents.first.border_style = :bottom_only if C(:headers)
457
+ elsif C(:border_style) == :grid || contents.length == 1
458
+ contents.each { |e| e.border_style = :all }
459
+ else
460
+ contents.first.border_style = C(:headers) ? :all : :no_bottom
461
+ contents.last.border_style = :no_top
462
+ end
463
+
464
+ if C(:headers)
465
+ contents.first.cells.each_with_index do |e,i|
466
+ if C(:align_headers)
467
+ case C(:align_headers)
468
+ when Hash
469
+ align = C(:align_headers)[i]
470
+ else
471
+ align = C(:align_headers)
472
+ end
473
+ end
474
+ e.align = align if align
475
+ e.text_color = C(:header_text_color) if C(:header_text_color)
476
+ e.background_color = C(:header_color) if C(:header_color)
477
+ end
478
+ end
479
+
480
+ # modified the height of the cells with rowspan attribute
481
+ contents.each_with_index do |x, i|
482
+ x.cells.each do |cell|
483
+ if cell.rowspan > 1
484
+ heights_per_row ||= contents.map { |x| x.height }
485
+ cell.height = heights_per_row.
486
+ slice( i, cell.rowspan ).inject(0){ |sum, h| sum + h }
487
+ end
488
+ end
489
+ end
490
+
491
+ contents.each do |x|
492
+ unless x.background_color
493
+ x.background_color = next_row_color if C(:row_colors)
494
+ end
495
+ x.border_color = C(:border_color) if C(:border_color)
496
+
497
+ x.draw
498
+ end
499
+
500
+ reset_row_colors
501
+ end
502
+
503
+
504
+ def next_row_color
505
+ color = C(:row_colors).shift
506
+ C(:row_colors).push(color)
507
+ color
508
+ end
509
+
510
+ def reset_row_colors
511
+ C(:row_colors => C(:original_row_colors).dup) if C(:row_colors)
512
+ end
513
+
514
+ end
515
+ end
data/spec/grid_spec.rb ADDED
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
3
+
4
+ describe "A document's grid" do
5
+ before do
6
+ @pdf = Prawn::Document.new
7
+ end
8
+
9
+ it "should allow definition of a grid" do
10
+ @pdf.define_grid(:columns => 5, :rows => 8, :gutter => 0.1)
11
+ @pdf.grid.columns.should == 5
12
+ @pdf.grid.rows.should == 8
13
+ @pdf.grid.gutter.should == 0.1
14
+ end
15
+
16
+ describe "when a grid is defined" do
17
+ before do
18
+ @num_columns = 5
19
+ @num_rows = 8
20
+ @gutter = 10.0
21
+ @pdf.define_grid(
22
+ :columns => @num_columns,
23
+ :rows => @num_rows,
24
+ :gutter => @gutter
25
+ )
26
+ end
27
+
28
+ it "should compute the column width" do
29
+ (@pdf.grid.column_width * @num_columns.to_f +
30
+ @gutter * (@num_columns - 1).to_f).should == @pdf.bounds.width
31
+ end
32
+
33
+ it "should compute the row height" do
34
+ (@pdf.grid.row_height * @num_rows.to_f +
35
+ @gutter * (@num_rows - 1).to_f).should == @pdf.bounds.height
36
+ end
37
+
38
+ it "should give the edges of a grid box" do
39
+ grid_width = (@pdf.bounds.width.to_f -
40
+ (@gutter * (@num_columns - 1).to_f )) / @num_columns.to_f
41
+ grid_height = (@pdf.bounds.height.to_f -
42
+ (@gutter * (@num_rows - 1).to_f ))/ @num_rows.to_f
43
+
44
+ exp_tl_x = (grid_width + @gutter.to_f) * 4.0
45
+ exp_tl_y = @pdf.bounds.height.to_f - (grid_height + @gutter.to_f)
46
+
47
+ @pdf.grid(1,4).top_left.should == [exp_tl_x, exp_tl_y]
48
+ @pdf.grid(1,4).top_right.should == [exp_tl_x + grid_width, exp_tl_y]
49
+ @pdf.grid(1,4).bottom_left.should == [exp_tl_x, exp_tl_y - grid_height]
50
+ @pdf.grid(1,4).bottom_right.should == [exp_tl_x + grid_width, exp_tl_y - grid_height]
51
+ end
52
+
53
+ it "should give the edges of a multiple grid boxes" do
54
+ # Hand verified. Cheating a bit. Don't tell.
55
+ @pdf.grid([1,3], [2,5]).top_left.should == [330.0, 628.75]
56
+ @pdf.grid([1,3], [2,5]).top_right.should == [650.0, 628.75]
57
+ @pdf.grid([1,3], [2,5]).bottom_left.should == [330.0, 456.25]
58
+ @pdf.grid([1,3], [2,5]).bottom_right.should == [650.0, 456.25]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
3
+
4
+ describe "When beginning each new page" do
5
+
6
+ it "should execute codeblock given to Document#header" do
7
+ call_count = 0
8
+
9
+ pdf = Prawn::Document.new
10
+ pdf.header(pdf.margin_box.top_left) do
11
+ call_count += 1
12
+ end
13
+
14
+ pdf.start_new_page
15
+ pdf.start_new_page
16
+ pdf.render
17
+
18
+ call_count.should == 3
19
+ end
20
+
21
+ end
22
+
23
+ describe "When ending each page" do
24
+
25
+ it "should execute codeblock given to Document#footer" do
26
+
27
+ call_count = 0
28
+
29
+ pdf = Prawn::Document.new
30
+ pdf.footer([pdf.margin_box.left, pdf.margin_box.bottom + 50]) do
31
+ call_count += 1
32
+ end
33
+
34
+ pdf.start_new_page
35
+ pdf.start_new_page
36
+ pdf.render
37
+
38
+ call_count.should == 3
39
+ end
40
+
41
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ puts "Prawn specs: Running on Ruby Version: #{RUBY_VERSION}"
4
+
5
+ require "rubygems"
6
+ require "test/spec"
7
+ require "mocha"
8
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
9
+
10
+ require "prawn"
11
+ require "prawn/layout"
12
+ $LOAD_PATH << File.join(Prawn::BASEDIR, 'vendor','pdf-inspector','lib')
13
+
14
+ Prawn.debug = true
15
+
16
+ gem 'pdf-reader', ">=0.7.3"
17
+ require "pdf/reader"
18
+ require "pdf/inspector"
19
+
20
+ def create_pdf(klass=Prawn::Document)
21
+ @pdf = klass.new(:left_margin => 0,
22
+ :right_margin => 0,
23
+ :top_margin => 0,
24
+ :bottom_margin => 0)
25
+ end
26
+
27
+ def width_table_for( pdf, hpad, fs, cells )
28
+ cells.inject( 2 * cells.size * hpad ) do |ret, cell|
29
+ ret + pdf.width_of( cell, :size => fs ).ceil
30
+ end
31
+ end