prawn-table-continued 1.0.0.rc1

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.
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,1626 @@
1
+ # encoding: utf-8
2
+
3
+ # run rspec -t issue:XYZ to run tests for a specific github issue
4
+ # or rspec -t unresolved to run tests for all unresolved issues
5
+
6
+
7
+ require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
8
+
9
+ require_relative "../lib/prawn/table"
10
+ require 'set'
11
+
12
+ describe "Prawn::Table" do
13
+
14
+ describe "converting data to Cell objects" do
15
+ before(:each) do
16
+ @pdf = Prawn::Document.new
17
+ @table = @pdf.table([%w[R0C0 R0C1], %w[R1C0 R1C1]])
18
+ end
19
+
20
+ it "should return a Prawn::Table" do
21
+ expect(@table).to be_a_kind_of Prawn::Table
22
+ end
23
+
24
+ it "should flatten the data into the @cells array in row-major order" do
25
+ expect(@table.cells.map { |c| c.content }).to eq %w[R0C0 R0C1 R1C0 R1C1]
26
+ end
27
+
28
+ it "should add row and column numbers to each cell" do
29
+ c = @table.cells.to_a.first
30
+ expect(c.row).to eq 0
31
+ expect(c.column).to eq 0
32
+ end
33
+
34
+ it "should allow empty fields" do
35
+ expect {
36
+ data = [["foo","bar"],["baz",""]]
37
+ @pdf.table(data)
38
+ }.to_not raise_error
39
+ end
40
+
41
+ it "should allow a table with a header but no body" do
42
+ expect { @pdf.table([["Header"]], :header => true) }.to_not raise_error
43
+ end
44
+
45
+ it "should accurately count columns from data" do
46
+ # First data row may contain colspan which would hide true column count
47
+ data = [["Name:", {:content => "Some very long name", :colspan => 5}]]
48
+ pdf = Prawn::Document.new
49
+ table = Prawn::Table.new data, pdf
50
+ expect(table.column_widths.length).to eq 6
51
+ end
52
+ end
53
+
54
+ describe "headers should allow for rowspan" do
55
+ it "should remember rowspans accross multiple pages", :issue => 721 do
56
+ pdf = Prawn::Document.new({:page_size => "A4", :page_layout => :portrait})
57
+ rows = [ [{:content=>"The\nNumber", :rowspan=>2}, {:content=>"Prefixed", :colspan=>2} ],
58
+ ["A's", "B's"] ]
59
+
60
+ (1..50).each do |n|
61
+ rows.push( ["#{n}", "A#{n}", "B#{n}"] )
62
+ end
63
+
64
+ pdf.table( rows, :header=>2 ) do
65
+ row(0..1).style :background_color=>"FFFFCC"
66
+ end
67
+
68
+ #ensure that the header on page 1 is identical to the header on page 0
69
+ output = PDF::Inspector::Page.analyze(pdf.render)
70
+ expect(output.pages[0][:strings][0..4]).to eq output.pages[1][:strings][0..4]
71
+ end
72
+
73
+ it "should respect an explicit set table with", :issue => 6 do
74
+ data = [[{ :content => "Current Supplier: BLINKY LIGHTS COMPANY", :colspan => 4 }],
75
+ ["Current Supplier: BLINKY LIGHTS COMPANY", "611 kWh X $.090041", "$", "55.02"]]
76
+ pdf = Prawn::Document.new
77
+ table = Prawn::Table.new data, pdf, :width => pdf.bounds.width
78
+ expect(table.column_widths.inject{|sum,x| sum + x }).to eq pdf.bounds.width
79
+ end
80
+ end
81
+
82
+ describe "Text may be longer than the available space in a row on a single page" do
83
+ it "should not glitch the layout if there is too much text to fit onto a single row on a single page", :unresolved, :issue => 562 do
84
+ pdf = Prawn::Document.new({:page_size => "A4", :page_layout => :portrait})
85
+
86
+ table_data = Array.new
87
+ text = 'This will be a very long text. ' * 5
88
+ table_data.push([{:content => text, :rowspan => 2}, 'b', 'c'])
89
+ table_data.push(['b','c'])
90
+
91
+ column_widths = [50, 60, 400]
92
+
93
+ table = Prawn::Table.new table_data, pdf,:column_widths => column_widths
94
+
95
+ #render the table onto the pdf
96
+ table.draw
97
+
98
+ #expected behavior would be for the long text to be cut off or an exception to be raised
99
+ #thus we only expect a single page
100
+ expect(pdf.page_count).to eq 1
101
+ end
102
+ end
103
+
104
+ describe "You can explicitly set the column widths and use a colspan > 1" do
105
+
106
+ it "should tolerate floating point rounding errors < 0.000000001" do
107
+ data=[["a", "b ", "c ", "d", "e", "f", "g", "h", "i", "j", "k", "l"],
108
+ [{:content=>"Foobar", :colspan=>12}]
109
+ ]
110
+ #we need values with lots of decimals so that arithmetic errors will occur
111
+ #the values are not arbitrary but where found converting mm to pdf pt
112
+ column_widths=[137, 40, 40, 54.69291338582678, 54.69291338582678,
113
+ 54.69291338582678, 54.69291338582678, 54.69291338582678,
114
+ 54.69291338582678, 54.69291338582678, 54.69291338582678,
115
+ 54.69291338582678]
116
+
117
+ pdf = Prawn::Document.new({:page_size => 'A4', :page_layout => :landscape})
118
+ table = Prawn::Table.new data, pdf, :column_widths => column_widths
119
+ expect(table.column_widths).to eq column_widths
120
+ end
121
+
122
+ it "should work with two different given colspans", :issue => 628 do
123
+ data = [
124
+ [" ", " ", " "],
125
+ [{:content=>" ", :colspan=>3}],
126
+ [" ", {:content=>" ", :colspan=>2}]
127
+ ]
128
+ column_widths = [60, 240, 60]
129
+ pdf = Prawn::Document.new
130
+ #the next line raised an Prawn::Errors::CannotFit exception before issue 628 was fixed
131
+ table = Prawn::Table.new data, pdf, :column_widths => column_widths
132
+ expect(table.column_widths).to eq column_widths
133
+ end
134
+
135
+ it "should work with a colspan > 1 with given column_widths (issue #407)" do
136
+ #normal entries in line 1
137
+ data = [
138
+ [ '','',''],
139
+ [ { :content => "", :colspan => 3 } ],
140
+ [ "", "", "" ],
141
+ ]
142
+ pdf = Prawn::Document.new
143
+ table = Prawn::Table.new data, pdf, :column_widths => [100 , 200, 240]
144
+
145
+ #colspan entry in line 1
146
+ data = [
147
+ [ { :content => "", :colspan => 3 } ],
148
+ [ "", "", "" ],
149
+ ]
150
+ pdf = Prawn::Document.new
151
+ table = Prawn::Table.new data, pdf, :column_widths => [100 , 200, 240]
152
+
153
+ #mixed entries in line 1
154
+ data = [
155
+ [ { :content => "", :colspan =>2 }, "" ],
156
+ [ "", "", "" ],
157
+ ]
158
+ pdf = Prawn::Document.new
159
+ table = Prawn::Table.new data, pdf, :column_widths => [100 , 200, 240]
160
+
161
+ data = [['', '', {:content => '', :colspan => 2}, '',''],
162
+ ['',{:content => '', :colspan => 5}]
163
+ ]
164
+ pdf = Prawn::Document.new
165
+ table = Prawn::Table.new data, pdf, :column_widths => [50 , 100, 50, 50, 50, 50]
166
+
167
+ end
168
+
169
+ it "should not increase column width when rendering a subtable",
170
+ :unresolved, :issue => 612 do
171
+
172
+ pdf = Prawn::Document.new
173
+
174
+ first = {:content=>"Foooo fo foooooo",:width=>50,:align=>:center}
175
+ second = {:content=>"Foooo",:colspan=>2,:width=>70,:align=>:center}
176
+ third = {:content=>"fooooooooooo, fooooooooooooo, fooo, foooooo fooooo",:width=>50,:align=>:center}
177
+ fourth = {:content=>"Bar",:width=>20,:align=>:center}
178
+
179
+ table_content = [[
180
+ first,
181
+ [[second],[third,fourth]]
182
+ ]]
183
+
184
+ table = Prawn::Table.new table_content, pdf
185
+ expect(table.column_widths).to eq [50.0, 70.0]
186
+ end
187
+
188
+ it "illustrates issue #710", :issue => 710 do
189
+ partial_width = 40
190
+ pdf = Prawn::Document.new({page_size: "LETTER", page_layout: :portrait})
191
+ col_widths = [
192
+ 50,
193
+ partial_width, partial_width, partial_width, partial_width
194
+ ]
195
+
196
+ day_header = [{
197
+ content: "Monday, August 5th, A.S. XLIX",
198
+ colspan: 5,
199
+ }]
200
+
201
+ times = [{
202
+ content: "Loc",
203
+ colspan: 1,
204
+ }, {
205
+ content: "8:00",
206
+ colspan: 4,
207
+ }]
208
+
209
+ data = [ day_header ] + [ times ]
210
+
211
+ #raised a Prawn::Errors::CannotFit:
212
+ #Table's width was set larger than its contents' maximum width (max width 210, requested 218.0)
213
+ table = Prawn::Table.new data, pdf, :column_widths => col_widths
214
+ end
215
+
216
+ it "illustrate issue #533" do
217
+ data = [['', '', '', '', '',''],
218
+ ['',{:content => '', :colspan => 5}]]
219
+ pdf = Prawn::Document.new
220
+ table = Prawn::Table.new data, pdf, :column_widths => [50, 200, 40, 40, 50, 50]
221
+ end
222
+
223
+ it "illustrates issue #502" do
224
+ pdf = Prawn::Document.new
225
+ first = {:content=>"Foooo fo foooooo",:width=>50,:align=>:center}
226
+ second = {:content=>"Foooo",:colspan=>2,:width=>70,:align=>:center}
227
+ third = {:content=>"fooooooooooo, fooooooooooooo, fooo, foooooo fooooo",:width=>50,:align=>:center}
228
+ fourth = {:content=>"Bar",:width=>20,:align=>:center}
229
+ table_content = [[
230
+ first,
231
+ [[second],[third,fourth]]
232
+ ]]
233
+ pdf.move_down(20)
234
+ table = Prawn::Table.new table_content, pdf
235
+ pdf.table(table_content)
236
+ end
237
+
238
+ #https://github.com/prawnpdf/prawn/issues/407#issuecomment-28556698
239
+ it "correctly computes column widths with empty cells + colspan" do
240
+ data = [['', ''],
241
+ [{:content => '', :colspan => 2}]
242
+ ]
243
+ pdf = Prawn::Document.new
244
+
245
+ table = Prawn::Table.new data, pdf, :column_widths => [50, 200]
246
+ expect(table.column_widths).to eq [50.0, 200.0]
247
+ end
248
+
249
+ it "illustrates a variant of problem in issue #407 - comment 28556698" do
250
+ pdf = Prawn::Document.new
251
+ table_data = [["a", "b", "c"], [{:content=>"d", :colspan=>3}]]
252
+ column_widths = [50, 60, 400]
253
+
254
+ # Before we fixed #407, this line incorrectly raise a CannotFit error
255
+ pdf.table(table_data, :column_widths => column_widths)
256
+ end
257
+
258
+ it "should not allow oversized subtables when parent column width is constrained" do
259
+ pdf = Prawn::Document.new
260
+ child_1 = pdf.make_table([['foo'*100]])
261
+ child_2 = pdf.make_table([['foo']])
262
+ expect {
263
+ pdf.table([[child_1], [child_2]], column_widths: [pdf.bounds.width/2] * 2)
264
+ }.to raise_error(Prawn::Errors::CannotFit)
265
+ end
266
+ end
267
+
268
+ describe "#initialize" do
269
+ before(:each) do
270
+ @pdf = Prawn::Document.new
271
+ end
272
+
273
+ it "should instance_eval a 0-arg block" do
274
+ initializer = double
275
+ expect(initializer).to receive(:kick).once
276
+
277
+ @pdf.table([["a"]]) do
278
+ initializer.kick
279
+ end
280
+ end
281
+
282
+ it "should call a 1-arg block with the document as the argument" do
283
+ initializer = double
284
+ expect(initializer).to receive(:kick).once
285
+
286
+ @pdf.table([["a"]]) do |doc|
287
+ expect(doc).to be_a_kind_of(Prawn::Table)
288
+ initializer.kick
289
+ end
290
+ end
291
+
292
+ it "should proxy cell methods to #cells" do
293
+ table = @pdf.table([["a"]], :cell_style => { :padding => 11 })
294
+ expect(table.cells[0, 0].padding).to eq [11, 11, 11, 11]
295
+ end
296
+
297
+ it "should set row and column length" do
298
+ table = @pdf.table([["a", "b", "c"], ["d", "e", "f"]])
299
+ expect(table.row_length).to eq 2
300
+ expect(table.column_length).to eq 3
301
+ end
302
+
303
+ it "should generate a text cell based on a String" do
304
+ t = @pdf.table([["foo"]])
305
+ expect(t.cells[0,0]).to be_a_kind_of(Prawn::Table::Cell::Text)
306
+ end
307
+
308
+ it "should pass through a text cell" do
309
+ c = Prawn::Table::Cell::Text.new(@pdf, [0,0], :content => "foo")
310
+ t = @pdf.table([[c]])
311
+ expect(t.cells[0,0]).to eq c
312
+ end
313
+ end
314
+
315
+ describe "cell accessors" do
316
+ before(:each) do
317
+ @pdf = Prawn::Document.new
318
+ @table = @pdf.table([%w[R0C0 R0C1], %w[R1C0 R1C1]])
319
+ end
320
+
321
+ it "should select rows by number or range" do
322
+ expect(@table.row(0).map(&:content)).to match_array %w[R0C0 R0C1]
323
+ expect(@table.rows(0..1).map(&:content)).to match_array %w[R0C0 R0C1 R1C0 R1C1]
324
+ end
325
+
326
+ it "should select rows by array" do
327
+ expect(@table.rows([0, 1]).map(&:content)).to match_array %w[R0C0 R0C1 R1C0 R1C1]
328
+ end
329
+
330
+ it "should allow negative row selectors" do
331
+ expect(@table.row(-1).map(&:content)).to match_array %w[R1C0 R1C1]
332
+ expect(@table.rows(-2..-1).map(&:content)).to match_array %w[R0C0 R0C1 R1C0 R1C1]
333
+ expect(@table.rows(0..-1).map(&:content)).to match_array %w[R0C0 R0C1 R1C0 R1C1]
334
+ end
335
+
336
+ it "should select columns by number or range" do
337
+ expect(@table.column(0).map(&:content)).to match_array %w[R0C0 R1C0]
338
+ expect(@table.columns(0..1).map(&:content)).to match_array %w[R0C0 R0C1 R1C0 R1C1]
339
+ end
340
+
341
+ it "should select columns by array" do
342
+ expect(@table.columns([0, 1]).map(&:content)).to match_array %w[R0C0 R0C1 R1C0 R1C1]
343
+ end
344
+
345
+ it "should allow negative column selectors" do
346
+ expect(@table.column(-1).map(&:content)).to match_array %w[R0C1 R1C1]
347
+ expect(@table.columns(-2..-1).map(&:content)).to match_array %w[R0C0 R0C1 R1C0 R1C1]
348
+ expect(@table.columns(0..-1).map(&:content)).to match_array %w[R0C0 R0C1 R1C0 R1C1]
349
+ end
350
+
351
+ it "should allow rows and columns to be combined" do
352
+ expect(@table.row(0).column(1).map { |c| c.content }).to eq ["R0C1"]
353
+ end
354
+
355
+ it "should accept a filter block, returning a cell proxy" do
356
+ expect(@table.cells.filter { |c| c.content =~ /R0/ }.column(1).map{ |c|
357
+ c.content }).to eq ["R0C1"]
358
+ end
359
+
360
+ it "should accept the [] method, returning a Cell or nil" do
361
+ expect(@table.cells[0, 0].content).to eq "R0C0"
362
+ expect(@table.cells[12, 12]).to be_nil
363
+ end
364
+
365
+ it "should proxy unknown methods to the cells" do
366
+ @table.cells.height = 200
367
+ @table.row(1).height = 100
368
+
369
+ expect(@table.cells[0, 0].height).to eq 200
370
+ expect(@table.cells[1, 0].height).to eq 100
371
+ end
372
+
373
+ it "should ignore non-setter methods" do
374
+ expect {
375
+ @table.cells.content_width
376
+ }.to raise_error(NoMethodError)
377
+ end
378
+
379
+ it "skips cells that don't respond to the given method" do
380
+ table = @pdf.make_table([[{:content => "R0", :colspan => 2}],
381
+ %w[R1C0 R1C1]])
382
+ expect {
383
+ table.row(0).font_style = :bold
384
+ }.to_not raise_error
385
+ end
386
+
387
+ it "should accept the style method, proxying its calls to the cells" do
388
+ @table.cells.style(:height => 200, :width => 200)
389
+ @table.column(0).style(:width => 100)
390
+
391
+ expect(@table.cells[0, 1].width).to eq 200
392
+ expect(@table.cells[1, 0].height).to eq 200
393
+ expect(@table.cells[1, 0].width).to eq 100
394
+ end
395
+
396
+ it "style method should accept a block, passing each cell to be styled" do
397
+ @table.cells.style { |c| c.height = 200 }
398
+ expect(@table.cells[0, 1].height).to eq 200
399
+ end
400
+
401
+ it "should return the width of selected columns for #width" do
402
+ c0_width = @table.column(0).map{ |c| c.width }.max
403
+ c1_width = @table.column(1).map{ |c| c.width }.max
404
+
405
+ expect(@table.column(0).width).to eq c0_width
406
+ expect(@table.column(1).width).to eq c1_width
407
+
408
+ expect(@table.columns(0..1).width).to eq c0_width + c1_width
409
+ expect(@table.cells.width).to eq c0_width + c1_width
410
+ end
411
+
412
+ it "should return the height of selected rows for #height" do
413
+ r0_height = @table.row(0).map{ |c| c.height }.max
414
+ r1_height = @table.row(1).map{ |c| c.height }.max
415
+
416
+ expect(@table.row(0).height).to eq r0_height
417
+ expect(@table.row(1).height).to eq r1_height
418
+
419
+ expect(@table.rows(0..1).height).to eq r0_height + r1_height
420
+ expect(@table.cells.height).to eq r0_height + r1_height
421
+ end
422
+ end
423
+
424
+ describe "layout" do
425
+ before(:each) do
426
+ @pdf = Prawn::Document.new
427
+ @long_text = "The quick brown fox jumped over the lazy dogs. " * 5
428
+ end
429
+
430
+ describe "width" do
431
+ it "should raise_error an error if the given width is outside of range" do
432
+ expect {
433
+ @pdf.table([["foo"]], :width => 1)
434
+ }.to raise_error(Prawn::Errors::CannotFit)
435
+
436
+ expect {
437
+ @pdf.table([[@long_text]], :width => @pdf.bounds.width + 100)
438
+ }.to raise_error(Prawn::Errors::CannotFit)
439
+ end
440
+
441
+ it "should accept the natural width for small tables" do
442
+ pad = 10 # default padding
443
+ @table = @pdf.table([["a"]])
444
+ expect(@table.width).to eq @table.cells[0, 0].natural_content_width + pad
445
+ end
446
+
447
+ it "width should == sum(column_widths)" do
448
+ table = Prawn::Table.new([%w[ a b c ], %w[d e f]], @pdf) do
449
+ column(0).width = 50
450
+ column(1).width = 100
451
+ column(2).width = 150
452
+ end
453
+ expect(table.width).to eq 300
454
+ end
455
+
456
+ it "should accept Numeric for column_widths" do
457
+ table = Prawn::Table.new([%w[ a b c ], %w[d e f]], @pdf) do |t|
458
+ t.column_widths = 50
459
+ end
460
+ expect(table.width).to eq 150
461
+ end
462
+
463
+ it "should calculate unspecified column widths as "+
464
+ "(max(string_width) + 2*horizontal_padding)" do
465
+ hpad, fs = 3, 12
466
+ columns = 2
467
+ table = Prawn::Table.new( [%w[ foo b ], %w[d foobar]], @pdf,
468
+ :cell_style => { :padding => hpad, :size => fs } )
469
+
470
+ col0_width = @pdf.width_of("foo", :size => fs)
471
+ col1_width = @pdf.width_of("foobar", :size => fs)
472
+
473
+ expect(table.width).to eq col0_width + col1_width + 2*columns*hpad
474
+ end
475
+
476
+ it "should allow mixing autocalculated and preset"+
477
+ "column widths within a single table" do
478
+ hpad, fs = 10, 6
479
+ stretchy_columns = 2
480
+
481
+ col0_width = 50
482
+ col1_width = @pdf.width_of("foo", :size => fs)
483
+ col2_width = @pdf.width_of("foobar", :size => fs)
484
+ col3_width = 150
485
+
486
+ table = Prawn::Table.new( [%w[snake foo b apple],
487
+ %w[kitten d foobar banana]], @pdf,
488
+ :cell_style => { :padding => hpad, :size => fs }) do
489
+
490
+ column(0).width = col0_width
491
+ column(3).width = col3_width
492
+ end
493
+
494
+ expect(table.width).to eq col1_width + col2_width +
495
+ 2*stretchy_columns*hpad +
496
+ col0_width + col3_width
497
+ end
498
+
499
+ it "should preserve all manually requested column widths" do
500
+ col0_width = 50
501
+ col1_width = 20
502
+ col3_width = 60
503
+
504
+ table = Prawn::Table.new( [["snake", "foo", "b",
505
+ "some long, long text that will wrap"],
506
+ %w[kitten d foobar banana]], @pdf,
507
+ :width => 150) do
508
+
509
+ column(0).width = col0_width
510
+ column(1).width = col1_width
511
+ column(3).width = col3_width
512
+ end
513
+
514
+ table.draw
515
+
516
+ expect(table.column(0).width).to eq col0_width
517
+ expect(table.column(1).width).to eq col1_width
518
+ expect(table.column(3).width).to eq col3_width
519
+ end
520
+
521
+ it "should_not exceed the maximum width of the margin_box" do
522
+ expected_width = @pdf.margin_box.width
523
+ data = [
524
+ ['This is a column with a lot of text that should comfortably exceed '+
525
+ 'the width of a normal document margin_box width', 'Some more text',
526
+ 'and then some more', 'Just a bit more to be extra sure']
527
+ ]
528
+ table = Prawn::Table.new(data, @pdf)
529
+
530
+ expect(table.width).to eq expected_width
531
+ end
532
+
533
+ it "should_not exceed the maximum width of the margin_box even with" +
534
+ "manual widths specified" do
535
+ expected_width = @pdf.margin_box.width
536
+ data = [
537
+ ['This is a column with a lot of text that should comfortably exceed '+
538
+ 'the width of a normal document margin_box width', 'Some more text',
539
+ 'and then some more', 'Just a bit more to be extra sure']
540
+ ]
541
+ table = Prawn::Table.new(data, @pdf) { column(1).width = 100 }
542
+
543
+ expect(table.width).to eq expected_width
544
+ end
545
+
546
+ it "scales down only the non-preset column widths when the natural width" +
547
+ "exceeds the maximum width of the margin_box" do
548
+ expected_width = @pdf.margin_box.width
549
+ data = [
550
+ ['This is a column with a lot of text that should comfortably exceed '+
551
+ 'the width of a normal document margin_box width', 'Some more text',
552
+ 'and then some more', 'Just a bit more to be extra sure']
553
+ ]
554
+ table = Prawn::Table.new(data, @pdf) { column(1).width = 100; column(3).width = 50 }
555
+
556
+ expect(table.width).to eq expected_width
557
+ expect(table.column_widths[1]).to eq 100
558
+ expect(table.column_widths[3]).to eq 50
559
+ end
560
+
561
+ it "should allow width to be reset even after it has been calculated" do
562
+ @table = @pdf.table([[@long_text]])
563
+ @table.width
564
+ @table.width = 100
565
+ expect(@table.width).to eq 100
566
+ end
567
+
568
+ it "should shrink columns evenly when two equal columns compete" do
569
+ @table = @pdf.table([["foo", @long_text], [@long_text, "foo"]])
570
+ expect(@table.cells[0, 0].width).to eq @table.cells[0, 1].width
571
+ end
572
+
573
+ it "should grow columns evenly when equal deficient columns compete" do
574
+ @table = @pdf.table([["foo", "foobar"], ["foobar", "foo"]], :width => 500)
575
+ expect(@table.cells[0, 0].width).to eq @table.cells[0, 1].width
576
+ end
577
+
578
+ it "should respect manual widths" do
579
+ @table = @pdf.table([%w[foo bar baz], %w[baz bar foo]], :width => 500) do
580
+ column(1).width = 60
581
+ end
582
+ expect(@table.column(1).width).to eq 60
583
+ expect(@table.column(0).width).to eq @table.column(2).width
584
+ end
585
+
586
+ it "should allow table cells to be resized in block" do
587
+ # if anything goes wrong, a CannotFit error will be raised
588
+
589
+ @pdf.table([%w[1 2 3 4 5]]) do |t|
590
+ t.width = 40
591
+ t.cells.size = 8
592
+ t.cells.padding = 0
593
+ end
594
+ end
595
+
596
+ it "should be the width of the :width parameter" do
597
+ expected_width = 300
598
+ table = Prawn::Table.new( [%w[snake foo b apple],
599
+ %w[kitten d foobar banana]], @pdf,
600
+ :width => expected_width)
601
+
602
+ expect(table.width).to eq expected_width
603
+ end
604
+
605
+ it "should_not exceed the :width option" do
606
+ expected_width = 400
607
+ data = [
608
+ ['This is a column with a lot of text that should comfortably exceed '+
609
+ 'the width of a normal document margin_box width', 'Some more text',
610
+ 'and then some more', 'Just a bit more to be extra sure']
611
+ ]
612
+ table = Prawn::Table.new(data, @pdf, :width => expected_width)
613
+
614
+ expect(table.width).to eq expected_width
615
+ end
616
+
617
+ it "should_not exceed the :width option even with manual widths specified" do
618
+ expected_width = 400
619
+ data = [
620
+ ['This is a column with a lot of text that should comfortably exceed '+
621
+ 'the width of a normal document margin_box width', 'Some more text',
622
+ 'and then some more', 'Just a bit more to be extra sure']
623
+ ]
624
+ table = Prawn::Table.new(data, @pdf, :width => expected_width) do
625
+ column(1).width = 100
626
+ end
627
+
628
+ expect(table.width).to eq expected_width
629
+ end
630
+
631
+ it "should calculate unspecified column widths even " +
632
+ "with colspan cells declared" do
633
+ pdf = Prawn::Document.new
634
+ hpad, fs = 3, 5
635
+ columns = 3
636
+
637
+ data = [ [ { :content => 'foo', :colspan => 2 }, "foobar" ],
638
+ [ "foo", "foo", "foo" ] ]
639
+ table = Prawn::Table.new( data, pdf,
640
+ :cell_style => {
641
+ :padding_left => hpad, :padding_right => hpad,
642
+ :size => fs
643
+ })
644
+
645
+ col0_width = pdf.width_of("foo", :size => fs) # cell 1, 0
646
+ col1_width = pdf.width_of("foo", :size => fs) # cell 1, 1
647
+ col2_width = pdf.width_of("foobar", :size => fs) # cell 0, 1 (at col 2)
648
+
649
+ expect(table.width).to eq col0_width + col1_width +
650
+ col2_width + 2*columns*hpad
651
+ end
652
+ end
653
+
654
+ describe "height" do
655
+ it "should set all cells in a row to the same height" do
656
+ @table = @pdf.table([["foo", @long_text]])
657
+ expect(@table.cells[0, 0].height).to eq @table.cells[0, 1].height
658
+ end
659
+
660
+ it "should move y-position to the bottom of the table after drawing" do
661
+ old_y = @pdf.y
662
+ table = @pdf.table([["foo"]])
663
+ expect(@pdf.y).to eq old_y - table.height
664
+ end
665
+
666
+ it "should_not wrap unnecessarily" do
667
+ # Test for FP errors and glitches
668
+ t = @pdf.table([["Bender Bending Rodriguez"]])
669
+ h = @pdf.height_of("one line")
670
+ expect(t.height - 10).to be < h*1.5
671
+ end
672
+
673
+ it "should have a height of n rows" do
674
+ data = [["foo"],["bar"],["baaaz"]]
675
+
676
+ vpad = 4
677
+ origin = @pdf.y
678
+ @pdf.table data, :cell_style => { :padding => vpad }
679
+
680
+ table_height = origin - @pdf.y
681
+ font_height = @pdf.font.height
682
+ line_gap = @pdf.font.line_gap
683
+
684
+ num_rows = data.length
685
+ expect(table_height).to be_within(0.001).of(
686
+ num_rows * font_height + 2*vpad*num_rows )
687
+ end
688
+
689
+ end
690
+
691
+ describe "position" do
692
+ it "should center tables with :position => :center" do
693
+ expect(@pdf).to receive(:bounding_box).with([(@pdf.bounds.width - 500) / 2.0, anything], kind_of(Hash))
694
+
695
+ @pdf.table([["foo"]], :column_widths => 500, :position => :center)
696
+ end
697
+
698
+ it "should right-align tables with :position => :right" do
699
+ expect(@pdf).to receive(:bounding_box).with([@pdf.bounds.width - 500, anything], kind_of(Hash))
700
+
701
+ @pdf.table([["foo"]], :column_widths => 500, :position => :right)
702
+ end
703
+
704
+ it "should accept a Numeric" do
705
+ expect(@pdf).to receive(:bounding_box).with([123, anything], kind_of(Hash))
706
+
707
+ @pdf.table([["foo"]], :column_widths => 500, :position => 123)
708
+ end
709
+
710
+ it "should raise_error an ArgumentError on unknown :position" do
711
+ expect {
712
+ @pdf.table([["foo"]], :position => :bratwurst)
713
+ }.to raise_error(ArgumentError)
714
+ end
715
+ end
716
+
717
+ end
718
+
719
+ describe "Multi-page tables" do
720
+ it "should flow to the next page when hitting the bottom of the bounds" do
721
+ expect(Prawn::Document.new { table([["foo"]] * 30) }.page_count).to eq 1
722
+ expect(Prawn::Document.new { table([["foo"]] * 31) }.page_count).to eq 2
723
+ expect(
724
+ Prawn::Document.new { table([["foo"]] * 31); table([["foo"]] * 35) }.page_count
725
+ ).to eq 3
726
+ end
727
+
728
+ it "should respect the containing bounds" do
729
+ expect(
730
+ Prawn::Document.new do
731
+ bounding_box([0, cursor], :width => bounds.width, :height => 72) do
732
+ table([["foo"]] * 4)
733
+ end
734
+ end.page_count
735
+ ).to eq 2
736
+ end
737
+
738
+ it "should_not start a new page before finishing out a row" do
739
+ expect(
740
+ Prawn::Document.new do
741
+ table([[ (1..80).map{ |i| "Line #{i}" }.join("\n"), "Column 2" ]])
742
+ end.page_count
743
+ ).to eq 1
744
+ end
745
+
746
+ it "should only start new page on long cells if it would gain us height" do
747
+ expect(
748
+ Prawn::Document.new do
749
+ text "Hello"
750
+ table([[ (1..80).map{ |i| "Line #{i}" }.join("\n"), "Column 2" ]])
751
+ end.page_count
752
+ ).to eq 2
753
+ end
754
+
755
+ it "should_not start a new page to gain height when at the top of " +
756
+ "a bounding box, even if stretchy" do
757
+ expect(
758
+ Prawn::Document.new do
759
+ bounding_box([bounds.left, bounds.top - 20], :width => 400) do
760
+ table([[ (1..80).map{ |i| "Line #{i}" }.join("\n"), "Column 2" ]])
761
+ end
762
+ end.page_count
763
+ ).to eq 1
764
+ end
765
+
766
+ it "should still break to the next page if in a stretchy bounding box " +
767
+ "but not at the top" do
768
+ expect(
769
+ Prawn::Document.new do
770
+ bounding_box([bounds.left, bounds.top - 20], :width => 400) do
771
+ text "Hello"
772
+ table([[ (1..80).map{ |i| "Line #{i}" }.join("\n"), "Column 2" ]])
773
+ end
774
+ end.page_count
775
+ ).to eq 2
776
+ end
777
+
778
+ it "should only draw first-page header if the first body row fits" do
779
+ pdf = Prawn::Document.new
780
+
781
+ pdf.y = 60 # not enough room for a table row
782
+ pdf.table [["Header"], ["Body"]], :header => true
783
+
784
+ output = PDF::Inspector::Page.analyze(pdf.render)
785
+ # Ensure we only drew the header once, on the second page
786
+ expect(output.pages[0][:strings]).to be_empty
787
+ expect(output.pages[1][:strings]).to eq ["Header", "Body"]
788
+ end
789
+
790
+ it 'should only draw first-page header if the first multi-row fits',
791
+ :issue => 707 do
792
+ pdf = Prawn::Document.new
793
+
794
+ pdf.y = 100 # not enough room for the header and multirow cell
795
+ pdf.table [
796
+ [{content: 'Header', colspan: 2}],
797
+ [{content: 'Multirow cell', rowspan: 3}, 'Line 1'],
798
+ ] + (2..3).map { |i| ["Line #{i}"] }, :header => true
799
+
800
+ output = PDF::Inspector::Page.analyze(pdf.render)
801
+ # Ensure we only drew the header once, on the second page
802
+ expect(output.pages[0][:strings]).to eq []
803
+ expect(output.pages[1][:strings]).to eq ['Header', 'Multirow cell', 'Line 1',
804
+ 'Line 2', 'Line 3']
805
+ end
806
+
807
+ context 'when the last row of first page of a table has a rowspan > 1' do
808
+ it 'should move the cells below that rowspan cell to the next page' do
809
+ pdf = Prawn::Document.new
810
+
811
+ pdf.y = 100 # not enough room for the rowspan cell
812
+ pdf.table [
813
+ ['R0C0', 'R0C1', 'R0C2'],
814
+ ['R1C0', {content: 'R1C1', rowspan: 2}, 'R1C2'],
815
+ ['R2C0', 'R2C2'],
816
+ ]
817
+
818
+ output = PDF::Inspector::Page.analyze(pdf.render)
819
+ # Ensure we output the cells of row 2 on the new page only
820
+ expect(output.pages[0][:strings]).to eq ['R0C0', 'R0C1', 'R0C2']
821
+ expect(output.pages[1][:strings]).to eq ['R1C0', 'R1C1', 'R1C2', 'R2C0', 'R2C2']
822
+ end
823
+ end
824
+
825
+ it "should draw background before borders, but only within pages" do
826
+ @pdf = Prawn::Document.new
827
+
828
+ # give enough room for only the first row
829
+ @pdf.y = @pdf.bounds.absolute_bottom + 30
830
+ t = @pdf.make_table([["A", "B"],
831
+ ["C", "D"]],
832
+ :cell_style => {:background_color => 'ff0000'})
833
+
834
+ ca = t.cells[0, 0]
835
+ cb = t.cells[0, 1]
836
+ cc = t.cells[1, 0]
837
+ cd = t.cells[1, 1]
838
+
839
+ # All backgrounds should draw before any borders on page 1...
840
+ expect(ca).to receive(:draw_background).ordered
841
+ expect(cb).to receive(:draw_background).ordered
842
+ expect(ca).to receive(:draw_borders).ordered
843
+ expect(cb).to receive(:draw_borders).ordered
844
+ # ...and page 2
845
+ expect(@pdf).to receive(:start_new_page).ordered
846
+ expect(cc).to receive(:draw_background).ordered
847
+ expect(cd).to receive(:draw_background).ordered
848
+ expect(cc).to receive(:draw_borders).ordered
849
+ expect(cd).to receive(:draw_borders).ordered
850
+
851
+ t.draw
852
+ end
853
+
854
+ describe "before_rendering_page callback" do
855
+ before(:each) { @pdf = Prawn::Document.new }
856
+
857
+ it "is passed all cells to be rendered on that page" do
858
+ kicked = 0
859
+
860
+ @pdf.table([["foo"]] * 100) do |t|
861
+ t.before_rendering_page do |page|
862
+ expect(page.row_count).to eq ((kicked < 3) ? 30 : 10)
863
+ expect(page.column_count).to eq 1
864
+ expect(page.row(0).first.content).to eq "foo"
865
+ expect(page.row(-1).first.content).to eq "foo"
866
+ kicked += 1
867
+ end
868
+ end
869
+
870
+ expect(kicked).to eq 4
871
+ end
872
+
873
+ it "numbers cells relative to their position on page" do
874
+ @pdf.table([["foo"]] * 100) do |t|
875
+ t.before_rendering_page do |page|
876
+ expect(page[0, 0].content).to eq "foo"
877
+ end
878
+ end
879
+ end
880
+
881
+ it "changing cells in the callback affects their rendering" do
882
+ t = @pdf.make_table([["foo"]] * 40) do |table|
883
+ table.before_rendering_page do |page|
884
+ page[0, 0].background_color = "ff0000"
885
+ end
886
+ end
887
+
888
+ expect(t.cells[30, 0]).to receive(:draw_background)
889
+ .and_wrap_original do |original_method, *args, &block|
890
+ expect(t.cells[30, 0].background_color).to eq 'ff0000'
891
+ original_method.call(*args, &block)
892
+ end
893
+
894
+ expect(t.cells[31, 0]).to receive(:draw_background)
895
+ .and_wrap_original do |original_method, *args, &block|
896
+ expect(t.cells[31, 0].background_color).to eq nil
897
+ original_method.call(*args, &block)
898
+ end
899
+
900
+ t.draw
901
+ end
902
+
903
+ it "passes headers on page 2+" do
904
+ @pdf.table([["header"]] + [["foo"]] * 100, :header => true) do |t|
905
+ t.before_rendering_page do |page|
906
+ expect(page[0, 0].content).to eq "header"
907
+ end
908
+ end
909
+ end
910
+
911
+ it "updates dummy cell header rows" do
912
+ header = [[{:content => "header", :colspan => 2}]]
913
+ data = [["foo", "bar"]] * 31
914
+ @pdf.table(header + data, :header => true) do |t|
915
+ t.before_rendering_page do |page|
916
+ cell = page[0, 0]
917
+ cell.dummy_cells.each {|dc| expect(dc.row).to eq cell.row }
918
+ end
919
+ end
920
+ end
921
+
922
+ it "allows headers to be changed" do
923
+ expect(@pdf).to receive(:draw_text!).with("hdr1", anything).ordered
924
+ expect(@pdf).to receive(:draw_text!).with("foo", anything).exactly(29).times.ordered
925
+ # Verify that the changed cell doesn't mutate subsequent pages
926
+ expect(@pdf).to receive(:draw_text!).with("header", anything).ordered
927
+ expect(@pdf).to receive(:draw_text!).with("foo", anything).exactly(11).times.ordered
928
+
929
+ set_first_page_headers = false
930
+ @pdf.table([["header"]] + [["foo"]] * 40, :header => true) do |t|
931
+ t.before_rendering_page do |page|
932
+ # only change first page header
933
+ page[0, 0].content = "hdr1" unless set_first_page_headers
934
+ set_first_page_headers = true
935
+ end
936
+ end
937
+ end
938
+ end
939
+ end
940
+
941
+ describe "#style" do
942
+ it "should send #style to its first argument, passing the style hash and" +
943
+ " block" do
944
+
945
+ stylable = double
946
+ expect(stylable).to receive(:style).with({:foo => :bar}).once.and_yield
947
+
948
+ block = double
949
+ expect(block).to receive(:kick).once
950
+
951
+ Prawn::Document.new do
952
+ table([["x"]]) do
953
+ style(stylable, {:foo => :bar}) { block.kick }
954
+ end
955
+ end
956
+ end
957
+
958
+ it "should default to {} for the hash argument" do
959
+ stylable = double
960
+ expect(stylable).to receive(:style).with({}).once
961
+
962
+ Prawn::Document.new do
963
+ table([["x"]]) { style(stylable) }
964
+ end
965
+ end
966
+
967
+ it "ignores unknown values on a cell-by-cell basis" do
968
+ Prawn::Document.new do
969
+ table([["x", [["y"]]]], :cell_style => {:overflow => :shrink_to_fit})
970
+ end
971
+ end
972
+ end
973
+
974
+ describe "row_colors" do
975
+ it "should allow array syntax for :row_colors" do
976
+ data = [["foo"], ["bar"], ["baz"]]
977
+ pdf = Prawn::Document.new
978
+ t = pdf.table(data, :row_colors => ['cccccc', 'ffffff'])
979
+ expect(t.cells.map{|x| x.background_color}).to eq %w[cccccc ffffff cccccc]
980
+ end
981
+
982
+ it "should ignore headers" do
983
+ data = [["header"], ["foo"], ["bar"], ["baz"]]
984
+ pdf = Prawn::Document.new
985
+ t = pdf.table(data,
986
+ { :header => true,
987
+ :row_colors => ['cccccc', 'ffffff']}) do
988
+ row(0).background_color = '333333'
989
+ end
990
+
991
+ expect(t.cells.map{|x| x.background_color}).to eq %w[333333 cccccc ffffff cccccc]
992
+ end
993
+
994
+ it "stripes rows consistently from page to page, skipping header rows" do
995
+ data = [["header"]] + [["foo"]] * 70
996
+ pdf = Prawn::Document.new
997
+ t = pdf.make_table(data,
998
+ { :header => true,
999
+ :row_colors => ['cccccc', 'ffffff']}) do
1000
+ cells.padding = 0
1001
+ cells.size = 9
1002
+ row(0).size = 11
1003
+ end
1004
+
1005
+ # page 1: header + 67 cells (odd number -- verifies that the next
1006
+ # page disrupts the even/odd coloring, since both the last data cell
1007
+ # on this page and the first one on the next are colored cccccc)
1008
+ expect(Prawn::Table::Cell).to receive(:draw_cells)
1009
+ .and_wrap_original do |original_method, *args, &block|
1010
+ cells = args.first
1011
+ expect(cells.map { |c, _| c.background_color }).to eq [nil] + (%w[cccccc ffffff] * 33) + %w[cccccc]
1012
+ original_method.call(*args, &block)
1013
+ end
1014
+ # page 2: header and 3 data cells
1015
+ expect(Prawn::Table::Cell).to receive(:draw_cells)
1016
+ .and_wrap_original do |original_method, *args, &block|
1017
+ cells = args.first
1018
+ expect(cells.map { |c, _| c.background_color }).to eq [nil] + %w[cccccc ffffff cccccc]
1019
+ original_method.call(*args, &block)
1020
+ end
1021
+
1022
+ t.draw
1023
+ end
1024
+
1025
+ it "should_not override an explicit background_color" do
1026
+ data = [["foo"], ["bar"], ["baz"]]
1027
+ pdf = Prawn::Document.new
1028
+ table = pdf.table(data, :row_colors => ['cccccc', 'ffffff']) { |t|
1029
+ t.cells[0, 0].background_color = 'dddddd'
1030
+ }
1031
+ expect(table.cells.map{|x| x.background_color}).to eq %w[dddddd ffffff cccccc]
1032
+ end
1033
+ end
1034
+
1035
+ describe "inking" do
1036
+ before(:each) do
1037
+ @pdf = Prawn::Document.new
1038
+ end
1039
+
1040
+ it "should set the x-position of each cell based on widths" do
1041
+ @table = @pdf.table([["foo", "bar", "baz"]])
1042
+
1043
+ x = 0
1044
+ (0..2).each do |col|
1045
+ cell = @table.cells[0, col]
1046
+ expect(cell.x).to eq x
1047
+ x += cell.width
1048
+ end
1049
+ end
1050
+
1051
+ it "should set the y-position of each cell based on heights" do
1052
+ y = 0
1053
+ @table = @pdf.make_table([["foo"], ["bar"], ["baz"]])
1054
+
1055
+ (0..2).each do |row|
1056
+ cell = @table.cells[row, 0]
1057
+ expect(cell.y).to be_within(0.01).of(y)
1058
+ y -= cell.height
1059
+ end
1060
+ end
1061
+
1062
+ it "should output content cell by cell, row by row" do
1063
+ data = [["foo","bar"],["baz","bang"]]
1064
+ @pdf = Prawn::Document.new
1065
+ @pdf.table(data)
1066
+ output = PDF::Inspector::Text.analyze(@pdf.render)
1067
+ expect(output.strings).to eq data.flatten
1068
+ end
1069
+
1070
+ it "should_not cause an error if rendering the very first row causes a " +
1071
+ "page break" do
1072
+ Prawn::Document.new do |pdf|
1073
+ arr = Array(1..5).collect{|i| ["cell #{i}"] }
1074
+
1075
+ pdf.move_down( pdf.y - (pdf.bounds.absolute_bottom + 3) )
1076
+
1077
+ expect {
1078
+ pdf.table(arr)
1079
+ }.to_not raise_error
1080
+ end
1081
+ end
1082
+
1083
+ it "should draw all backgrounds before any borders" do
1084
+ # lest backgrounds overlap borders:
1085
+ # https://github.com/sandal/prawn/pull/226
1086
+
1087
+ t = @pdf.make_table([["A", "B"]],
1088
+ :cell_style => {:background_color => 'ff0000'})
1089
+ ca = t.cells[0, 0]
1090
+ cb = t.cells[0, 1]
1091
+
1092
+ # XXX Not a perfectly general test, because it would still be acceptable
1093
+ # if we drew B then A
1094
+ expect(ca).to receive(:draw_background).ordered
1095
+ expect(cb).to receive(:draw_background).ordered
1096
+ expect(ca).to receive(:draw_borders).ordered
1097
+ expect(cb).to receive(:draw_borders).ordered
1098
+
1099
+ t.draw
1100
+ end
1101
+
1102
+ it "should allow multiple inkings of the same table" do
1103
+ pdf = Prawn::Document.new
1104
+ t = Prawn::Table.new([["foo"]], pdf)
1105
+
1106
+ expect(pdf).to receive(:bounding_box).with([anything, 495], kind_of(Hash)).and_yield
1107
+ expect(pdf).to receive(:bounding_box).with([anything, 395], kind_of(Hash)).and_yield
1108
+ expect(pdf).to receive(:draw_text!).with("foo", anything).twice
1109
+
1110
+ pdf.move_cursor_to(500)
1111
+ t.draw
1112
+
1113
+ pdf.move_cursor_to(400)
1114
+ t.draw
1115
+ end
1116
+
1117
+ describe "in stretchy bounding boxes" do
1118
+ it "should draw all cells on a row at the same y-position" do
1119
+ pdf = Prawn::Document.new
1120
+
1121
+ text_y = pdf.y.to_i - 5 # text starts 5pt below current y pos (padding)
1122
+
1123
+ pdf.bounding_box([0, pdf.cursor], :width => pdf.bounds.width) do
1124
+ expect(pdf).to receive(:draw_text!).exactly(3).times
1125
+ .and_wrap_original do |original_method, *args, &block|
1126
+ expect(pdf.bounds.absolute_top).to eq text_y
1127
+ original_method.call(*args, &block)
1128
+ end
1129
+
1130
+ pdf.table([%w[a b c]])
1131
+ end
1132
+ end
1133
+ end
1134
+ end
1135
+
1136
+ describe "headers" do
1137
+ context "single row header" do
1138
+ it "should add headers to output when specified" do
1139
+ data = [["a", "b"], ["foo","bar"],["baz","bang"]]
1140
+ @pdf = Prawn::Document.new
1141
+ @pdf.table(data, :header => true)
1142
+ output = PDF::Inspector::Text.analyze(@pdf.render)
1143
+ expect(output.strings).to eq data.flatten
1144
+ end
1145
+
1146
+ it "should repeat headers across pages" do
1147
+ data = [["foo","bar"]] * 30
1148
+ headers = ["baz","foobar"]
1149
+ @pdf = Prawn::Document.new
1150
+ @pdf.table([headers] + data, :header => true)
1151
+ output = PDF::Inspector::Text.analyze(@pdf.render)
1152
+ expect(output.strings).to eq headers + data.flatten[0..-3] + headers +
1153
+ data.flatten[-2..-1]
1154
+ end
1155
+
1156
+ it "draws headers at the correct position" do
1157
+ data = [["header"]] + [["foo"]] * 40
1158
+
1159
+ expect(Prawn::Table::Cell).to receive(:draw_cells).twice
1160
+ .and_wrap_original do |original_method, *args, &block|
1161
+ cells = args.first
1162
+ cells.each do |cell, pt|
1163
+ if cell.content == "header"
1164
+ # Assert that header text is drawn at the same location on each page
1165
+ if @header_location
1166
+ expect(pt).to eq @header_location
1167
+ else
1168
+ @header_location = pt
1169
+ end
1170
+ end
1171
+ end
1172
+ original_method.call(*args, &block)
1173
+ end
1174
+ @pdf = Prawn::Document.new
1175
+ @pdf.table(data, :header => true)
1176
+ end
1177
+
1178
+ it "draws headers at the correct position with column box" do
1179
+ data = [["header"]] + [["foo"]] * 40
1180
+
1181
+ expect(Prawn::Table::Cell).to receive(:draw_cells).twice
1182
+ .and_wrap_original do |original_method, *args, &block|
1183
+ cells = args.first
1184
+ cells.each do |cell, pt|
1185
+ if cell.content == "header"
1186
+ expect(pt[0]).to eq @pdf.bounds.left
1187
+ end
1188
+ end
1189
+ original_method.call(*args, &block)
1190
+ end
1191
+ @pdf = Prawn::Document.new
1192
+ @pdf.column_box [0, @pdf.cursor], :width => @pdf.bounds.width, :columns => 2 do
1193
+ @pdf.table(data, :header => true)
1194
+ end
1195
+ end
1196
+
1197
+ it "should_not draw header twice when starting new page" do
1198
+ @pdf = Prawn::Document.new
1199
+ @pdf.y = 0
1200
+ @pdf.table([["Header"], ["Body"]], :header => true)
1201
+ output = PDF::Inspector::Text.analyze(@pdf.render)
1202
+ expect(output.strings).to eq ["Header", "Body"]
1203
+ end
1204
+ end
1205
+
1206
+ context "multiple row header" do
1207
+ it "should add headers to output when specified" do
1208
+ data = [["a", "b"], ["c", "d"], ["foo","bar"],["baz","bang"]]
1209
+ @pdf = Prawn::Document.new
1210
+ @pdf.table(data, :header => 2)
1211
+ output = PDF::Inspector::Text.analyze(@pdf.render)
1212
+ expect(output.strings).to eq data.flatten
1213
+ end
1214
+
1215
+ it "should repeat headers across pages" do
1216
+ data = [["foo","bar"]] * 30
1217
+ headers = ["baz","foobar"] + ["bas", "foobaz"]
1218
+ @pdf = Prawn::Document.new
1219
+ @pdf.table([headers] + data, :header => 2)
1220
+ output = PDF::Inspector::Text.analyze(@pdf.render)
1221
+ expect(output.strings).to eq headers + data.flatten[0..-3] + headers +
1222
+ data.flatten[-4..-1]
1223
+ end
1224
+
1225
+ it "draws headers at the correct position" do
1226
+ data = [["header"]] + [["header2"]] + [["foo"]] * 40
1227
+
1228
+ expect(Prawn::Table::Cell).to receive(:draw_cells).twice
1229
+ .and_wrap_original do |original_method, *args, &block|
1230
+ cells = args.first
1231
+ cells.each do |cell, pt|
1232
+ if cell.content == "header"
1233
+ # Assert that header text is drawn at the same location on each page
1234
+ if @header_location
1235
+ expect(pt).to eq @header_location
1236
+ else
1237
+ @header_location = pt
1238
+ end
1239
+ end
1240
+
1241
+ if cell.content == "header2"
1242
+ # Assert that header text is drawn at the same location on each page
1243
+ if @header2_location
1244
+ expect(pt).to eq @header2_location
1245
+ else
1246
+ @header2_location = pt
1247
+ end
1248
+ end
1249
+ end
1250
+
1251
+ original_method.call(*args, &block)
1252
+ end
1253
+
1254
+ @pdf = Prawn::Document.new
1255
+ @pdf.table(data, :header => 2)
1256
+ end
1257
+
1258
+ it "should_not draw header twice when starting new page" do
1259
+ @pdf = Prawn::Document.new
1260
+ @pdf.y = 0
1261
+ @pdf.table([["Header"], ["Header2"], ["Body"]], :header => 2)
1262
+ output = PDF::Inspector::Text.analyze(@pdf.render)
1263
+ expect(output.strings).to eq ["Header", "Header2", "Body"]
1264
+ end
1265
+ end
1266
+ end
1267
+
1268
+ describe "nested tables" do
1269
+ before(:each) do
1270
+ @pdf = Prawn::Document.new
1271
+ @subtable = Prawn::Table.new([["foo"]], @pdf)
1272
+ @table = @pdf.table([[@subtable, "bar"], ['', { content: @subtable, padding: 10 }]])
1273
+ end
1274
+
1275
+ it "can be created from an Array" do
1276
+ cell = Prawn::Table::Cell.make(@pdf, [["foo"]])
1277
+ expect(cell).to be_a_kind_of(Prawn::Table::Cell::Subtable)
1278
+ expect(cell.subtable).to be_a_kind_of(Prawn::Table)
1279
+ end
1280
+
1281
+ it "defaults its padding to zero" do
1282
+ expect(@table.cells[0, 0].padding).to eq [0, 0, 0, 0]
1283
+ end
1284
+
1285
+ it "has a subtable accessor" do
1286
+ expect(@table.cells[0, 0].subtable).to eq @subtable
1287
+ end
1288
+
1289
+ it "determines its dimensions from the subtable" do
1290
+ expect(@table.cells[0, 0].width).to eq @subtable.width
1291
+ expect(@table.cells[0, 0].height).to eq @subtable.height
1292
+ end
1293
+
1294
+ it "pads the holding cell with padding options" do
1295
+ expect(@table.cells[1, 1].padding).to eq [10, 10, 10, 10]
1296
+ end
1297
+ end
1298
+
1299
+ it "Prints table on one page when using subtable with colspan > 1", :unresolved, issue: 10 do
1300
+ pdf = Prawn::Document.new(margin: [ 30, 71, 55, 71])
1301
+
1302
+ lines = "one\ntwo\nthree\nfour"
1303
+
1304
+ sub_table_lines = lines.split("\n").map do |line|
1305
+ if line == "one"
1306
+ [ { content: "#{line}", colspan: 2, size: 11} ]
1307
+ else
1308
+ [ { content: "\u2022"}, { content: "#{line}"} ]
1309
+ end
1310
+ end
1311
+
1312
+ sub_table = pdf.make_table(sub_table_lines,
1313
+ cell_style: { border_color: '00ff00'})
1314
+
1315
+ #outer table
1316
+ pdf.table [[
1317
+ { content: "Placeholder text", width: 200 },
1318
+ { content: sub_table }
1319
+ ]], width: 515, cell_style: { border_width: 1, border_color: 'ff0000' }
1320
+
1321
+ pdf.render
1322
+ expect(pdf.page_count).to eq 1
1323
+ end
1324
+
1325
+ describe "An invalid table" do
1326
+
1327
+ before(:each) do
1328
+ @pdf = Prawn::Document.new
1329
+ @bad_data = ["Single Nested Array"]
1330
+ end
1331
+
1332
+ it "should raise_error error when invalid table data is given" do
1333
+ expect {
1334
+ @pdf.table(@bad_data)
1335
+ }.to raise_error(Prawn::Errors::InvalidTableData)
1336
+ end
1337
+
1338
+ it "should raise_error an EmptyTableError with empty table data" do
1339
+ expect {
1340
+ data = []
1341
+ @pdf = Prawn::Document.new
1342
+ @pdf.table(data)
1343
+ }.to raise_error( Prawn::Errors::EmptyTable )
1344
+ end
1345
+
1346
+ it "should raise_error an EmptyTableError with nil table data" do
1347
+ expect {
1348
+ data = nil
1349
+ @pdf = Prawn::Document.new
1350
+ @pdf.table(data)
1351
+ }.to raise_error( Prawn::Errors::EmptyTable )
1352
+ end
1353
+
1354
+ end
1355
+
1356
+ end
1357
+
1358
+ describe "colspan / rowspan" do
1359
+ before(:each) { create_pdf }
1360
+
1361
+ it "doesn't raise an error" do
1362
+ expect {
1363
+ @pdf.table([[{:content => "foo", :colspan => 2, :rowspan => 2}]])
1364
+ }.to_not raise_error
1365
+ end
1366
+
1367
+ it "colspan is properly counted" do
1368
+ t = @pdf.make_table([[{:content => "foo", :colspan => 2}]])
1369
+ expect(t.column_length).to eq 2
1370
+ end
1371
+
1372
+ it "rowspan is properly counted" do
1373
+ t = @pdf.make_table([[{:content => "foo", :rowspan => 2}]])
1374
+ expect(t.row_length).to eq 2
1375
+ end
1376
+
1377
+ it "raises if colspan or rowspan are called after layout" do
1378
+ expect {
1379
+ @pdf.table([["foo"]]) { cells[0, 0].colspan = 2 }
1380
+ }.to raise_error(Prawn::Errors::InvalidTableSpan)
1381
+
1382
+ expect {
1383
+ @pdf.table([["foo"]]) { cells[0, 0].rowspan = 2 }
1384
+ }.to raise_error(Prawn::Errors::InvalidTableSpan)
1385
+ end
1386
+
1387
+ it "raises when spans overlap" do
1388
+ expect {
1389
+ @pdf.table([["foo", {:content => "bar", :rowspan => 2}],
1390
+ [{:content => "baz", :colspan => 2}]])
1391
+ }.to raise_error(Prawn::Errors::InvalidTableSpan)
1392
+ end
1393
+
1394
+ it "table and cell width account for colspan" do
1395
+ t = @pdf.table([["a", {:content => "b", :colspan => 2}]],
1396
+ :column_widths => [100, 100, 100])
1397
+ spanned = t.cells[0, 1]
1398
+ expect(spanned.colspan).to eq 2
1399
+ expect(t.width).to eq 300
1400
+ expect(t.cells.min_width).to eq 300
1401
+ expect(t.cells.max_width).to eq 300
1402
+ expect(spanned.width).to eq 200
1403
+ end
1404
+
1405
+ it "table and cell height account for rowspan" do
1406
+ t = @pdf.table([["a"], [{:content => "b", :rowspan => 2}]]) do
1407
+ row(0..2).height = 100
1408
+ end
1409
+ spanned = t.cells[1, 0]
1410
+ expect(spanned.rowspan).to eq 2
1411
+ expect(t.height).to eq 300
1412
+ expect(spanned.height).to eq 200
1413
+ end
1414
+
1415
+ it "provides the full content_width as drawing space" do
1416
+ w = @pdf.make_table([["foo"]]).cells[0, 0].content_width
1417
+
1418
+ t = @pdf.make_table([[{:content => "foo", :colspan => 2}]])
1419
+ expect(t.cells[0, 0].spanned_content_width).to eq w
1420
+ end
1421
+
1422
+ it "dummy cells are not drawn" do
1423
+ # make a fake master cell for the dummy cell to slave to
1424
+ t = @pdf.make_table([[{:content => "foo", :colspan => 2}]])
1425
+
1426
+ # drawing just a dummy cell should_not ink
1427
+ expect(@pdf).to_not receive(:stroke_line)
1428
+ expect(@pdf).to_not receive(:draw_text!)
1429
+ Prawn::Table::Cell.draw_cells([t.cells[0, 1]])
1430
+ end
1431
+
1432
+ it "dummy cells do not add any height or width" do
1433
+ t1 = @pdf.table([["foo"]])
1434
+
1435
+ t2 = @pdf.table([[{:content => "foo", :colspan => 2}]])
1436
+ expect(t2.width).to eq t1.width
1437
+
1438
+ t3 = @pdf.table([[{:content => "foo", :rowspan => 2}]])
1439
+ expect(t3.height).to eq t1.height
1440
+ end
1441
+
1442
+ it "dummy cells ignored by #style" do
1443
+ t = @pdf.table([[{:content => "blah", :colspan => 2}]],
1444
+ :cell_style => { :size => 9 })
1445
+ expect(t.cells[0, 0].size).to eq 9
1446
+ end
1447
+
1448
+ context "inheriting master cell styles from dummy cell" do
1449
+ # Relatively full coverage for all these attributes that should be
1450
+ # inherited.
1451
+ [["border_X_width", 20],
1452
+ ["border_X_color", "123456"],
1453
+ ["padding_X", 20]].each do |attribute, val|
1454
+ attribute_right = attribute.sub("X", "right")
1455
+ attribute_left = attribute.sub("X", "left")
1456
+ attribute_bottom = attribute.sub("X", "bottom")
1457
+ attribute_top = attribute.sub("X", "top")
1458
+
1459
+ specify "#{attribute_right} of right column is inherited" do
1460
+ t = @pdf.table([[{:content => "blah", :colspan => 2}]]) do |table|
1461
+ table.column(1).send("#{attribute_right}=", val)
1462
+ end
1463
+
1464
+ expect(t.cells[0, 0].send(attribute_right)).to eq val
1465
+ end
1466
+
1467
+ specify "#{attribute_bottom} of bottom row is inherited" do
1468
+ t = @pdf.table([[{:content => "blah", :rowspan => 2}]]) do |table|
1469
+ table.row(1).send("#{attribute_bottom}=", val)
1470
+ end
1471
+
1472
+ expect(t.cells[0, 0].send(attribute_bottom)).to eq val
1473
+ end
1474
+
1475
+ specify "#{attribute_left} of right column is not inherited" do
1476
+ t = @pdf.table([[{:content => "blah", :colspan => 2}]]) do |table|
1477
+ table.column(1).send("#{attribute_left}=", val)
1478
+ end
1479
+
1480
+ expect(t.cells[0, 0].send(attribute_left)).to_not eq val
1481
+ end
1482
+
1483
+ specify "#{attribute_right} of interior column is not inherited" do
1484
+ t = @pdf.table([[{:content => "blah", :colspan => 3}]]) do |table|
1485
+ table.column(1).send("#{attribute_right}=", val)
1486
+ end
1487
+
1488
+ expect(t.cells[0, 0].send(attribute_right)).to_not eq val
1489
+ end
1490
+
1491
+ specify "#{attribute_bottom} of interior row is not inherited" do
1492
+ t = @pdf.table([[{:content => "blah", :rowspan => 3}]]) do |table|
1493
+ table.row(1).send("#{attribute_bottom}=", val)
1494
+ end
1495
+
1496
+ expect(t.cells[0, 0].send(attribute_bottom)).to_not eq val
1497
+ end
1498
+
1499
+ specify "#{attribute_top} of bottom row is not inherited" do
1500
+ t = @pdf.table([[{:content => "blah", :rowspan => 2}]]) do |table|
1501
+ table.row(1).send("#{attribute_top}=", val)
1502
+ end
1503
+
1504
+ expect(t.cells[0, 0].send(attribute_top)).to_not eq val
1505
+ end
1506
+ end
1507
+ end
1508
+
1509
+ it "splits natural width between cols in the group" do
1510
+ t = @pdf.table([[{:content => "foo", :colspan => 2}]])
1511
+ widths = t.column_widths
1512
+ expect(widths[0]).to eq widths[1]
1513
+ end
1514
+
1515
+ it "splits natural width between cols when width is increased" do
1516
+ t = @pdf.table([[{:content => "foo", :colspan => 2}]],
1517
+ :width => @pdf.bounds.width)
1518
+ widths = t.column_widths
1519
+ expect(widths[0]).to eq widths[1]
1520
+ end
1521
+
1522
+ it "splits min-width between cols in the group" do
1523
+ # Since column_widths, when reducing column widths, reduces proportional to
1524
+ # the remaining width after each column's min width, we must ensure that the
1525
+ # min-width is split proportionally in order to ensure the width is still
1526
+ # split evenly when the width is reduced. (See "splits natural width between
1527
+ # cols when width is reduced".)
1528
+ t = @pdf.table([[{:content => "foo", :colspan => 2}]],
1529
+ :width => 20)
1530
+ expect(t.column(0).min_width).to eq t.column(1).min_width
1531
+ end
1532
+
1533
+ it "splits natural width between cols when width is reduced" do
1534
+ t = @pdf.table([[{:content => "foo", :colspan => 2}]],
1535
+ :width => 20)
1536
+ widths = t.column_widths
1537
+ expect(widths[0]).to eq widths[1]
1538
+ end
1539
+
1540
+ it "honors a large, explicitly set table width" do
1541
+ t = @pdf.table([[{:content => "AAAAAAAAAA", :colspan => 3}],
1542
+ ["A", "B", "C"]],
1543
+ :width => 400)
1544
+
1545
+ expect(t.column_widths.inject(0) { |sum, w| sum + w }).to be_within(0.01).of(400)
1546
+ end
1547
+
1548
+ it "honors a small, explicitly set table width" do
1549
+ t = @pdf.table([[{:content => "Lorem ipsum dolor sit amet " * 20,
1550
+ :colspan => 3}],
1551
+ ["A", "B", "C"]],
1552
+ :width => 200)
1553
+ expect(t.column_widths.inject(0) { |sum, w| sum + w }).to be_within(0.01).of(200)
1554
+ end
1555
+
1556
+ it "splits natural_content_height between rows in the group" do
1557
+ t = @pdf.table([[{:content => "foo", :rowspan => 2}]])
1558
+ heights = t.row_heights
1559
+ expect(heights[0]).to eq heights[1]
1560
+ end
1561
+
1562
+ it "skips column numbers that have been col-spanned" do
1563
+ t = @pdf.table([["a", "b", {:content => "c", :colspan => 3}, "d"]])
1564
+ expect(t.cells[0, 0].content).to eq "a"
1565
+ expect(t.cells[0, 1].content).to eq "b"
1566
+ expect(t.cells[0, 2].content).to eq "c"
1567
+ expect(t.cells[0, 3]).to be_a_kind_of(Prawn::Table::Cell::SpanDummy)
1568
+ expect(t.cells[0, 4]).to be_a_kind_of(Prawn::Table::Cell::SpanDummy)
1569
+ expect(t.cells[0, 5].content).to eq "d"
1570
+ end
1571
+
1572
+ it "skips row/col positions that have been row-spanned" do
1573
+ t = @pdf.table([["a", {:content => "b", :colspan => 2, :rowspan => 2}, "c"],
1574
+ ["d", "e"],
1575
+ ["f", "g", "h", "i"]])
1576
+ expect(t.cells[0, 0].content).to eq "a"
1577
+ expect(t.cells[0, 1].content).to eq "b"
1578
+ expect(t.cells[0, 2]).to be_a_kind_of(Prawn::Table::Cell::SpanDummy)
1579
+ expect(t.cells[0, 3].content).to eq "c"
1580
+
1581
+ expect(t.cells[1, 0].content).to eq "d"
1582
+ expect(t.cells[1, 1]).to be_a_kind_of(Prawn::Table::Cell::SpanDummy)
1583
+ expect(t.cells[1, 2]).to be_a_kind_of(Prawn::Table::Cell::SpanDummy)
1584
+ expect(t.cells[1, 3].content).to eq "e"
1585
+
1586
+ expect(t.cells[2, 0].content).to eq "f"
1587
+ expect(t.cells[2, 1].content).to eq "g"
1588
+ expect(t.cells[2, 2].content).to eq "h"
1589
+ expect(t.cells[2, 3].content).to eq "i"
1590
+ end
1591
+
1592
+ it 'illustrates issue #20', issue: 20 do
1593
+ pdf = Prawn::Document.new
1594
+ description = "one\ntwo\nthree"
1595
+ bullets = description.split("\n")
1596
+ bullets.each_with_index do |bullet, ndx|
1597
+ rows = [[]]
1598
+
1599
+ if ndx < 1
1600
+ rows << [ { content: "blah blah blah", colspan: 2, font_style: :bold, size: 12, padding_bottom: 1 }]
1601
+ else
1602
+ rows << [ { content: bullet, width: 440, padding_top: 0, align: :justify } ]
1603
+ end
1604
+ pdf.table(rows, header: true, cell_style: { border_width: 0, inline_format: true })
1605
+ end
1606
+ pdf.render
1607
+ end
1608
+
1609
+ it 'illustrates issue #20 (2) and #22', issue: 22 do
1610
+ pdf = Prawn::Document.new
1611
+ pdf.table [['one', 'two']], position: :center
1612
+ pdf.table [['three', 'four']], position: :center
1613
+ pdf.render
1614
+ expect(pdf.page_count).to eq 1
1615
+ end
1616
+
1617
+ it 'illustrates issue #56 cell style should not be overwritten by table style', issue: 56 do
1618
+ t = @pdf.table([['col1', 'col2'],
1619
+ ['val1', { content: 'val2', align: :left }]],
1620
+ cell_style: { align: :center })
1621
+ expect(t.cells[0, 0].align).to eq :center
1622
+ expect(t.cells[0, 1].align).to eq :center
1623
+ expect(t.cells[1, 0].align).to eq :center
1624
+ expect(t.cells[1, 1].align).to eq :left
1625
+ end
1626
+ end