prawn-table-continued 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -0
  3. data/GPLv2 +340 -0
  4. data/GPLv3 +674 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +56 -0
  7. data/lib/prawn/table/cell/image.rb +69 -0
  8. data/lib/prawn/table/cell/in_table.rb +33 -0
  9. data/lib/prawn/table/cell/span_dummy.rb +93 -0
  10. data/lib/prawn/table/cell/subtable.rb +66 -0
  11. data/lib/prawn/table/cell/text.rb +155 -0
  12. data/lib/prawn/table/cell.rb +787 -0
  13. data/lib/prawn/table/cells.rb +261 -0
  14. data/lib/prawn/table/column_width_calculator.rb +182 -0
  15. data/lib/prawn/table/version.rb +5 -0
  16. data/lib/prawn/table.rb +711 -0
  17. data/manual/contents.rb +13 -0
  18. data/manual/example_helper.rb +8 -0
  19. data/manual/images/prawn.png +0 -0
  20. data/manual/images/stef.jpg +0 -0
  21. data/manual/table/basic_block.rb +53 -0
  22. data/manual/table/before_rendering_page.rb +26 -0
  23. data/manual/table/cell_border_lines.rb +24 -0
  24. data/manual/table/cell_borders_and_bg.rb +31 -0
  25. data/manual/table/cell_dimensions.rb +36 -0
  26. data/manual/table/cell_text.rb +38 -0
  27. data/manual/table/column_widths.rb +30 -0
  28. data/manual/table/content_and_subtables.rb +39 -0
  29. data/manual/table/creation.rb +27 -0
  30. data/manual/table/filtering.rb +36 -0
  31. data/manual/table/flow_and_header.rb +17 -0
  32. data/manual/table/image_cells.rb +33 -0
  33. data/manual/table/position.rb +29 -0
  34. data/manual/table/row_colors.rb +20 -0
  35. data/manual/table/span.rb +30 -0
  36. data/manual/table/style.rb +33 -0
  37. data/manual/table/table.rb +52 -0
  38. data/manual/table/width.rb +27 -0
  39. data/prawn-table.gemspec +36 -0
  40. data/spec/cell_spec.rb +652 -0
  41. data/spec/extensions/encoding_helpers.rb +11 -0
  42. data/spec/extensions/file_fixture_helper.rb +15 -0
  43. data/spec/fixtures/files/prawn.png +0 -0
  44. data/spec/spec_helper.rb +50 -0
  45. data/spec/table/span_dummy_spec.rb +26 -0
  46. data/spec/table_spec.rb +1626 -0
  47. metadata +234 -0
