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.
- data/README +1 -0
- data/Rakefile +70 -0
- data/examples/addressbook.csv +6 -0
- data/examples/cell.rb +43 -0
- data/examples/currency.csv +1834 -0
- data/examples/fancy_table.rb +67 -0
- data/examples/table.rb +54 -0
- data/examples/table_alignment.rb +21 -0
- data/examples/table_border_color.rb +20 -0
- data/examples/table_colspan.rb +22 -0
- data/examples/table_header_color.rb +22 -0
- data/examples/table_header_underline.rb +18 -0
- data/examples/table_rowspan.rb +33 -0
- data/examples/table_widths.rb +64 -0
- data/lib/prawn/errors.rb +9 -0
- data/lib/prawn/flexible-table.rb +516 -0
- data/lib/prawn/flexible-table/cell.rb +267 -0
- data/spec/grid_spec.rb +61 -0
- data/spec/page_layout_spec.rb +41 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/table_spec.rb +400 -0
- metadata +79 -0
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
data/spec/table_spec.rb
ADDED
@@ -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
|