prawn-flexible-table 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.
@@ -0,0 +1,267 @@
1
+ module Prawn
2
+
3
+ class Document
4
+ # Builds and renders a FlexibleTable::Cell. A cell is essentially a
5
+ # special-purpose bounding box designed for flowing text within a bordered
6
+ # area. For available options, see FlexibleTable::Cell#new.
7
+ #
8
+ # Prawn::Document.generate("cell.pdf") do
9
+ # f_cell [100,500],
10
+ # :width => 200,
11
+ # :text => "The rain in Spain falls mainly on the plains"
12
+ # end
13
+ #
14
+ def f_cell(point, options={})
15
+ Prawn::FlexibleTable::Cell.new(
16
+ options.merge(:document => self, :point => point)).draw
17
+ end
18
+ end
19
+
20
+ class FlexibleTable
21
+ # A cell is a special-purpose bounding box designed to flow text within a
22
+ # bordered area. This is used by Prawn's Document::FlexibleTable implementation but
23
+ # can also be used standalone for drawing text boxes via Document#f_cell
24
+ class Cell
25
+
26
+ # Creates a new cell object. Generally used indirectly via Document#f_cell
27
+ #
28
+ # Of the available options listed below, <tt>:point</tt>, <tt>:width</tt>,
29
+ # and <tt>:text</tt> must be provided. If you are not using the
30
+ # Document#f_cell shortcut, the <tt>:document</tt> must also be provided.
31
+ #
32
+ # <tt>:point</tt>:: Absolute [x,y] coordinate of the top-left corner of the cell.
33
+ # <tt>:document</tt>:: The Prawn::Document object to render on.
34
+ # <tt>:text</tt>:: The text to be flowed within the cell
35
+ # <tt>:text_color</tt>:: The color of the text to be displayed
36
+ # <tt>:width</tt>:: The width in PDF points of the cell.
37
+ # <tt>:height</tt>:: The height in PDF points of the cell.
38
+ # <tt>:horizontal_padding</tt>:: The horizontal padding in PDF points
39
+ # <tt>:vertical_padding</tt>:: The vertical padding in PDF points
40
+ # <tt>:padding</tt>:: Overrides both horizontal and vertical padding
41
+ # <tt>:align</tt>:: One of <tt>:left</tt>, <tt>:right</tt>, <tt>:center</tt>
42
+ # <tt>:borders</tt>:: An array of sides which should have a border. Any of <tt>:top</tt>, <tt>:left</tt>, <tt>:right</tt>, <tt>:bottom</tt>
43
+ # <tt>:border_width</tt>:: The border line width. Defaults to 1.
44
+ # <tt>:border_style</tt>:: One of <tt>:all</tt>, <tt>:no_top</tt>, <tt>:no_bottom</tt>, <tt>:sides</tt>, <tt>:none</tt>, <tt>:bottom_only</tt>. Defaults to :all.
45
+ # <tt>:border_color</tt>:: The color of the cell border.
46
+ # <tt>:font_size</tt>:: The font size for the cell text.
47
+ # <tt>:font_style</tt>:: The font style for the cell text.
48
+ def initialize(options={})
49
+ @point = options[:point]
50
+ @document = options[:document]
51
+ @text = options[:text].to_s
52
+ @text_color = options[:text_color]
53
+ @width = options[:width]
54
+ @height = options[:height]
55
+ @borders = options[:borders]
56
+ @border_width = options[:border_width] || 1
57
+ @border_style = options[:border_style] || :all
58
+ @border_color = options[:border_color]
59
+ @background_color = options[:background_color]
60
+ @align = options[:align] || :left
61
+ @font_size = options[:font_size]
62
+ @font_style = options[:font_style]
63
+
64
+ @horizontal_padding = options[:horizontal_padding] || 0
65
+ @vertical_padding = options[:vertical_padding] || 0
66
+
67
+ if options[:padding]
68
+ @horizontal_padding = @vertical_padding = options[:padding]
69
+ end
70
+
71
+ @rowspan = options[:rowspan] || 1
72
+ @colspan = options[:colspan] || 1
73
+ end
74
+
75
+ attr_accessor :point, :border_style, :border_width, :background_color,
76
+ :document, :horizontal_padding, :vertical_padding, :align,
77
+ :borders, :text_color, :border_color, :font_size, :font_style,
78
+ :rowspan, :colspan
79
+
80
+ attr_writer :height, :width #:nodoc:
81
+
82
+ # Returns the cell's text as a string.
83
+ def to_s
84
+ @text
85
+ end
86
+
87
+ # The width of the text area excluding the horizonal padding
88
+ def text_area_width
89
+ width - 2*@horizontal_padding
90
+ end
91
+
92
+ # The width of the cell in PDF points
93
+ def width
94
+ @width || (@document.width_of(@text, :size => @font_size)) + 2*@horizontal_padding
95
+ end
96
+
97
+ # The height of the cell in PDF points
98
+ def height
99
+ @height || text_area_height + 2*@vertical_padding
100
+ end
101
+
102
+ # The height of the text area excluding the vertical padding
103
+ def text_area_height
104
+ text_height = 0
105
+ if @font_size
106
+ @document.font_size(@font_size) do
107
+ text_height = @document.height_of(@text, text_area_width)
108
+ end
109
+ else
110
+ text_height = @document.height_of(@text, text_area_width)
111
+ end
112
+ text_height
113
+ end
114
+
115
+ # Draws the cell onto the PDF document
116
+ def draw
117
+ rel_point = @point
118
+
119
+ if @background_color
120
+ @document.mask(:fill_color) do
121
+ @document.fill_color @background_color
122
+ h = borders.include?(:bottom) ?
123
+ height - border_width : height + border_width / 2.0
124
+ @document.fill_rectangle [rel_point[0] + border_width / 2.0,
125
+ rel_point[1] - border_width / 2.0 ],
126
+ width - border_width, h
127
+ end
128
+ end
129
+
130
+ if @border_width > 0
131
+ @document.mask(:line_width) do
132
+ @document.line_width = @border_width
133
+
134
+ @document.mask(:stroke_color) do
135
+ @document.stroke_color @border_color if @border_color
136
+
137
+ if borders.include?(:left)
138
+ @document.stroke_line [rel_point[0], rel_point[1] + (@border_width / 2.0)],
139
+ [rel_point[0], rel_point[1] - height - @border_width / 2.0 ]
140
+ end
141
+
142
+ if borders.include?(:right)
143
+ @document.stroke_line(
144
+ [rel_point[0] + width, rel_point[1] + (@border_width / 2.0)],
145
+ [rel_point[0] + width, rel_point[1] - height - @border_width / 2.0] )
146
+ end
147
+
148
+ if borders.include?(:top)
149
+ @document.stroke_line(
150
+ [ rel_point[0] + @border_width / 2.0, rel_point[1] ],
151
+ [ rel_point[0] - @border_width / 2.0 + width, rel_point[1] ])
152
+ end
153
+
154
+ if borders.include?(:bottom)
155
+ @document.stroke_line [rel_point[0], rel_point[1] - height ],
156
+ [rel_point[0] + width, rel_point[1] - height]
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ borders
163
+
164
+ end
165
+
166
+ @document.bounding_box( [@point[0] + @horizontal_padding,
167
+ @point[1] - @vertical_padding],
168
+ :width => text_area_width,
169
+ :height => height - @vertical_padding) do
170
+ @document.move_down((@document.font.line_gap - @document.font.descender)/2)
171
+
172
+ options = {:align => @align, :final_gap => false}
173
+
174
+ options[:size] = @font_size if @font_size
175
+ options[:style] = @font_style if @font_style
176
+
177
+ @document.mask(:fill_color) do
178
+ @document.fill_color @text_color if @text_color
179
+ @document.text @text, options
180
+ end
181
+ end
182
+ end
183
+
184
+ private
185
+
186
+ def borders
187
+ @borders ||= case @border_style
188
+ when :all
189
+ [:top,:left,:right,:bottom]
190
+ when :sides
191
+ [:left,:right]
192
+ when :no_top
193
+ [:left,:right,:bottom]
194
+ when :no_bottom
195
+ [:left,:right,:top]
196
+ when :bottom_only
197
+ [:bottom]
198
+ when :none
199
+ []
200
+ end
201
+ end
202
+ end # class Cell
203
+
204
+ class CellFake < Cell #:nodoc:
205
+ def height
206
+ 0
207
+ end
208
+
209
+ def draw
210
+ end
211
+ end
212
+
213
+ class CellBlock #:nodoc:
214
+ def initialize(document)
215
+ @document = document
216
+ @cells = []
217
+ @width = 0
218
+ @height = 0
219
+ end
220
+
221
+ attr_reader :width, :height, :cells
222
+ attr_accessor :background_color, :text_color, :border_color
223
+
224
+ def <<(cell)
225
+ @cells << cell
226
+ @height = cell.height if cell.height > @height
227
+ @width += cell.width
228
+ self
229
+ end
230
+
231
+ def draw
232
+ y = @document.y
233
+ x = @document.bounds.absolute_left
234
+
235
+ @cells.each do |e|
236
+ e.point = [x - @document.bounds.absolute_left,
237
+ y - @document.bounds.absolute_bottom]
238
+ e.height ||= @height
239
+ e.background_color ||= @background_color
240
+ e.text_color ||= @text_color
241
+ e.border_color ||= @border_color
242
+ e.draw
243
+ x += e.width
244
+ end
245
+
246
+ @document.y = y - @height
247
+ end
248
+
249
+ def border_width
250
+ @cells[0].border_width
251
+ end
252
+
253
+ def border_style=(s)
254
+ @cells.each { |e| e.border_style = s }
255
+ end
256
+
257
+ def align=(align)
258
+ @cells.each { |e| e.align = align }
259
+ end
260
+
261
+ def border_style
262
+ @cells[0].border_style
263
+ end
264
+ end # class CellBlock
265
+
266
+ end # class FlexibleTable
267
+ end # module Prawn
data/spec/grid_spec.rb ADDED
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
3
+
4
+ describe "A document's grid" do
5
+ before do
6
+ @pdf = Prawn::Document.new
7
+ end
8
+
9
+ it "should allow definition of a grid" do
10
+ @pdf.define_grid(:columns => 5, :rows => 8, :gutter => 0.1)
11
+ @pdf.grid.columns.should == 5
12
+ @pdf.grid.rows.should == 8
13
+ @pdf.grid.gutter.should == 0.1
14
+ end
15
+
16
+ describe "when a grid is defined" do
17
+ before do
18
+ @num_columns = 5
19
+ @num_rows = 8
20
+ @gutter = 10.0
21
+ @pdf.define_grid(
22
+ :columns => @num_columns,
23
+ :rows => @num_rows,
24
+ :gutter => @gutter
25
+ )
26
+ end
27
+
28
+ it "should compute the column width" do
29
+ (@pdf.grid.column_width * @num_columns.to_f +
30
+ @gutter * (@num_columns - 1).to_f).should == @pdf.bounds.width
31
+ end
32
+
33
+ it "should compute the row height" do
34
+ (@pdf.grid.row_height * @num_rows.to_f +
35
+ @gutter * (@num_rows - 1).to_f).should == @pdf.bounds.height
36
+ end
37
+
38
+ it "should give the edges of a grid box" do
39
+ grid_width = (@pdf.bounds.width.to_f -
40
+ (@gutter * (@num_columns - 1).to_f )) / @num_columns.to_f
41
+ grid_height = (@pdf.bounds.height.to_f -
42
+ (@gutter * (@num_rows - 1).to_f ))/ @num_rows.to_f
43
+
44
+ exp_tl_x = (grid_width + @gutter.to_f) * 4.0
45
+ exp_tl_y = @pdf.bounds.height.to_f - (grid_height + @gutter.to_f)
46
+
47
+ @pdf.grid(1,4).top_left.should == [exp_tl_x, exp_tl_y]
48
+ @pdf.grid(1,4).top_right.should == [exp_tl_x + grid_width, exp_tl_y]
49
+ @pdf.grid(1,4).bottom_left.should == [exp_tl_x, exp_tl_y - grid_height]
50
+ @pdf.grid(1,4).bottom_right.should == [exp_tl_x + grid_width, exp_tl_y - grid_height]
51
+ end
52
+
53
+ it "should give the edges of a multiple grid boxes" do
54
+ # Hand verified. Cheating a bit. Don't tell.
55
+ @pdf.grid([1,3], [2,5]).top_left.should == [330.0, 628.75]
56
+ @pdf.grid([1,3], [2,5]).top_right.should == [650.0, 628.75]
57
+ @pdf.grid([1,3], [2,5]).bottom_left.should == [330.0, 456.25]
58
+ @pdf.grid([1,3], [2,5]).bottom_right.should == [650.0, 456.25]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
3
+
4
+ describe "When beginning each new page" do
5
+
6
+ it "should execute codeblock given to Document#header" do
7
+ call_count = 0
8
+
9
+ pdf = Prawn::Document.new
10
+ pdf.header(pdf.margin_box.top_left) do
11
+ call_count += 1
12
+ end
13
+
14
+ pdf.start_new_page
15
+ pdf.start_new_page
16
+ pdf.render
17
+
18
+ call_count.should == 3
19
+ end
20
+
21
+ end
22
+
23
+ describe "When ending each page" do
24
+
25
+ it "should execute codeblock given to Document#footer" do
26
+
27
+ call_count = 0
28
+
29
+ pdf = Prawn::Document.new
30
+ pdf.footer([pdf.margin_box.left, pdf.margin_box.bottom + 50]) do
31
+ call_count += 1
32
+ end
33
+
34
+ pdf.start_new_page
35
+ pdf.start_new_page
36
+ pdf.render
37
+
38
+ call_count.should == 3
39
+ end
40
+
41
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ puts "Prawn specs: Running on Ruby Version: #{RUBY_VERSION}"
4
+
5
+ require "rubygems"
6
+ require "test/spec"
7
+ require "mocha"
8
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
9
+
10
+ require "prawn"
11
+ require "prawn/layout"
12
+ $LOAD_PATH << File.join(Prawn::BASEDIR, 'vendor','pdf-inspector','lib')
13
+
14
+ Prawn.debug = true
15
+
16
+ gem 'pdf-reader', ">=0.7.3"
17
+ require "pdf/reader"
18
+ require "pdf/inspector"
19
+
20
+ def create_pdf(klass=Prawn::Document)
21
+ @pdf = klass.new(:left_margin => 0,
22
+ :right_margin => 0,
23
+ :top_margin => 0,
24
+ :bottom_margin => 0)
25
+ end
26
+
27
+ def width_table_for( pdf, hpad, fs, cells )
28
+ cells.inject( 2 * cells.size * hpad ) do |ret, cell|
29
+ ret + pdf.width_of( cell, :size => fs ).ceil
30
+ end
31
+ end
@@ -0,0 +1,400 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
4
+
5
+ describe "A table's width" do
6
+ it "should equal sum(column_widths)" do
7
+ pdf = Prawn::Document.new
8
+ table = Prawn::Table.new( [%w[ a b c ], %w[d e f]], pdf,
9
+ :column_widths => { 0 => 50, 1 => 100, 2 => 150 })
10
+
11
+ table.width.should == 300
12
+ end
13
+
14
+ it "should calculate unspecified column widths even " +
15
+ "with rowspan cells declared (as hashes)" do
16
+ pdf = Prawn::Document.new
17
+ hpad, fs = 3, 12
18
+ # +--------------------+
19
+ # | foo | foobar |
20
+ # +--------------------+
21
+ # | foo | foo | foo |
22
+ # +--------------------+
23
+ data = [ [ { :text => 'foo', :colspan => 2 }, "foobar" ],
24
+ [ "foo", "foo", "foo" ] ]
25
+ table = Prawn::Table.new( data, pdf,
26
+ :horizontal_padding => hpad,
27
+ :font_size => fs )
28
+ # The relevant cells are:
29
+ # - (1, 0) "foo"
30
+ # - (1, 1) "foo"
31
+ # - (0 ,1) "foobar" [at col 2]
32
+ cells = %w( foo foo foobar )
33
+
34
+ table.width.should == width_table_for( pdf, hpad, fs, cells )
35
+ end
36
+
37
+ it "should calculate unspecified column widths even " +
38
+ "with rowspan cells declared before another bigger cells" do
39
+ pdf = Prawn::Document.new
40
+ hpad, fs = 3, 12
41
+ # +----------------------------+
42
+ # | foobarfoobar | foobar |
43
+ # +----------------------------+
44
+ # | foo | foo | foo |
45
+ # | foobarfoo | foo | foo |
46
+ # +----------------------------+
47
+ data = [ [ { :text => 'foobarfoobar', :colspan => 2 }, "foobar" ],
48
+ [ "foo", "foo", "foo" ],
49
+ [ "foobarfoo", "foo", "foo" ] ]
50
+ table = Prawn::Table.new( data, pdf,
51
+ :horizontal_padding => hpad,
52
+ :font_size => fs )
53
+ # The relevant cells are:
54
+ # - (2, 0) "foobarfoo"
55
+ # - (1, 1) "foo"
56
+ # - (0, 1) "foobar"
57
+ cells = %w( foobarfoo foo foobar )
58
+
59
+ table.width.should == width_table_for( pdf, hpad, fs, cells )
60
+ end
61
+
62
+ it "should calculate unspecified column widths even when there is a cell " +
63
+ "with colspan attribute and it's bigger than the other cells of " +
64
+ "these columns" do
65
+ pdf = Prawn::Document.new
66
+ hpad, fs = 3, 12
67
+
68
+ # +---------------------------------+
69
+ # | foobar baz waldo waldo | foobar |
70
+ # +---------------------------------+
71
+ # | foo | foo | foo |
72
+ # +---------------------------------+
73
+ data = [ [ { :text => 'foobar baz waldo waldo', :colspan => 2 }, "foobar" ],
74
+ [ "foo", "foo", "foo" ] ]
75
+ table = Prawn::Table.new( data, pdf,
76
+ :horizontal_padding => hpad,
77
+ :font_size => fs )
78
+ # The relevant cells are:
79
+ # - (0, 0) "foobar baz waldo waldo"
80
+ # - (0 ,1) "foobar" [at col 2]
81
+ cells = %w( foobar\ baz\ waldo\ waldo foobar )
82
+
83
+ table.width.should == width_table_for( pdf, hpad, fs, cells )
84
+ end
85
+
86
+ it "should calculate unspecified column widths even when there are cells " +
87
+ "with rowspain attribute" do
88
+ pdf = Prawn::Document.new
89
+ hpad, fs = 3, 12
90
+
91
+ # +---------------------------------+
92
+ # | foobar baz waldo waldo | foobar |
93
+ # | + -------+
94
+ # | | foo |
95
+ # +---------------------------------+
96
+ data = [ [ { :text => 'foobar baz waldo waldo', :rowspan => 2 }, "foobar" ],
97
+ [ "foo" ] ]
98
+ table = Prawn::Table.new( data, pdf,
99
+ :horizontal_padding => hpad,
100
+ :font_size => fs )
101
+ # The relevant cells are:
102
+ # - (0, 0) "foobar baz waldo waldo"
103
+ # - (0 ,1) "foobar" [at col 2]
104
+ cells = %w( foobar\ baz\ waldo\ waldo foobar )
105
+
106
+ table.width.should == width_table_for( pdf, hpad, fs, cells )
107
+ end
108
+
109
+ it "should calculate unspecified column widths as "+
110
+ "(max(string_width).ceil + 2*horizontal_padding)" do
111
+ pdf = Prawn::Document.new
112
+ hpad, fs = 3, 12
113
+ columns = 2
114
+ table = Prawn::Table.new( [%w[ foo b ], %w[d foobar]], pdf,
115
+ :horizontal_padding => hpad, :font_size => fs)
116
+
117
+ col0_width = pdf.width_of("foo", :size => fs)
118
+ col1_width = pdf.width_of("foobar", :size => fs)
119
+
120
+ table.width.should == col0_width.ceil + col1_width.ceil + 2*columns*hpad
121
+ end
122
+
123
+ it "should allow mixing autocalculated and preset"+
124
+ "column widths within a single table" do
125
+
126
+ pdf = Prawn::Document.new
127
+ hpad, fs = 10, 6
128
+ stretchy_columns = 2
129
+
130
+ col0_width = 50
131
+ col1_width = pdf.width_of("foo", :size => fs)
132
+ col2_width = pdf.width_of("foobar", :size => fs)
133
+ col3_width = 150
134
+
135
+ table = Prawn::Table.new( [%w[snake foo b apple],
136
+ %w[kitten d foobar banana]], pdf,
137
+ :horizontal_padding => hpad, :font_size => fs,
138
+ :column_widths => { 0 => col0_width, 3 => col3_width } )
139
+
140
+ table.width.should == col1_width.ceil + col2_width.ceil +
141
+ 2*stretchy_columns*hpad +
142
+ col0_width.ceil + col3_width.ceil
143
+
144
+ end
145
+
146
+ it "should not exceed the maximum width of the margin_box" do
147
+
148
+ pdf = Prawn::Document.new
149
+ expected_width = pdf.margin_box.width
150
+
151
+ data = [
152
+ ['This is a column with a lot of text that should comfortably exceed '+
153
+ 'the width of a normal document margin_box width', 'Some more text',
154
+ 'and then some more', 'Just a bit more to be extra sure']
155
+ ]
156
+
157
+ table = Prawn::Table.new(data, pdf)
158
+
159
+ table.width.should == expected_width
160
+
161
+ end
162
+
163
+ it "should not exceed the maximum width of the margin_box even with manual widths specified" do
164
+
165
+ pdf = Prawn::Document.new
166
+ expected_width = pdf.margin_box.width
167
+
168
+ data = [
169
+ ['This is a column with a lot of text that should comfortably exceed '+
170
+ 'the width of a normal document margin_box width', 'Some more text',
171
+ 'and then some more', 'Just a bit more to be extra sure']
172
+ ]
173
+
174
+
175
+ table = Prawn::Table.new(data, pdf, :column_widths => { 1 => 100 })
176
+
177
+ table.width.should == expected_width
178
+
179
+ end
180
+
181
+ it "should be the width of the :width parameter" do
182
+
183
+ pdf = Prawn::Document.new
184
+ expected_width = 300
185
+
186
+ table = Prawn::Table.new( [%w[snake foo b apple],
187
+ %w[kitten d foobar banana]], pdf,
188
+ :width => expected_width
189
+ )
190
+
191
+ table.width.should == expected_width
192
+
193
+ end
194
+
195
+ it "should not exceed the :width option" do
196
+
197
+ pdf = Prawn::Document.new
198
+ expected_width = 400
199
+
200
+ data = [
201
+ ['This is a column with a lot of text that should comfortably exceed '+
202
+ 'the width of a normal document margin_box width', 'Some more text',
203
+ 'and then some more', 'Just a bit more to be extra sure']
204
+ ]
205
+
206
+ table = Prawn::Table.new(data, pdf, :width => expected_width)
207
+
208
+ table.width.should == expected_width
209
+
210
+ end
211
+
212
+ it "should not exceed the :width option even with manual widths specified" do
213
+
214
+ pdf = Prawn::Document.new
215
+ expected_width = 400
216
+
217
+ data = [
218
+ ['This is a column with a lot of text that should comfortably exceed '+
219
+ 'the width of a normal document margin_box width', 'Some more text',
220
+ 'and then some more', 'Just a bit more to be extra sure']
221
+ ]
222
+
223
+ table = Prawn::Table.new(data, pdf, :column_widths => { 1 => 100 }, :width => expected_width)
224
+
225
+ table.width.should == expected_width
226
+
227
+ end
228
+
229
+ end
230
+
231
+ describe "A table's height" do
232
+
233
+ before :each do
234
+ data = [["foo"],["bar"],["baaaz"]]
235
+ pdf = Prawn::Document.new
236
+ @num_rows = data.length
237
+
238
+ @vpad = 4
239
+ origin = pdf.y
240
+ pdf.table data, :vertical_padding => @vpad
241
+
242
+ @table_height = origin - pdf.y
243
+
244
+ @font_height = pdf.font.height
245
+ end
246
+
247
+ it "should have a height of n rows" do
248
+ @table_height.should.be.close(
249
+ @num_rows*@font_height + 2*@vpad*@num_rows, 0.001 )
250
+ end
251
+
252
+ end
253
+
254
+ describe "A table's content" do
255
+
256
+ it "should not cause an error if rendering the very first row causes a page break" do
257
+ Prawn::Document.new( :page_layout => :portrait ) do
258
+ arr = Array(1..5).collect{|i| ["cell #{i}"] }
259
+
260
+ move_down( y - (bounds.absolute_bottom + 3) )
261
+
262
+ lambda {
263
+ table( arr,
264
+ :font_size => 9,
265
+ :horizontal_padding => 3,
266
+ :vertical_padding => 3,
267
+ :border_width => 0.05,
268
+ :border_style => :none,
269
+ :row_colors => %w{ffffff eeeeee},
270
+ :column_widths => {0 =>110},
271
+ :position => :left,
272
+ :headers => ["exploding header"],
273
+ :align => :left,
274
+ :align_headers => :center)
275
+ }.should.not.raise
276
+ end
277
+ end
278
+
279
+ it "should output content cell by cell, row by row" do
280
+ data = [["foo","bar"],["baz","bang"]]
281
+ @pdf = Prawn::Document.new
282
+ @pdf.table(data)
283
+ output = PDF::Inspector::Text.analyze(@pdf.render)
284
+ output.strings.should == data.flatten
285
+ end
286
+
287
+ it "should add headers to output when specified" do
288
+ data = [["foo","bar"],["baz","bang"]]
289
+ headers = %w[a b]
290
+ @pdf = Prawn::Document.new
291
+ @pdf.table(data, :headers => headers)
292
+ output = PDF::Inspector::Text.analyze(@pdf.render)
293
+ output.strings.should == headers + data.flatten
294
+ end
295
+
296
+ it "should repeat headers across pages" do
297
+ data = [["foo","bar"]]*30
298
+ headers = ["baz","foobar"]
299
+ @pdf = Prawn::Document.new
300
+ @pdf.table(data, :headers => headers)
301
+ output = PDF::Inspector::Text.analyze(@pdf.render)
302
+ output.strings.should == headers + data.flatten[0..-3] + headers +
303
+ data.flatten[-2..-1]
304
+ end
305
+
306
+ it "should allow empty fields" do
307
+ lambda {
308
+ data = [["foo","bar"],["baz",""]]
309
+ @pdf = Prawn::Document.new
310
+ @pdf.table(data)
311
+ }.should.not.raise
312
+ end
313
+
314
+ it "should paginate for large tables" do
315
+ # 30 rows fit on the table with default setting, 31 exceed.
316
+ data = [["foo"]] * 31
317
+ pdf = Prawn::Document.new
318
+
319
+ pdf.table data
320
+ pdf.page_count.should == 2
321
+
322
+ pdf.table data
323
+ pdf.page_count.should == 3
324
+ end
325
+
326
+ it "should accurately count columns from data" do
327
+ # First data row may contain colspan which would hide true column count
328
+ data = [["Name:",{:text => "Some very long name", :colspan => 5}]]
329
+ pdf = Prawn::Document.new
330
+ table = Prawn::Table.new data, pdf
331
+ table.column_widths.length.should == 6
332
+ end
333
+
334
+ end
335
+
336
+ describe "An invalid table" do
337
+
338
+ before(:each) do
339
+ @pdf = Prawn::Document.new
340
+ @bad_data = ["Single Nested Array"]
341
+ end
342
+
343
+ it "should raise error when invalid table data is given" do
344
+
345
+ assert_raises(Prawn::Errors::InvalidTableData) do
346
+ @pdf.table(@bad_data)
347
+ end
348
+ end
349
+
350
+ it "should raise an EmptyTableError with empty table data" do
351
+ lambda {
352
+ data = []
353
+ @pdf = Prawn::Document.new
354
+ @pdf.table(data)
355
+ }.should.raise( Prawn::Errors::EmptyTable )
356
+ end
357
+
358
+ it "should raise an EmptyTableError with nil table data" do
359
+ lambda {
360
+ data = nil
361
+ @pdf = Prawn::Document.new
362
+ @pdf.table(data)
363
+ }.should.raise( Prawn::Errors::EmptyTable )
364
+ end
365
+
366
+ it "should raise an InvalidTableData with bad formed data" do
367
+ lambda {
368
+ data = [ [ 'a', 'b' ], [ 'c' ] ]
369
+ @pdf.table( data )
370
+ }.should.raise( Prawn::Errors::InvalidTableData )
371
+
372
+ lambda {
373
+ data = [ [ 'a' ], [ 'b', 'c' ] ]
374
+ @pdf.table( data )
375
+ }.should.raise( Prawn::Errors::InvalidTableData )
376
+ end
377
+
378
+ it "should raise an InvalidTableData with bad formed data even with " +
379
+ "either rowspan or colspan cells" do
380
+ lambda {
381
+ data = [ [ { :rowspan => 2, :text => 'a' }, 'b' ],
382
+ [ 'c', 'd' ] ]
383
+ @pdf.table( data )
384
+ }.should.raise( Prawn::Errors::InvalidTableData )
385
+
386
+ lambda {
387
+ data = [ [ { :rowspan => 2, :text => 'a' },
388
+ { :rowspan => 2, :text => 'b' } ],
389
+ [ 'c', 'd', 'e', 'f', 'g' ] ]
390
+ @pdf.table( data )
391
+ }.should.raise( Prawn::Errors::InvalidTableData )
392
+
393
+ lambda {
394
+ data = [ [ { :rowspan => 2, :text => 'a' },
395
+ { :colspan => 2, :text => 'b' } ],
396
+ [ 'c', 'd', 'e' ] ]
397
+ @pdf.table( data )
398
+ }.should.raise( Prawn::Errors::InvalidTableData )
399
+ end
400
+ end