prawn-table 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -0
  3. data/GPLv2 +340 -0
  4. data/GPLv3 +674 -0
  5. data/Gemfile +5 -0
  6. data/LICENSE +56 -0
  7. data/lib/prawn/table.rb +641 -0
  8. data/lib/prawn/table/cell.rb +772 -0
  9. data/lib/prawn/table/cell/image.rb +69 -0
  10. data/lib/prawn/table/cell/in_table.rb +33 -0
  11. data/lib/prawn/table/cell/span_dummy.rb +93 -0
  12. data/lib/prawn/table/cell/subtable.rb +66 -0
  13. data/lib/prawn/table/cell/text.rb +154 -0
  14. data/lib/prawn/table/cells.rb +255 -0
  15. data/lib/prawn/table/column_width_calculator.rb +182 -0
  16. data/manual/contents.rb +13 -0
  17. data/manual/example_helper.rb +8 -0
  18. data/manual/table/basic_block.rb +53 -0
  19. data/manual/table/before_rendering_page.rb +26 -0
  20. data/manual/table/cell_border_lines.rb +24 -0
  21. data/manual/table/cell_borders_and_bg.rb +31 -0
  22. data/manual/table/cell_dimensions.rb +30 -0
  23. data/manual/table/cell_text.rb +38 -0
  24. data/manual/table/column_widths.rb +30 -0
  25. data/manual/table/content_and_subtables.rb +39 -0
  26. data/manual/table/creation.rb +27 -0
  27. data/manual/table/filtering.rb +36 -0
  28. data/manual/table/flow_and_header.rb +17 -0
  29. data/manual/table/image_cells.rb +33 -0
  30. data/manual/table/position.rb +29 -0
  31. data/manual/table/row_colors.rb +20 -0
  32. data/manual/table/span.rb +30 -0
  33. data/manual/table/style.rb +22 -0
  34. data/manual/table/table.rb +52 -0
  35. data/manual/table/width.rb +27 -0
  36. data/prawn-table.gemspec +48 -0
  37. data/spec/cell_spec.rb +629 -0
  38. data/spec/extensions/encoding_helpers.rb +11 -0
  39. data/spec/extensions/mocha.rb +46 -0
  40. data/spec/spec_helper.rb +53 -0
  41. data/spec/table/span_dummy_spec.rb +17 -0
  42. data/spec/table_spec.rb +1527 -0
  43. metadata +240 -0
@@ -0,0 +1,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