prawn-table 0.0.1

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