@@ -0,0 +1,787 @@
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
+
165
+ return Cell::Image.new(pdf, at, content) if content.is_a?(Hash) && content[:image]
166
+
167
+ if content.is_a?(Hash)
168
+ options.update(content)
169
+ content = options[:content]
170
+ end
171
+
172
+ content = content.to_s if stringify_content?(content)
173
+ options[:content] = content
174
+
175
+ case content
176
+ when Prawn::Table::Cell
177
+ content
178
+ when String
179
+ Cell::Text.new(pdf, at, options)
180
+ when Prawn::Table
181
+ Cell::Subtable.new(pdf, at, options)
182
+ when Array
183
+ subtable = Prawn::Table.new(options[:content], pdf, {})
184
+ Cell::Subtable.new(pdf, at, options.merge(:content => subtable))
185
+ else
186
+ raise Errors::UnrecognizedTableContent
187
+ end
188
+ end
189
+
190
+ def self.stringify_content?(content)
191
+ return true if content.nil?
192
+ return true if content.kind_of?(Numeric)
193
+ return true if content.kind_of?(Date)
194
+ return true if content.kind_of?(Time)
195
+
196
+ false
197
+ end
198
+
199
+ # A small amount added to the bounding box width to cover over floating-
200
+ # point errors when round-tripping from content_width to width and back.
201
+ # This does not change cell positioning; it only slightly expands each
202
+ # cell's bounding box width so that rounding error does not prevent a cell
203
+ # from rendering.
204
+ #
205
+ FPTolerance = 1
206
+
207
+ # Sets up a cell on the document +pdf+, at the given x/y location +point+,
208
+ # with the given +options+. Cell, like Table, follows the "options set
209
+ # accessors" paradigm (see "Options" under the Table documentation), so
210
+ # any cell accessor <tt>cell.foo = :bar</tt> can be set by providing the
211
+ # option <tt>:foo => :bar</tt> here.
212
+ #
213
+ def initialize(pdf, point, options={})
214
+ @pdf = pdf
215
+ @point = point
216
+
217
+ # Set defaults; these can be changed by options
218
+ @padding = [5, 5, 5, 5]
219
+ @borders = [:top, :bottom, :left, :right]
220
+ @border_widths = [1] * 4
221
+ @border_colors = ['000000'] * 4
222
+ @border_lines = [:solid] * 4
223
+ @colspan = 1
224
+ @rowspan = 1
225
+ @dummy_cells = []
226
+
227
+ style(options)
228
+
229
+ @initializer_run = true
230
+ end
231
+
232
+ # Supports setting multiple properties at once.
233
+ #
234
+ # cell.style(:padding => 0, :border_width => 2)
235
+ #
236
+ # is the same as:
237
+ #
238
+ # cell.padding = 0
239
+ # cell.border_width = 2
240
+ #
241
+ def style(options={}, &block)
242
+ options.each do |k, v|
243
+ send("#{k}=", v) if respond_to?("#{k}=")
244
+ end
245
+
246
+ # The block form supports running a single block for multiple cells, as
247
+ # in Cells#style.
248
+ block.call(self) if block
249
+ end
250
+
251
+ # Returns the width of the cell in its first column alone, ignoring any
252
+ # colspans.
253
+ #
254
+ def width_ignoring_span
255
+ # We can't ||= here because the FP error accumulates on the round-trip
256
+ # from #content_width.
257
+ defined?(@width) && @width || (content_width + padding_left + padding_right)
258
+ end
259
+
260
+ # Returns the cell's width in points, inclusive of padding. If the cell is
261
+ # the master cell of a colspan, returns the width of the entire span
262
+ # group.
263
+ #
264
+ def width
265
+ return width_ignoring_span if @colspan == 1 && @rowspan == 1
266
+
267
+ # We're in a span group; get the maximum width per column (including
268
+ # the master cell) and sum each column.
269
+ column_widths = Hash.new(0)
270
+ dummy_cells.each do |cell|
271
+ column_widths[cell.column] =
272
+ [column_widths[cell.column], cell.width].max
273
+ end
274
+ column_widths[column] = [column_widths[column], width_ignoring_span].max
275
+ column_widths.values.inject(0, &:+)
276
+ end
277
+
278
+ # Manually sets the cell's width, inclusive of padding.
279
+ #
280
+ def width=(w)
281
+ @width = @min_width = @max_width = w
282
+ end
283
+
284
+ # Returns the width of the bare content in the cell, excluding padding.
285
+ #
286
+ def content_width
287
+ if defined?(@width) && @width # manually set
288
+ return @width - padding_left - padding_right
289
+ end
290
+
291
+ natural_content_width
292
+ end
293
+
294
+ # Width of the entire span group.
295
+ #
296
+ def spanned_content_width
297
+ width - padding_left - padding_right
298
+ end
299
+
300
+ # Returns the width this cell would naturally take on, absent other
301
+ # constraints. Must be implemented in subclasses.
302
+ #
303
+ def natural_content_width
304
+ raise NotImplementedError,
305
+ "subclasses must implement natural_content_width"
306
+ end
307
+
308
+ # Returns the cell's height in points, inclusive of padding, in its first
309
+ # row only.
310
+ #
311
+ def height_ignoring_span
312
+ # We can't ||= here because the FP error accumulates on the round-trip
313
+ # from #content_height.
314
+ defined?(@height) && @height || (content_height + padding_top + padding_bottom)
315
+ end
316
+
317
+ # Returns the cell's height in points, inclusive of padding. If the cell
318
+ # is the master cell of a rowspan, returns the width of the entire span
319
+ # group.
320
+ #
321
+ def height
322
+ return height_ignoring_span if @colspan == 1 && @rowspan == 1
323
+
324
+ # We're in a span group; get the maximum height per row (including the
325
+ # master cell) and sum each row.
326
+ row_heights = Hash.new(0)
327
+ dummy_cells.each do |cell|
328
+ row_heights[cell.row] = [row_heights[cell.row], cell.height].max
329
+ end
330
+ row_heights[row] = [row_heights[row], height_ignoring_span].max
331
+ row_heights.values.inject(0, &:+)
332
+ end
333
+
334
+ # Returns the height of the bare content in the cell, excluding padding.
335
+ #
336
+ def content_height
337
+ if defined?(@height) && @height # manually set
338
+ return @height - padding_top - padding_bottom
339
+ end
340
+
341
+ natural_content_height
342
+ end
343
+
344
+ # Height of the entire span group.
345
+ #
346
+ def spanned_content_height
347
+ height - padding_top - padding_bottom
348
+ end
349
+
350
+ # Returns the height this cell would naturally take on, absent
351
+ # constraints. Must be implemented in subclasses.
352
+ #
353
+ def natural_content_height
354
+ raise NotImplementedError,
355
+ "subclasses must implement natural_content_height"
356
+ end
357
+
358
+ # Indicates the number of columns that this cell is to span. Defaults to
359
+ # 1.
360
+ #
361
+ # This must be provided as part of the table data, like so:
362
+ #
363
+ # pdf.table([["foo", {:content => "bar", :colspan => 2}]])
364
+ #
365
+ # Setting colspan from the initializer block is invalid because layout
366
+ # has already run. For example, this will NOT work:
367
+ #
368
+ # pdf.table([["foo", "bar"]]) { cells[0, 1].colspan = 2 }
369
+ #
370
+ def colspan=(span)
371
+ if defined?(@initializer_run) && @initializer_run
372
+ raise Prawn::Errors::InvalidTableSpan,
373
+ "colspan must be provided in the table's structure, never in the " +
374
+ "initialization block. See Prawn's documentation for details."
375
+ end
376
+
377
+ @colspan = span
378
+ end
379
+
380
+ # Indicates the number of rows that this cell is to span. Defaults to 1.
381
+ #
382
+ # This must be provided as part of the table data, like so:
383
+ #
384
+ # pdf.table([["foo", {:content => "bar", :rowspan => 2}], ["baz"]])
385
+ #
386
+ # Setting rowspan from the initializer block is invalid because layout
387
+ # has already run. For example, this will NOT work:
388
+ #
389
+ # pdf.table([["foo", "bar"], ["baz"]]) { cells[0, 1].rowspan = 2 }
390
+ #
391
+ def rowspan=(span)
392
+ if defined?(@initializer_run) && @initializer_run
393
+ raise Prawn::Errors::InvalidTableSpan,
394
+ "rowspan must be provided in the table's structure, never in the " +
395
+ "initialization block. See Prawn's documentation for details."
396
+ end
397
+
398
+ @rowspan = span
399
+ end
400
+
401
+ # Draws the cell onto the document. Pass in a point [x,y] to override the
402
+ # location at which the cell is drawn.
403
+ #
404
+ # If drawing a group of cells at known positions, look into
405
+ # Cell.draw_cells, which ensures that the backgrounds, borders, and
406
+ # content are all drawn in correct order so as not to overlap.
407
+ #
408
+ def draw(pt=[x, y])
409
+ Prawn::Table::Cell.draw_cells([[self, pt]])
410
+ end
411
+
412
+ # Given an array of pairs [cell, pt], draws each cell at its
413
+ # corresponding pt, making sure all backgrounds are behind all borders
414
+ # and content.
415
+ #
416
+ def self.draw_cells(cells)
417
+ cells.each do |cell, pt|
418
+ cell.set_width_constraints
419
+ cell.draw_background(pt)
420
+ end
421
+
422
+ cells.each do |cell, pt|
423
+ cell.draw_borders(pt)
424
+ cell.draw_bounded_content(pt)
425
+ end
426
+ end
427
+
428
+ # Draws the cell's content at the point provided.
429
+ #
430
+ def draw_bounded_content(pt)
431
+ @pdf.float do
432
+ @pdf.bounding_box([pt[0] + padding_left, pt[1] - padding_top],
433
+ :width => spanned_content_width + FPTolerance,
434
+ :height => spanned_content_height + FPTolerance) do
435
+ draw_content
436
+ end
437
+ end
438
+ end
439
+
440
+ # x-position of the cell within the parent bounds.
441
+ #
442
+ def x
443
+ @point[0]
444
+ end
445
+
446
+ # Set the x-position of the cell within the parent bounds.
447
+ #
448
+ def x=(val)
449
+ @point[0] = val
450
+ end
451
+
452
+ def relative_x
453
+ # Translate coordinates to the bounds we are in, since drawing is
454
+ # relative to the cursor, not ref_bounds.
455
+ x + @pdf.bounds.left_side - @pdf.bounds.absolute_left
456
+ end
457
+
458
+ # y-position of the cell within the parent bounds.
459
+ #
460
+ def y
461
+ @point[1]
462
+ end
463
+
464
+ # Set the y-position of the cell within the parent bounds.
465
+ #
466
+ def y=(val)
467
+ @point[1] = val
468
+ end
469
+
470
+ def relative_y(offset = 0)
471
+ y + offset - @pdf.bounds.absolute_bottom
472
+ end
473
+
474
+ # Sets padding on this cell. The argument can be one of:
475
+ #
476
+ # * an integer (sets all padding)
477
+ # * a two-element array [vertical, horizontal]
478
+ # * a three-element array [top, horizontal, bottom]
479
+ # * a four-element array [top, right, bottom, left]
480
+ #
481
+ def padding=(pad)
482
+ @padding = case
483
+ when pad.nil?
484
+ [0, 0, 0, 0]
485
+ when Numeric === pad # all padding
486
+ [pad, pad, pad, pad]
487
+ when pad.length == 2 # vert, horiz
488
+ [pad[0], pad[1], pad[0], pad[1]]
489
+ when pad.length == 3 # top, horiz, bottom
490
+ [pad[0], pad[1], pad[2], pad[1]]
491
+ when pad.length == 4 # top, right, bottom, left
492
+ [pad[0], pad[1], pad[2], pad[3]]
493
+ else
494
+ raise ArgumentError, ":padding must be a number or an array [v,h] " +
495
+ "or [t,r,b,l]"
496
+ end
497
+ end
498
+
499
+ def padding_top
500
+ @padding[0]
501
+ end
502
+
503
+ def padding_top=(val)
504
+ @padding[0] = val
505
+ end
506
+
507
+ def padding_right
508
+ @padding[1]
509
+ end
510
+
511
+ def padding_right=(val)
512
+ @padding[1] = val
513
+ end
514
+
515
+ def padding_bottom
516
+ @padding[2]
517
+ end
518
+
519
+ def padding_bottom=(val)
520
+ @padding[2] = val
521
+ end
522
+
523
+ def padding_left
524
+ @padding[3]
525
+ end
526
+
527
+ def padding_left=(val)
528
+ @padding[3] = val
529
+ end
530
+
531
+ # Sets border colors on this cell. The argument can be one of:
532
+ #
533
+ # * an integer (sets all colors)
534
+ # * a two-element array [vertical, horizontal]
535
+ # * a three-element array [top, horizontal, bottom]
536
+ # * a four-element array [top, right, bottom, left]
537
+ #
538
+ def border_color=(color)
539
+ @border_colors = case
540
+ when color.nil?
541
+ ["000000"] * 4
542
+ when String === color # all colors
543
+ [color, color, color, color]
544
+ when color.length == 2 # vert, horiz
545
+ [color[0], color[1], color[0], color[1]]
546
+ when color.length == 3 # top, horiz, bottom
547
+ [color[0], color[1], color[2], color[1]]
548
+ when color.length == 4 # top, right, bottom, left
549
+ [color[0], color[1], color[2], color[3]]
550
+ else
551
+ raise ArgumentError, ":border_color must be a string " +
552
+ "or an array [v,h] or [t,r,b,l]"
553
+ end
554
+ end
555
+ alias_method :border_colors=, :border_color=
556
+
557
+ def border_top_color
558
+ @border_colors[0]
559
+ end
560
+
561
+ def border_top_color=(val)
562
+ @border_colors[0] = val
563
+ end
564
+
565
+ def border_right_color
566
+ @border_colors[1]
567
+ end
568
+
569
+ def border_right_color=(val)
570
+ @border_colors[1] = val
571
+ end
572
+
573
+ def border_bottom_color
574
+ @border_colors[2]
575
+ end
576
+
577
+ def border_bottom_color=(val)
578
+ @border_colors[2] = val
579
+ end
580
+
581
+ def border_left_color
582
+ @border_colors[3]
583
+ end
584
+
585
+ def border_left_color=(val)
586
+ @border_colors[3] = val
587
+ end
588
+
589
+ # Sets border widths on this cell. The argument can be one of:
590
+ #
591
+ # * an integer (sets all widths)
592
+ # * a two-element array [vertical, horizontal]
593
+ # * a three-element array [top, horizontal, bottom]
594
+ # * a four-element array [top, right, bottom, left]
595
+ #
596
+ def border_width=(width)
597
+ @border_widths = case
598
+ when width.nil?
599
+ ["000000"] * 4
600
+ when Numeric === width # all widths
601
+ [width, width, width, width]
602
+ when width.length == 2 # vert, horiz
603
+ [width[0], width[1], width[0], width[1]]
604
+ when width.length == 3 # top, horiz, bottom
605
+ [width[0], width[1], width[2], width[1]]
606
+ when width.length == 4 # top, right, bottom, left
607
+ [width[0], width[1], width[2], width[3]]
608
+ else
609
+ raise ArgumentError, ":border_width must be a string " +
610
+ "or an array [v,h] or [t,r,b,l]"
611
+ end
612
+ end
613
+ alias_method :border_widths=, :border_width=
614
+
615
+ def border_top_width
616
+ @borders.include?(:top) ? @border_widths[0] : 0
617
+ end
618
+
619
+ def border_top_width=(val)
620
+ @border_widths[0] = val
621
+ end
622
+
623
+ def border_right_width
624
+ @borders.include?(:right) ? @border_widths[1] : 0
625
+ end
626
+
627
+ def border_right_width=(val)
628
+ @border_widths[1] = val
629
+ end
630
+
631
+ def border_bottom_width
632
+ @borders.include?(:bottom) ? @border_widths[2] : 0
633
+ end
634
+
635
+ def border_bottom_width=(val)
636
+ @border_widths[2] = val
637
+ end
638
+
639
+ def border_left_width
640
+ @borders.include?(:left) ? @border_widths[3] : 0
641
+ end
642
+
643
+ def border_left_width=(val)
644
+ @border_widths[3] = val
645
+ end
646
+
647
+ # Sets the cell's minimum and maximum width. Deferred until requested
648
+ # because padding and size can change.
649
+ #
650
+ def set_width_constraints
651
+ @min_width ||= padding_left + padding_right
652
+ @max_width ||= @pdf.bounds.width
653
+ end
654
+
655
+ # Sets border line style on this cell. The argument can be one of:
656
+ #
657
+ # Possible values are: :solid, :dashed, :dotted
658
+ #
659
+ # * one value (sets all lines)
660
+ # * a two-element array [vertical, horizontal]
661
+ # * a three-element array [top, horizontal, bottom]
662
+ # * a four-element array [top, right, bottom, left]
663
+ #
664
+ def border_line=(line)
665
+ @border_lines = case
666
+ when line.nil?
667
+ [:solid] * 4
668
+ when line.length == 1 # all lines
669
+ [line[0]] * 4
670
+ when line.length == 2
671
+ [line[0], line[1], line[0], line[1]]
672
+ when line.length == 3
673
+ [line[0], line[1], line[2], line[1]]
674
+ when line.length == 4
675
+ [line[0], line[1], line[2], line[3]]
676
+ else
677
+ raise ArgumentError, "border_line must be one of :solid, :dashed, "
678
+ ":dotted or an array [v,h] or [t,r,b,l]"
679
+ end
680
+ end
681
+ alias_method :border_lines=, :border_line=
682
+
683
+ def border_top_line
684
+ @borders.include?(:top) ? @border_lines[0] : 0
685
+ end
686
+
687
+ def border_top_line=(val)
688
+ @border_lines[0] = val
689
+ end
690
+
691
+ def border_right_line
692
+ @borders.include?(:right) ? @border_lines[1] : 0
693
+ end
694
+
695
+ def border_right_line=(val)
696
+ @border_lines[1] = val
697
+ end
698
+
699
+ def border_bottom_line
700
+ @borders.include?(:bottom) ? @border_lines[2] : 0
701
+ end
702
+
703
+ def border_bottom_line=(val)
704
+ @border_lines[2] = val
705
+ end
706
+
707
+ def border_left_line
708
+ @borders.include?(:left) ? @border_lines[3] : 0
709
+ end
710
+
711
+ def border_left_line=(val)
712
+ @border_lines[3] = val
713
+ end
714
+
715
+ # Draws the cell's background color.
716
+ #
717
+ def draw_background(pt)
718
+ return unless background_color
719
+
720
+ @pdf.mask(:fill_color) do
721
+ @pdf.fill_color background_color
722
+ @pdf.fill_rectangle pt, width, height
723
+ end
724
+ end
725
+
726
+ # Draws borders around the cell. Borders are centered on the bounds of
727
+ # the cell outside of any padding, so the caller is responsible for
728
+ # setting appropriate padding to ensure the border does not overlap with
729
+ # cell content.
730
+ #
731
+ def draw_borders(pt)
732
+ x, y = pt
733
+
734
+ @pdf.mask(:line_width, :stroke_color) do
735
+ @borders.each do |border|
736
+ idx = {:top => 0, :right => 1, :bottom => 2, :left => 3}[border]
737
+ border_color = @border_colors[idx]
738
+ border_width = @border_widths[idx]
739
+ border_line = @border_lines[idx]
740
+
741
+ next if border_width <= 0
742
+
743
+ # Left and right borders are drawn one-half border beyond the center
744
+ # of the corner, so that the corners end up square.
745
+ from, to = case border
746
+ when :top
747
+ [[x, y], [x+width, y]]
748
+ when :bottom
749
+ [[x, y-height], [x+width, y-height]]
750
+ when :left
751
+ [[x, y + (border_top_width / 2.0)],
752
+ [x, y - height - (border_bottom_width / 2.0)]]
753
+ when :right
754
+ [[x+width, y + (border_top_width / 2.0)],
755
+ [x+width, y - height - (border_bottom_width / 2.0)]]
756
+ end
757
+
758
+ case border_line
759
+ when :dashed
760
+ @pdf.dash border_width * 4
761
+ when :dotted
762
+ @pdf.dash border_width, :space => border_width * 2
763
+ when :solid
764
+ # normal line style
765
+ else
766
+ raise ArgumentError, "border_line must be :solid, :dotted or" +
767
+ " :dashed"
768
+ end
769
+
770
+ @pdf.line_width = border_width
771
+ @pdf.stroke_color = border_color
772
+ @pdf.stroke_line(from, to)
773
+ @pdf.undash
774
+ end
775
+ end
776
+ end
777
+
778
+ # Draws cell content within the cell's bounding box. Must be implemented
779
+ # in subclasses.
780
+ #
781
+ def draw_content
782
+ raise NotImplementedError, "subclasses must implement draw_content"
783
+ end
784
+
785
+ end
786
+ end
787
+ end