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.
- checksums.yaml +7 -0
- data/COPYING +2 -0
- data/GPLv2 +340 -0
- data/GPLv3 +674 -0
- data/Gemfile +5 -0
- data/LICENSE +56 -0
- data/lib/prawn/table.rb +641 -0
- data/lib/prawn/table/cell.rb +772 -0
- data/lib/prawn/table/cell/image.rb +69 -0
- data/lib/prawn/table/cell/in_table.rb +33 -0
- data/lib/prawn/table/cell/span_dummy.rb +93 -0
- data/lib/prawn/table/cell/subtable.rb +66 -0
- data/lib/prawn/table/cell/text.rb +154 -0
- data/lib/prawn/table/cells.rb +255 -0
- data/lib/prawn/table/column_width_calculator.rb +182 -0
- data/manual/contents.rb +13 -0
- data/manual/example_helper.rb +8 -0
- data/manual/table/basic_block.rb +53 -0
- data/manual/table/before_rendering_page.rb +26 -0
- data/manual/table/cell_border_lines.rb +24 -0
- data/manual/table/cell_borders_and_bg.rb +31 -0
- data/manual/table/cell_dimensions.rb +30 -0
- data/manual/table/cell_text.rb +38 -0
- data/manual/table/column_widths.rb +30 -0
- data/manual/table/content_and_subtables.rb +39 -0
- data/manual/table/creation.rb +27 -0
- data/manual/table/filtering.rb +36 -0
- data/manual/table/flow_and_header.rb +17 -0
- data/manual/table/image_cells.rb +33 -0
- data/manual/table/position.rb +29 -0
- data/manual/table/row_colors.rb +20 -0
- data/manual/table/span.rb +30 -0
- data/manual/table/style.rb +22 -0
- data/manual/table/table.rb +52 -0
- data/manual/table/width.rb +27 -0
- data/prawn-table.gemspec +48 -0
- data/spec/cell_spec.rb +629 -0
- data/spec/extensions/encoding_helpers.rb +11 -0
- data/spec/extensions/mocha.rb +46 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/table/span_dummy_spec.rb +17 -0
- data/spec/table_spec.rb +1527 -0
- metadata +240 -0
@@ -0,0 +1,772 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# cell.rb: Table cell drawing.
|
4
|
+
#
|
5
|
+
# Copyright December 2009, Gregory Brown and Brad Ediger. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
require 'date'
|
10
|
+
module Prawn
|
11
|
+
class Document
|
12
|
+
|
13
|
+
# @group Experimental API
|
14
|
+
|
15
|
+
# Instantiates and draws a cell on the document.
|
16
|
+
#
|
17
|
+
# cell(:content => "Hello world!", :at => [12, 34])
|
18
|
+
#
|
19
|
+
# See Prawn::Table::Cell.make for full options.
|
20
|
+
#
|
21
|
+
def cell(options={})
|
22
|
+
cell = Table::Cell.make(self, options.delete(:content), options)
|
23
|
+
cell.draw
|
24
|
+
cell
|
25
|
+
end
|
26
|
+
|
27
|
+
# Set up, but do not draw, a cell. Useful for creating cells with
|
28
|
+
# formatting options to be inserted into a Table. Call +draw+ on the
|
29
|
+
# resulting Cell to ink it.
|
30
|
+
#
|
31
|
+
# See the documentation on Prawn::Cell for details on the arguments.
|
32
|
+
#
|
33
|
+
def make_cell(content, options={})
|
34
|
+
Prawn::Table::Cell.make(self, content, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class Table
|
40
|
+
|
41
|
+
# A Cell is a rectangular area of the page into which content is drawn. It
|
42
|
+
# has a framework for sizing itself and adding padding and simple styling.
|
43
|
+
# There are several standard Cell subclasses that handle things like text,
|
44
|
+
# Tables, and (in the future) stamps, images, and arbitrary content.
|
45
|
+
#
|
46
|
+
# Cells are a basic building block for table support (see Prawn::Table).
|
47
|
+
#
|
48
|
+
# Please subclass me if you want new content types! I'm designed to be very
|
49
|
+
# extensible. See the different standard Cell subclasses in
|
50
|
+
# lib/prawn/table/cell/*.rb for a template.
|
51
|
+
#
|
52
|
+
class Cell
|
53
|
+
|
54
|
+
# Amount of dead space (in PDF points) inside the borders but outside the
|
55
|
+
# content. Padding defaults to 5pt.
|
56
|
+
#
|
57
|
+
attr_reader :padding
|
58
|
+
|
59
|
+
# If provided, the minimum width that this cell in its column will permit.
|
60
|
+
#
|
61
|
+
def min_width_ignoring_span
|
62
|
+
set_width_constraints
|
63
|
+
@min_width
|
64
|
+
end
|
65
|
+
|
66
|
+
# Minimum width of the entire span group this cell controls.
|
67
|
+
#
|
68
|
+
def min_width
|
69
|
+
return min_width_ignoring_span if @colspan == 1
|
70
|
+
|
71
|
+
# Sum up the largest min-width from each column, including myself.
|
72
|
+
min_widths = Hash.new(0)
|
73
|
+
dummy_cells.each do |cell|
|
74
|
+
min_widths[cell.column] =
|
75
|
+
[min_widths[cell.column], cell.min_width].max
|
76
|
+
end
|
77
|
+
min_widths[column] = [min_widths[column], min_width_ignoring_span].max
|
78
|
+
min_widths.values.inject(0, &:+)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Min-width of the span divided by the number of columns.
|
82
|
+
#
|
83
|
+
def avg_spanned_min_width
|
84
|
+
min_width.to_f / colspan
|
85
|
+
end
|
86
|
+
|
87
|
+
# If provided, the maximum width that this cell can be drawn in, within
|
88
|
+
# its column.
|
89
|
+
#
|
90
|
+
def max_width_ignoring_span
|
91
|
+
set_width_constraints
|
92
|
+
@max_width
|
93
|
+
end
|
94
|
+
|
95
|
+
# Maximum width of the entire span group this cell controls.
|
96
|
+
#
|
97
|
+
def max_width
|
98
|
+
return max_width_ignoring_span if @colspan == 1
|
99
|
+
|
100
|
+
# Sum the smallest max-width from each column in the group, including
|
101
|
+
# myself.
|
102
|
+
max_widths = Hash.new(0)
|
103
|
+
dummy_cells.each do |cell|
|
104
|
+
max_widths[cell.column] =
|
105
|
+
[max_widths[cell.column], cell.max_width].min
|
106
|
+
end
|
107
|
+
max_widths[column] = [max_widths[column], max_width_ignoring_span].min
|
108
|
+
max_widths.values.inject(0, &:+)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Manually specify the cell's height.
|
112
|
+
#
|
113
|
+
attr_writer :height
|
114
|
+
|
115
|
+
# Specifies which borders to enable. Must be an array of zero or more of:
|
116
|
+
# <tt>[:left, :right, :top, :bottom]</tt>.
|
117
|
+
#
|
118
|
+
attr_accessor :borders
|
119
|
+
|
120
|
+
# Width, in PDF points, of the cell's borders: [top, right, bottom, left].
|
121
|
+
#
|
122
|
+
attr_reader :border_widths
|
123
|
+
|
124
|
+
# HTML RGB-format ("ccffff") border colors: [top, right, bottom, left].
|
125
|
+
#
|
126
|
+
attr_reader :border_colors
|
127
|
+
|
128
|
+
# Line style
|
129
|
+
#
|
130
|
+
attr_reader :border_lines
|
131
|
+
|
132
|
+
# Specifies the content for the cell. Must be a "cellable" object. See the
|
133
|
+
# "Data" section of the Prawn::Table documentation for details on cellable
|
134
|
+
# objects.
|
135
|
+
#
|
136
|
+
attr_accessor :content
|
137
|
+
|
138
|
+
# The background color, if any, for this cell. Specified in HTML RGB
|
139
|
+
# format, e.g., "ccffff". The background is drawn under the whole cell,
|
140
|
+
# including any padding.
|
141
|
+
#
|
142
|
+
attr_accessor :background_color
|
143
|
+
|
144
|
+
# Number of columns this cell spans. Defaults to 1.
|
145
|
+
#
|
146
|
+
attr_reader :colspan
|
147
|
+
|
148
|
+
# Number of rows this cell spans. Defaults to 1.
|
149
|
+
#
|
150
|
+
attr_reader :rowspan
|
151
|
+
|
152
|
+
# Array of SpanDummy cells (if any) that represent the other cells in
|
153
|
+
# this span group. They know their own width / height, but do not draw
|
154
|
+
# anything.
|
155
|
+
#
|
156
|
+
attr_reader :dummy_cells
|
157
|
+
|
158
|
+
# Instantiates a Cell based on the given options. The particular class of
|
159
|
+
# cell returned depends on the :content argument. See the Prawn::Table
|
160
|
+
# documentation under "Data" for allowable content types.
|
161
|
+
#
|
162
|
+
def self.make(pdf, content, options={})
|
163
|
+
at = options.delete(:at) || [0, pdf.cursor]
|
164
|
+
content = content.to_s if content.nil? || content.kind_of?(Numeric) ||
|
165
|
+
content.kind_of?(Date)
|
166
|
+
|
167
|
+
if content.is_a?(Hash)
|
168
|
+
if content[:image]
|
169
|
+
return Cell::Image.new(pdf, at, content)
|
170
|
+
end
|
171
|
+
options.update(content)
|
172
|
+
content = options[:content]
|
173
|
+
else
|
174
|
+
options[:content] = content
|
175
|
+
end
|
176
|
+
|
177
|
+
options[:content] = content = "" if content.nil?
|
178
|
+
|
179
|
+
case content
|
180
|
+
when Prawn::Table::Cell
|
181
|
+
content
|
182
|
+
when String
|
183
|
+
Cell::Text.new(pdf, at, options)
|
184
|
+
when Prawn::Table
|
185
|
+
Cell::Subtable.new(pdf, at, options)
|
186
|
+
when Array
|
187
|
+
subtable = Prawn::Table.new(options[:content], pdf, {})
|
188
|
+
Cell::Subtable.new(pdf, at, options.merge(:content => subtable))
|
189
|
+
else
|
190
|
+
raise Errors::UnrecognizedTableContent
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# A small amount added to the bounding box width to cover over floating-
|
195
|
+
# point errors when round-tripping from content_width to width and back.
|
196
|
+
# This does not change cell positioning; it only slightly expands each
|
197
|
+
# cell's bounding box width so that rounding error does not prevent a cell
|
198
|
+
# from rendering.
|
199
|
+
#
|
200
|
+
FPTolerance = 1
|
201
|
+
|
202
|
+
# Sets up a cell on the document +pdf+, at the given x/y location +point+,
|
203
|
+
# with the given +options+. Cell, like Table, follows the "options set
|
204
|
+
# accessors" paradigm (see "Options" under the Table documentation), so
|
205
|
+
# any cell accessor <tt>cell.foo = :bar</tt> can be set by providing the
|
206
|
+
# option <tt>:foo => :bar</tt> here.
|
207
|
+
#
|
208
|
+
def initialize(pdf, point, options={})
|
209
|
+
@pdf = pdf
|
210
|
+
@point = point
|
211
|
+
|
212
|
+
# Set defaults; these can be changed by options
|
213
|
+
@padding = [5, 5, 5, 5]
|
214
|
+
@borders = [:top, :bottom, :left, :right]
|
215
|
+
@border_widths = [1] * 4
|
216
|
+
@border_colors = ['000000'] * 4
|
217
|
+
@border_lines = [:solid] * 4
|
218
|
+
@colspan = 1
|
219
|
+
@rowspan = 1
|
220
|
+
@dummy_cells = []
|
221
|
+
|
222
|
+
options.each { |k, v| send("#{k}=", v) }
|
223
|
+
|
224
|
+
@initializer_run = true
|
225
|
+
end
|
226
|
+
|
227
|
+
# Supports setting multiple properties at once.
|
228
|
+
#
|
229
|
+
# cell.style(:padding => 0, :border_width => 2)
|
230
|
+
#
|
231
|
+
# is the same as:
|
232
|
+
#
|
233
|
+
# cell.padding = 0
|
234
|
+
# cell.border_width = 2
|
235
|
+
#
|
236
|
+
def style(options={}, &block)
|
237
|
+
options.each do |k, v|
|
238
|
+
send("#{k}=", v) if respond_to?("#{k}=")
|
239
|
+
end
|
240
|
+
|
241
|
+
# The block form supports running a single block for multiple cells, as
|
242
|
+
# in Cells#style.
|
243
|
+
block.call(self) if block
|
244
|
+
end
|
245
|
+
|
246
|
+
# Returns the width of the cell in its first column alone, ignoring any
|
247
|
+
# colspans.
|
248
|
+
#
|
249
|
+
def width_ignoring_span
|
250
|
+
# We can't ||= here because the FP error accumulates on the round-trip
|
251
|
+
# from #content_width.
|
252
|
+
defined?(@width) && @width || (content_width + padding_left + padding_right)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Returns the cell's width in points, inclusive of padding. If the cell is
|
256
|
+
# the master cell of a colspan, returns the width of the entire span
|
257
|
+
# group.
|
258
|
+
#
|
259
|
+
def width
|
260
|
+
return width_ignoring_span if @colspan == 1 && @rowspan == 1
|
261
|
+
|
262
|
+
# We're in a span group; get the maximum width per column (including
|
263
|
+
# the master cell) and sum each column.
|
264
|
+
column_widths = Hash.new(0)
|
265
|
+
dummy_cells.each do |cell|
|
266
|
+
column_widths[cell.column] =
|
267
|
+
[column_widths[cell.column], cell.width].max
|
268
|
+
end
|
269
|
+
column_widths[column] = [column_widths[column], width_ignoring_span].max
|
270
|
+
column_widths.values.inject(0, &:+)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Manually sets the cell's width, inclusive of padding.
|
274
|
+
#
|
275
|
+
def width=(w)
|
276
|
+
@width = @min_width = @max_width = w
|
277
|
+
end
|
278
|
+
|
279
|
+
# Returns the width of the bare content in the cell, excluding padding.
|
280
|
+
#
|
281
|
+
def content_width
|
282
|
+
if defined?(@width) && @width # manually set
|
283
|
+
return @width - padding_left - padding_right
|
284
|
+
end
|
285
|
+
|
286
|
+
natural_content_width
|
287
|
+
end
|
288
|
+
|
289
|
+
# Width of the entire span group.
|
290
|
+
#
|
291
|
+
def spanned_content_width
|
292
|
+
width - padding_left - padding_right
|
293
|
+
end
|
294
|
+
|
295
|
+
# Returns the width this cell would naturally take on, absent other
|
296
|
+
# constraints. Must be implemented in subclasses.
|
297
|
+
#
|
298
|
+
def natural_content_width
|
299
|
+
raise NotImplementedError,
|
300
|
+
"subclasses must implement natural_content_width"
|
301
|
+
end
|
302
|
+
|
303
|
+
# Returns the cell's height in points, inclusive of padding, in its first
|
304
|
+
# row only.
|
305
|
+
#
|
306
|
+
def height_ignoring_span
|
307
|
+
# We can't ||= here because the FP error accumulates on the round-trip
|
308
|
+
# from #content_height.
|
309
|
+
defined?(@height) && @height || (content_height + padding_top + padding_bottom)
|
310
|
+
end
|
311
|
+
|
312
|
+
# Returns the cell's height in points, inclusive of padding. If the cell
|
313
|
+
# is the master cell of a rowspan, returns the width of the entire span
|
314
|
+
# group.
|
315
|
+
#
|
316
|
+
def height
|
317
|
+
return height_ignoring_span if @colspan == 1 && @rowspan == 1
|
318
|
+
|
319
|
+
# We're in a span group; get the maximum height per row (including the
|
320
|
+
# master cell) and sum each row.
|
321
|
+
row_heights = Hash.new(0)
|
322
|
+
dummy_cells.each do |cell|
|
323
|
+
row_heights[cell.row] = [row_heights[cell.row], cell.height].max
|
324
|
+
end
|
325
|
+
row_heights[row] = [row_heights[row], height_ignoring_span].max
|
326
|
+
row_heights.values.inject(0, &:+)
|
327
|
+
end
|
328
|
+
|
329
|
+
# Returns the height of the bare content in the cell, excluding padding.
|
330
|
+
#
|
331
|
+
def content_height
|
332
|
+
if defined?(@height) && @height # manually set
|
333
|
+
return @height - padding_top - padding_bottom
|
334
|
+
end
|
335
|
+
|
336
|
+
natural_content_height
|
337
|
+
end
|
338
|
+
|
339
|
+
# Height of the entire span group.
|
340
|
+
#
|
341
|
+
def spanned_content_height
|
342
|
+
height - padding_top - padding_bottom
|
343
|
+
end
|
344
|
+
|
345
|
+
# Returns the height this cell would naturally take on, absent
|
346
|
+
# constraints. Must be implemented in subclasses.
|
347
|
+
#
|
348
|
+
def natural_content_height
|
349
|
+
raise NotImplementedError,
|
350
|
+
"subclasses must implement natural_content_height"
|
351
|
+
end
|
352
|
+
|
353
|
+
# Indicates the number of columns that this cell is to span. Defaults to
|
354
|
+
# 1.
|
355
|
+
#
|
356
|
+
# This must be provided as part of the table data, like so:
|
357
|
+
#
|
358
|
+
# pdf.table([["foo", {:content => "bar", :colspan => 2}]])
|
359
|
+
#
|
360
|
+
# Setting colspan from the initializer block is invalid because layout
|
361
|
+
# has already run. For example, this will NOT work:
|
362
|
+
#
|
363
|
+
# pdf.table([["foo", "bar"]]) { cells[0, 1].colspan = 2 }
|
364
|
+
#
|
365
|
+
def colspan=(span)
|
366
|
+
if defined?(@initializer_run) && @initializer_run
|
367
|
+
raise Prawn::Errors::InvalidTableSpan,
|
368
|
+
"colspan must be provided in the table's structure, never in the " +
|
369
|
+
"initialization block. See Prawn's documentation for details."
|
370
|
+
end
|
371
|
+
|
372
|
+
@colspan = span
|
373
|
+
end
|
374
|
+
|
375
|
+
# Indicates the number of rows that this cell is to span. Defaults to 1.
|
376
|
+
#
|
377
|
+
# This must be provided as part of the table data, like so:
|
378
|
+
#
|
379
|
+
# pdf.table([["foo", {:content => "bar", :rowspan => 2}], ["baz"]])
|
380
|
+
#
|
381
|
+
# Setting rowspan from the initializer block is invalid because layout
|
382
|
+
# has already run. For example, this will NOT work:
|
383
|
+
#
|
384
|
+
# pdf.table([["foo", "bar"], ["baz"]]) { cells[0, 1].rowspan = 2 }
|
385
|
+
#
|
386
|
+
def rowspan=(span)
|
387
|
+
if defined?(@initializer_run) && @initializer_run
|
388
|
+
raise Prawn::Errors::InvalidTableSpan,
|
389
|
+
"rowspan must be provided in the table's structure, never in the " +
|
390
|
+
"initialization block. See Prawn's documentation for details."
|
391
|
+
end
|
392
|
+
|
393
|
+
@rowspan = span
|
394
|
+
end
|
395
|
+
|
396
|
+
# Draws the cell onto the document. Pass in a point [x,y] to override the
|
397
|
+
# location at which the cell is drawn.
|
398
|
+
#
|
399
|
+
# If drawing a group of cells at known positions, look into
|
400
|
+
# Cell.draw_cells, which ensures that the backgrounds, borders, and
|
401
|
+
# content are all drawn in correct order so as not to overlap.
|
402
|
+
#
|
403
|
+
def draw(pt=[x, y])
|
404
|
+
Prawn::Table::Cell.draw_cells([[self, pt]])
|
405
|
+
end
|
406
|
+
|
407
|
+
# Given an array of pairs [cell, pt], draws each cell at its
|
408
|
+
# corresponding pt, making sure all backgrounds are behind all borders
|
409
|
+
# and content.
|
410
|
+
#
|
411
|
+
def self.draw_cells(cells)
|
412
|
+
cells.each do |cell, pt|
|
413
|
+
cell.set_width_constraints
|
414
|
+
cell.draw_background(pt)
|
415
|
+
end
|
416
|
+
|
417
|
+
cells.each do |cell, pt|
|
418
|
+
cell.draw_borders(pt)
|
419
|
+
cell.draw_bounded_content(pt)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
# Draws the cell's content at the point provided.
|
424
|
+
#
|
425
|
+
def draw_bounded_content(pt)
|
426
|
+
@pdf.float do
|
427
|
+
@pdf.bounding_box([pt[0] + padding_left, pt[1] - padding_top],
|
428
|
+
:width => spanned_content_width + FPTolerance,
|
429
|
+
:height => spanned_content_height + FPTolerance) do
|
430
|
+
draw_content
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
# x-position of the cell within the parent bounds.
|
436
|
+
#
|
437
|
+
def x
|
438
|
+
@point[0]
|
439
|
+
end
|
440
|
+
|
441
|
+
# Set the x-position of the cell within the parent bounds.
|
442
|
+
#
|
443
|
+
def x=(val)
|
444
|
+
@point[0] = val
|
445
|
+
end
|
446
|
+
|
447
|
+
# y-position of the cell within the parent bounds.
|
448
|
+
#
|
449
|
+
def y
|
450
|
+
@point[1]
|
451
|
+
end
|
452
|
+
|
453
|
+
# Set the y-position of the cell within the parent bounds.
|
454
|
+
#
|
455
|
+
def y=(val)
|
456
|
+
@point[1] = val
|
457
|
+
end
|
458
|
+
|
459
|
+
# Sets padding on this cell. The argument can be one of:
|
460
|
+
#
|
461
|
+
# * an integer (sets all padding)
|
462
|
+
# * a two-element array [vertical, horizontal]
|
463
|
+
# * a three-element array [top, horizontal, bottom]
|
464
|
+
# * a four-element array [top, right, bottom, left]
|
465
|
+
#
|
466
|
+
def padding=(pad)
|
467
|
+
@padding = case
|
468
|
+
when pad.nil?
|
469
|
+
[0, 0, 0, 0]
|
470
|
+
when Numeric === pad # all padding
|
471
|
+
[pad, pad, pad, pad]
|
472
|
+
when pad.length == 2 # vert, horiz
|
473
|
+
[pad[0], pad[1], pad[0], pad[1]]
|
474
|
+
when pad.length == 3 # top, horiz, bottom
|
475
|
+
[pad[0], pad[1], pad[2], pad[1]]
|
476
|
+
when pad.length == 4 # top, right, bottom, left
|
477
|
+
[pad[0], pad[1], pad[2], pad[3]]
|
478
|
+
else
|
479
|
+
raise ArgumentError, ":padding must be a number or an array [v,h] " +
|
480
|
+
"or [t,r,b,l]"
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def padding_top
|
485
|
+
@padding[0]
|
486
|
+
end
|
487
|
+
|
488
|
+
def padding_top=(val)
|
489
|
+
@padding[0] = val
|
490
|
+
end
|
491
|
+
|
492
|
+
def padding_right
|
493
|
+
@padding[1]
|
494
|
+
end
|
495
|
+
|
496
|
+
def padding_right=(val)
|
497
|
+
@padding[1] = val
|
498
|
+
end
|
499
|
+
|
500
|
+
def padding_bottom
|
501
|
+
@padding[2]
|
502
|
+
end
|
503
|
+
|
504
|
+
def padding_bottom=(val)
|
505
|
+
@padding[2] = val
|
506
|
+
end
|
507
|
+
|
508
|
+
def padding_left
|
509
|
+
@padding[3]
|
510
|
+
end
|
511
|
+
|
512
|
+
def padding_left=(val)
|
513
|
+
@padding[3] = val
|
514
|
+
end
|
515
|
+
|
516
|
+
# Sets border colors on this cell. The argument can be one of:
|
517
|
+
#
|
518
|
+
# * an integer (sets all colors)
|
519
|
+
# * a two-element array [vertical, horizontal]
|
520
|
+
# * a three-element array [top, horizontal, bottom]
|
521
|
+
# * a four-element array [top, right, bottom, left]
|
522
|
+
#
|
523
|
+
def border_color=(color)
|
524
|
+
@border_colors = case
|
525
|
+
when color.nil?
|
526
|
+
["000000"] * 4
|
527
|
+
when String === color # all colors
|
528
|
+
[color, color, color, color]
|
529
|
+
when color.length == 2 # vert, horiz
|
530
|
+
[color[0], color[1], color[0], color[1]]
|
531
|
+
when color.length == 3 # top, horiz, bottom
|
532
|
+
[color[0], color[1], color[2], color[1]]
|
533
|
+
when color.length == 4 # top, right, bottom, left
|
534
|
+
[color[0], color[1], color[2], color[3]]
|
535
|
+
else
|
536
|
+
raise ArgumentError, ":border_color must be a string " +
|
537
|
+
"or an array [v,h] or [t,r,b,l]"
|
538
|
+
end
|
539
|
+
end
|
540
|
+
alias_method :border_colors=, :border_color=
|
541
|
+
|
542
|
+
def border_top_color
|
543
|
+
@border_colors[0]
|
544
|
+
end
|
545
|
+
|
546
|
+
def border_top_color=(val)
|
547
|
+
@border_colors[0] = val
|
548
|
+
end
|
549
|
+
|
550
|
+
def border_right_color
|
551
|
+
@border_colors[1]
|
552
|
+
end
|
553
|
+
|
554
|
+
def border_right_color=(val)
|
555
|
+
@border_colors[1] = val
|
556
|
+
end
|
557
|
+
|
558
|
+
def border_bottom_color
|
559
|
+
@border_colors[2]
|
560
|
+
end
|
561
|
+
|
562
|
+
def border_bottom_color=(val)
|
563
|
+
@border_colors[2] = val
|
564
|
+
end
|
565
|
+
|
566
|
+
def border_left_color
|
567
|
+
@border_colors[3]
|
568
|
+
end
|
569
|
+
|
570
|
+
def border_left_color=(val)
|
571
|
+
@border_colors[3] = val
|
572
|
+
end
|
573
|
+
|
574
|
+
# Sets border widths on this cell. The argument can be one of:
|
575
|
+
#
|
576
|
+
# * an integer (sets all widths)
|
577
|
+
# * a two-element array [vertical, horizontal]
|
578
|
+
# * a three-element array [top, horizontal, bottom]
|
579
|
+
# * a four-element array [top, right, bottom, left]
|
580
|
+
#
|
581
|
+
def border_width=(width)
|
582
|
+
@border_widths = case
|
583
|
+
when width.nil?
|
584
|
+
["000000"] * 4
|
585
|
+
when Numeric === width # all widths
|
586
|
+
[width, width, width, width]
|
587
|
+
when width.length == 2 # vert, horiz
|
588
|
+
[width[0], width[1], width[0], width[1]]
|
589
|
+
when width.length == 3 # top, horiz, bottom
|
590
|
+
[width[0], width[1], width[2], width[1]]
|
591
|
+
when width.length == 4 # top, right, bottom, left
|
592
|
+
[width[0], width[1], width[2], width[3]]
|
593
|
+
else
|
594
|
+
raise ArgumentError, ":border_width must be a string " +
|
595
|
+
"or an array [v,h] or [t,r,b,l]"
|
596
|
+
end
|
597
|
+
end
|
598
|
+
alias_method :border_widths=, :border_width=
|
599
|
+
|
600
|
+
def border_top_width
|
601
|
+
@borders.include?(:top) ? @border_widths[0] : 0
|
602
|
+
end
|
603
|
+
|
604
|
+
def border_top_width=(val)
|
605
|
+
@border_widths[0] = val
|
606
|
+
end
|
607
|
+
|
608
|
+
def border_right_width
|
609
|
+
@borders.include?(:right) ? @border_widths[1] : 0
|
610
|
+
end
|
611
|
+
|
612
|
+
def border_right_width=(val)
|
613
|
+
@border_widths[1] = val
|
614
|
+
end
|
615
|
+
|
616
|
+
def border_bottom_width
|
617
|
+
@borders.include?(:bottom) ? @border_widths[2] : 0
|
618
|
+
end
|
619
|
+
|
620
|
+
def border_bottom_width=(val)
|
621
|
+
@border_widths[2] = val
|
622
|
+
end
|
623
|
+
|
624
|
+
def border_left_width
|
625
|
+
@borders.include?(:left) ? @border_widths[3] : 0
|
626
|
+
end
|
627
|
+
|
628
|
+
def border_left_width=(val)
|
629
|
+
@border_widths[3] = val
|
630
|
+
end
|
631
|
+
|
632
|
+
# Sets the cell's minimum and maximum width. Deferred until requested
|
633
|
+
# because padding and size can change.
|
634
|
+
#
|
635
|
+
def set_width_constraints
|
636
|
+
@min_width ||= padding_left + padding_right
|
637
|
+
@max_width ||= @pdf.bounds.width
|
638
|
+
end
|
639
|
+
|
640
|
+
# Sets border line style on this cell. The argument can be one of:
|
641
|
+
#
|
642
|
+
# Possible values are: :solid, :dashed, :dotted
|
643
|
+
#
|
644
|
+
# * one value (sets all lines)
|
645
|
+
# * a two-element array [vertical, horizontal]
|
646
|
+
# * a three-element array [top, horizontal, bottom]
|
647
|
+
# * a four-element array [top, right, bottom, left]
|
648
|
+
#
|
649
|
+
def border_line=(line)
|
650
|
+
@border_lines = case
|
651
|
+
when line.nil?
|
652
|
+
[:solid] * 4
|
653
|
+
when line.length == 1 # all lines
|
654
|
+
[line[0]] * 4
|
655
|
+
when line.length == 2
|
656
|
+
[line[0], line[1], line[0], line[1]]
|
657
|
+
when line.length == 3
|
658
|
+
[line[0], line[1], line[2], line[1]]
|
659
|
+
when line.length == 4
|
660
|
+
[line[0], line[1], line[2], line[3]]
|
661
|
+
else
|
662
|
+
raise ArgumentError, "border_line must be one of :solid, :dashed, "
|
663
|
+
":dotted or an array [v,h] or [t,r,b,l]"
|
664
|
+
end
|
665
|
+
end
|
666
|
+
alias_method :border_lines=, :border_line=
|
667
|
+
|
668
|
+
def border_top_line
|
669
|
+
@borders.include?(:top) ? @border_lines[0] : 0
|
670
|
+
end
|
671
|
+
|
672
|
+
def border_top_line=(val)
|
673
|
+
@border_lines[0] = val
|
674
|
+
end
|
675
|
+
|
676
|
+
def border_right_line
|
677
|
+
@borders.include?(:right) ? @border_lines[1] : 0
|
678
|
+
end
|
679
|
+
|
680
|
+
def border_right_line=(val)
|
681
|
+
@border_lines[1] = val
|
682
|
+
end
|
683
|
+
|
684
|
+
def border_bottom_line
|
685
|
+
@borders.include?(:bottom) ? @border_lines[2] : 0
|
686
|
+
end
|
687
|
+
|
688
|
+
def border_bottom_line=(val)
|
689
|
+
@border_lines[2] = val
|
690
|
+
end
|
691
|
+
|
692
|
+
def border_left_line
|
693
|
+
@borders.include?(:left) ? @border_lines[3] : 0
|
694
|
+
end
|
695
|
+
|
696
|
+
def border_left_line=(val)
|
697
|
+
@border_lines[3] = val
|
698
|
+
end
|
699
|
+
|
700
|
+
# Draws the cell's background color.
|
701
|
+
#
|
702
|
+
def draw_background(pt)
|
703
|
+
if defined?(@background_color) && @background_color
|
704
|
+
@pdf.mask(:fill_color) do
|
705
|
+
@pdf.fill_color @background_color
|
706
|
+
@pdf.fill_rectangle pt, width, height
|
707
|
+
end
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
# Draws borders around the cell. Borders are centered on the bounds of
|
712
|
+
# the cell outside of any padding, so the caller is responsible for
|
713
|
+
# setting appropriate padding to ensure the border does not overlap with
|
714
|
+
# cell content.
|
715
|
+
#
|
716
|
+
def draw_borders(pt)
|
717
|
+
x, y = pt
|
718
|
+
|
719
|
+
@pdf.mask(:line_width, :stroke_color) do
|
720
|
+
@borders.each do |border|
|
721
|
+
idx = {:top => 0, :right => 1, :bottom => 2, :left => 3}[border]
|
722
|
+
border_color = @border_colors[idx]
|
723
|
+
border_width = @border_widths[idx]
|
724
|
+
border_line = @border_lines[idx]
|
725
|
+
|
726
|
+
next if border_width <= 0
|
727
|
+
|
728
|
+
# Left and right borders are drawn one-half border beyond the center
|
729
|
+
# of the corner, so that the corners end up square.
|
730
|
+
from, to = case border
|
731
|
+
when :top
|
732
|
+
[[x, y], [x+width, y]]
|
733
|
+
when :bottom
|
734
|
+
[[x, y-height], [x+width, y-height]]
|
735
|
+
when :left
|
736
|
+
[[x, y + (border_top_width / 2.0)],
|
737
|
+
[x, y - height - (border_bottom_width / 2.0)]]
|
738
|
+
when :right
|
739
|
+
[[x+width, y + (border_top_width / 2.0)],
|
740
|
+
[x+width, y - height - (border_bottom_width / 2.0)]]
|
741
|
+
end
|
742
|
+
|
743
|
+
case border_line
|
744
|
+
when :dashed
|
745
|
+
@pdf.dash border_width * 4
|
746
|
+
when :dotted
|
747
|
+
@pdf.dash border_width, :space => border_width * 2
|
748
|
+
when :solid
|
749
|
+
# normal line style
|
750
|
+
else
|
751
|
+
raise ArgumentError, "border_line must be :solid, :dotted or" +
|
752
|
+
" :dashed"
|
753
|
+
end
|
754
|
+
|
755
|
+
@pdf.line_width = border_width
|
756
|
+
@pdf.stroke_color = border_color
|
757
|
+
@pdf.stroke_line(from, to)
|
758
|
+
@pdf.undash
|
759
|
+
end
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
# Draws cell content within the cell's bounding box. Must be implemented
|
764
|
+
# in subclasses.
|
765
|
+
#
|
766
|
+
def draw_content
|
767
|
+
raise NotImplementedError, "subclasses must implement draw_content"
|
768
|
+
end
|
769
|
+
|
770
|
+
end
|
771
|
+
end
|
772
|
+
end
|