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,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