prawn-table 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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