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,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# image.rb: Table image cells.
|
4
|
+
#
|
5
|
+
# Copyright September 2010, Brad Ediger. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
module Prawn
|
9
|
+
class Table
|
10
|
+
class Cell
|
11
|
+
|
12
|
+
# @private
|
13
|
+
class Image < Cell
|
14
|
+
|
15
|
+
def initialize(pdf, point, options={})
|
16
|
+
@image_options = {}
|
17
|
+
super
|
18
|
+
|
19
|
+
@pdf_object, @image_info = @pdf.build_image_object(@file)
|
20
|
+
@natural_width, @natural_height = @image_info.calc_image_dimensions(
|
21
|
+
@image_options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def image=(file)
|
25
|
+
@file = file
|
26
|
+
end
|
27
|
+
|
28
|
+
def scale=(s)
|
29
|
+
@image_options[:scale] = s
|
30
|
+
end
|
31
|
+
|
32
|
+
def fit=(f)
|
33
|
+
@image_options[:fit] = f
|
34
|
+
end
|
35
|
+
|
36
|
+
def image_height=(h)
|
37
|
+
@image_options[:height] = h
|
38
|
+
end
|
39
|
+
|
40
|
+
def image_width=(w)
|
41
|
+
@image_options[:width] = w
|
42
|
+
end
|
43
|
+
|
44
|
+
def position=(p)
|
45
|
+
@image_options[:position] = p
|
46
|
+
end
|
47
|
+
|
48
|
+
def vposition=(vp)
|
49
|
+
@image_options[:vposition] = vp
|
50
|
+
end
|
51
|
+
|
52
|
+
def natural_content_width
|
53
|
+
@natural_width
|
54
|
+
end
|
55
|
+
|
56
|
+
def natural_content_height
|
57
|
+
@natural_height
|
58
|
+
end
|
59
|
+
|
60
|
+
# Draw the image on the page.
|
61
|
+
#
|
62
|
+
def draw_content
|
63
|
+
@pdf.embed_image(@pdf_object, @image_info, @image_options)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Accessors for using a Cell inside a Table.
|
4
|
+
#
|
5
|
+
# Contributed by Brad Ediger.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
module Prawn
|
10
|
+
class Table
|
11
|
+
|
12
|
+
class Cell
|
13
|
+
|
14
|
+
# This module extends Cell objects when they are used in a table (as
|
15
|
+
# opposed to standalone). Its properties apply to cells-in-tables but not
|
16
|
+
# cells themselves.
|
17
|
+
#
|
18
|
+
# @private
|
19
|
+
module InTable
|
20
|
+
|
21
|
+
# Row number (0-based).
|
22
|
+
#
|
23
|
+
attr_accessor :row
|
24
|
+
|
25
|
+
# Column number (0-based).
|
26
|
+
#
|
27
|
+
attr_accessor :column
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# span_dummy.rb: Placeholder for non-master spanned cells.
|
4
|
+
#
|
5
|
+
# Copyright December 2011, Brad Ediger. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
module Prawn
|
9
|
+
class Table
|
10
|
+
class Cell
|
11
|
+
|
12
|
+
# A Cell object used to represent all but the topmost cell in a span
|
13
|
+
# group.
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
class SpanDummy < Cell
|
17
|
+
def initialize(pdf, master_cell)
|
18
|
+
super(pdf, [0, pdf.cursor])
|
19
|
+
@master_cell = master_cell
|
20
|
+
@padding = [0, 0, 0, 0]
|
21
|
+
end
|
22
|
+
|
23
|
+
# By default, a span dummy will never increase the height demand.
|
24
|
+
#
|
25
|
+
def natural_content_height
|
26
|
+
0
|
27
|
+
end
|
28
|
+
|
29
|
+
# By default, a span dummy will never increase the width demand.
|
30
|
+
#
|
31
|
+
def natural_content_width
|
32
|
+
0
|
33
|
+
end
|
34
|
+
|
35
|
+
def avg_spanned_min_width
|
36
|
+
@master_cell.avg_spanned_min_width
|
37
|
+
end
|
38
|
+
|
39
|
+
# Dummy cells have nothing to draw.
|
40
|
+
#
|
41
|
+
def draw_borders(pt)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Dummy cells have nothing to draw.
|
45
|
+
#
|
46
|
+
def draw_bounded_content(pt)
|
47
|
+
end
|
48
|
+
|
49
|
+
def padding_right=(val)
|
50
|
+
@master_cell.padding_right = val if rightmost?
|
51
|
+
end
|
52
|
+
|
53
|
+
def padding_bottom=(val)
|
54
|
+
@master_cell.padding_bottom = val if bottommost?
|
55
|
+
end
|
56
|
+
|
57
|
+
def border_right_color=(val)
|
58
|
+
@master_cell.border_right_color = val if rightmost?
|
59
|
+
end
|
60
|
+
|
61
|
+
def border_bottom_color=(val)
|
62
|
+
@master_cell.border_bottom_color = val if bottommost?
|
63
|
+
end
|
64
|
+
|
65
|
+
def border_right_width=(val)
|
66
|
+
@master_cell.border_right_width = val if rightmost?
|
67
|
+
end
|
68
|
+
|
69
|
+
def border_bottom_width=(val)
|
70
|
+
@master_cell.border_bottom_width = val if bottommost?
|
71
|
+
end
|
72
|
+
|
73
|
+
def background_color
|
74
|
+
@master_cell.background_color
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Are we on the right border of the span?
|
80
|
+
#
|
81
|
+
def rightmost?
|
82
|
+
@column == @master_cell.column + @master_cell.colspan - 1
|
83
|
+
end
|
84
|
+
|
85
|
+
# Are we on the bottom border of the span?
|
86
|
+
#
|
87
|
+
def bottommost?
|
88
|
+
@row == @master_cell.row + @master_cell.rowspan - 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# subtable.rb: Yo dawg.
|
4
|
+
#
|
5
|
+
# Copyright January 2010, Brad Ediger. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
module Prawn
|
9
|
+
class Table
|
10
|
+
class Cell
|
11
|
+
|
12
|
+
# A Cell that contains another table.
|
13
|
+
#
|
14
|
+
# @private
|
15
|
+
class Subtable < Cell
|
16
|
+
|
17
|
+
attr_reader :subtable
|
18
|
+
|
19
|
+
def initialize(pdf, point, options={})
|
20
|
+
super
|
21
|
+
@subtable = options[:content]
|
22
|
+
|
23
|
+
# Subtable padding defaults to zero
|
24
|
+
@padding = [0, 0, 0, 0]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sets the text color of the entire subtable.
|
28
|
+
#
|
29
|
+
def text_color=(color)
|
30
|
+
@subtable.cells.text_color = color
|
31
|
+
end
|
32
|
+
|
33
|
+
# Proxied to subtable.
|
34
|
+
#
|
35
|
+
def natural_content_width
|
36
|
+
@subtable.cells.width
|
37
|
+
end
|
38
|
+
|
39
|
+
# Proxied to subtable.
|
40
|
+
#
|
41
|
+
def min_width
|
42
|
+
@subtable.cells.min_width
|
43
|
+
end
|
44
|
+
|
45
|
+
# Proxied to subtable.
|
46
|
+
#
|
47
|
+
def max_width
|
48
|
+
@subtable.cells.max_width
|
49
|
+
end
|
50
|
+
|
51
|
+
# Proxied to subtable.
|
52
|
+
#
|
53
|
+
def natural_content_height
|
54
|
+
@subtable.cells.height
|
55
|
+
end
|
56
|
+
|
57
|
+
# Draws the subtable.
|
58
|
+
#
|
59
|
+
def draw_content
|
60
|
+
@subtable.draw
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# text.rb: Text table cells.
|
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
|
+
module Prawn
|
9
|
+
class Table
|
10
|
+
class Cell
|
11
|
+
|
12
|
+
# A Cell that contains text. Has some limited options to set font family,
|
13
|
+
# size, and style.
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
class Text < Cell
|
17
|
+
|
18
|
+
TextOptions = [:inline_format, :kerning, :size, :align, :valign,
|
19
|
+
:rotate, :rotate_around, :leading, :single_line, :skip_encoding,
|
20
|
+
:overflow, :min_font_size]
|
21
|
+
|
22
|
+
TextOptions.each do |option|
|
23
|
+
define_method("#{option}=") { |v| @text_options[option] = v }
|
24
|
+
define_method(option) { @text_options[option] }
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_writer :font, :text_color
|
28
|
+
|
29
|
+
def initialize(pdf, point, options={})
|
30
|
+
@text_options = {}
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the font that will be used to draw this cell.
|
35
|
+
#
|
36
|
+
def font
|
37
|
+
with_font { @pdf.font }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets the style of the font in use. Equivalent to the Text::Box
|
41
|
+
# +style+ option, but we already have a style method.
|
42
|
+
#
|
43
|
+
def font_style=(style)
|
44
|
+
@text_options[:style] = style
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the width of this text with no wrapping. This will be far off
|
48
|
+
# from the final width if the text is long.
|
49
|
+
#
|
50
|
+
def natural_content_width
|
51
|
+
@natural_content_width ||= [styled_width_of(@content), @pdf.bounds.width].min
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the natural height of this block of text, wrapped to the
|
55
|
+
# preset width.
|
56
|
+
#
|
57
|
+
def natural_content_height
|
58
|
+
with_font do
|
59
|
+
b = text_box(:width => spanned_content_width + FPTolerance)
|
60
|
+
b.render(:dry_run => true)
|
61
|
+
b.height + b.line_gap
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Draws the text content into its bounding box.
|
66
|
+
#
|
67
|
+
def draw_content
|
68
|
+
with_font do
|
69
|
+
@pdf.move_down((@pdf.font.line_gap + @pdf.font.descender)/2)
|
70
|
+
with_text_color do
|
71
|
+
text_box(:width => spanned_content_width + FPTolerance,
|
72
|
+
:height => spanned_content_height + FPTolerance,
|
73
|
+
:at => [0, @pdf.cursor]).render
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_width_constraints
|
79
|
+
# Sets a reasonable minimum width. If the cell has any content, make
|
80
|
+
# sure we have enough width to be at least one character wide. This is
|
81
|
+
# a bit of a hack, but it should work well enough.
|
82
|
+
unless defined?(@min_width) && @min_width
|
83
|
+
min_content_width = [natural_content_width, styled_width_of_single_character].min
|
84
|
+
@min_width = padding_left + padding_right + min_content_width
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
def with_font
|
92
|
+
@pdf.save_font do
|
93
|
+
options = {}
|
94
|
+
options[:style] = @text_options[:style] if @text_options[:style]
|
95
|
+
options[:style] ||= @pdf.font.options[:style] if @pdf.font.options[:style]
|
96
|
+
|
97
|
+
@pdf.font(defined?(@font) && @font || @pdf.font.family, options)
|
98
|
+
|
99
|
+
yield
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def with_text_color
|
104
|
+
if defined?(@text_color) && @text_color
|
105
|
+
begin
|
106
|
+
old_color = @pdf.fill_color || '000000'
|
107
|
+
@pdf.fill_color(@text_color)
|
108
|
+
yield
|
109
|
+
ensure
|
110
|
+
@pdf.fill_color(old_color)
|
111
|
+
end
|
112
|
+
else
|
113
|
+
yield
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def text_box(extra_options={})
|
118
|
+
if p = @text_options[:inline_format]
|
119
|
+
p = [] unless p.is_a?(Array)
|
120
|
+
options = @text_options.dup
|
121
|
+
options.delete(:inline_format)
|
122
|
+
options.merge!(extra_options)
|
123
|
+
options[:document] = @pdf
|
124
|
+
|
125
|
+
array = @pdf.text_formatter.format(@content, *p)
|
126
|
+
::Prawn::Text::Formatted::Box.new(array,
|
127
|
+
options.merge(extra_options).merge(:document => @pdf))
|
128
|
+
else
|
129
|
+
::Prawn::Text::Box.new(@content, @text_options.merge(extra_options).
|
130
|
+
merge(:document => @pdf))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns the width of +text+ under the given text options.
|
135
|
+
#
|
136
|
+
def styled_width_of(text)
|
137
|
+
@pdf.width_of(text, @text_options)
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
# Returns the greatest possible width of any single character
|
143
|
+
# under the given text options.
|
144
|
+
# (We use this to determine the minimum width of a table cell)
|
145
|
+
# (Although we currently determine this by measuring "M", it should really
|
146
|
+
# use whichever character is widest under the current font)
|
147
|
+
#
|
148
|
+
def styled_width_of_single_character
|
149
|
+
styled_width_of("M")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# cells.rb: Methods for accessing rows, columns, and cells of a Prawn::Table.
|
4
|
+
#
|
5
|
+
# Copyright December 2009, Brad Ediger. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
module Prawn
|
10
|
+
class Table
|
11
|
+
# Selects the given rows (0-based) for styling. Returns a Cells object --
|
12
|
+
# see the documentation on Cells for things you can do with cells.
|
13
|
+
#
|
14
|
+
def rows(row_spec)
|
15
|
+
cells.rows(row_spec)
|
16
|
+
end
|
17
|
+
alias_method :row, :rows
|
18
|
+
|
19
|
+
# Selects the given columns (0-based) for styling. Returns a Cells object
|
20
|
+
# -- see the documentation on Cells for things you can do with cells.
|
21
|
+
#
|
22
|
+
def columns(col_spec)
|
23
|
+
cells.columns(col_spec)
|
24
|
+
end
|
25
|
+
alias_method :column, :columns
|
26
|
+
|
27
|
+
# Represents a selection of cells to be styled. Operations on a CellProxy
|
28
|
+
# can be chained, and cell properties can be set one-for-all on the proxy.
|
29
|
+
#
|
30
|
+
# To set vertical borders only:
|
31
|
+
#
|
32
|
+
# table.cells.borders = [:left, :right]
|
33
|
+
#
|
34
|
+
# To highlight a rectangular area of the table:
|
35
|
+
#
|
36
|
+
# table.rows(1..3).columns(2..4).background_color = 'ff0000'
|
37
|
+
#
|
38
|
+
class Cells < Array
|
39
|
+
|
40
|
+
# @group Experimental API
|
41
|
+
|
42
|
+
# Limits selection to the given row or rows. +row_spec+ can be anything
|
43
|
+
# that responds to the === operator selecting a set of 0-based row
|
44
|
+
# numbers; most commonly a number or a range.
|
45
|
+
#
|
46
|
+
# table.row(0) # selects first row
|
47
|
+
# table.rows(3..4) # selects rows four and five
|
48
|
+
#
|
49
|
+
def rows(row_spec)
|
50
|
+
index_cells unless defined?(@indexed) && @indexed
|
51
|
+
row_spec = transform_spec(row_spec, @first_row, @row_count)
|
52
|
+
Cells.new(@rows[row_spec] ||= select { |c|
|
53
|
+
row_spec.respond_to?(:include?) ?
|
54
|
+
row_spec.include?(c.row) : row_spec === c.row })
|
55
|
+
end
|
56
|
+
alias_method :row, :rows
|
57
|
+
|
58
|
+
# Returns the number of rows in the list.
|
59
|
+
#
|
60
|
+
def row_count
|
61
|
+
index_cells unless defined?(@indexed) && @indexed
|
62
|
+
@row_count
|
63
|
+
end
|
64
|
+
|
65
|
+
# Limits selection to the given column or columns. +col_spec+ can be
|
66
|
+
# anything that responds to the === operator selecting a set of 0-based
|
67
|
+
# column numbers; most commonly a number or a range.
|
68
|
+
#
|
69
|
+
# table.column(0) # selects first column
|
70
|
+
# table.columns(3..4) # selects columns four and five
|
71
|
+
#
|
72
|
+
def columns(col_spec)
|
73
|
+
index_cells unless defined?(@indexed) && @indexed
|
74
|
+
col_spec = transform_spec(col_spec, @first_column, @column_count)
|
75
|
+
Cells.new(@columns[col_spec] ||= select { |c|
|
76
|
+
col_spec.respond_to?(:include?) ?
|
77
|
+
col_spec.include?(c.column) : col_spec === c.column })
|
78
|
+
end
|
79
|
+
alias_method :column, :columns
|
80
|
+
|
81
|
+
# Returns the number of columns in the list.
|
82
|
+
#
|
83
|
+
def column_count
|
84
|
+
index_cells unless defined?(@indexed) && @indexed
|
85
|
+
@column_count
|
86
|
+
end
|
87
|
+
|
88
|
+
# Allows you to filter the given cells by arbitrary properties.
|
89
|
+
#
|
90
|
+
# table.column(4).filter { |cell| cell.content =~ /Yes/ }.
|
91
|
+
# background_color = '00ff00'
|
92
|
+
#
|
93
|
+
def filter(&block)
|
94
|
+
Cells.new(select(&block))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Retrieves a cell based on its 0-based row and column. Returns an
|
98
|
+
# individual Cell, not a Cells collection.
|
99
|
+
#
|
100
|
+
# table.cells[0, 0].content # => "First cell content"
|
101
|
+
#
|
102
|
+
def [](row, col)
|
103
|
+
return nil if empty?
|
104
|
+
index_cells unless defined?(@indexed) && @indexed
|
105
|
+
row_array, col_array = @rows[@first_row + row] || [], @columns[@first_column + col] || []
|
106
|
+
if row_array.length < col_array.length
|
107
|
+
row_array.find { |c| c.column == @first_column + col }
|
108
|
+
else
|
109
|
+
col_array.find { |c| c.row == @first_row + row }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Puts a cell in the collection at the given position. Internal use only.
|
114
|
+
#
|
115
|
+
def []=(row, col, cell) # :nodoc:
|
116
|
+
cell.extend(Cell::InTable)
|
117
|
+
cell.row = row
|
118
|
+
cell.column = col
|
119
|
+
|
120
|
+
if defined?(@indexed) && @indexed
|
121
|
+
(@rows[row] ||= []) << cell
|
122
|
+
(@columns[col] ||= []) << cell
|
123
|
+
@first_row = row if !@first_row || row < @first_row
|
124
|
+
@first_column = col if !@first_column || col < @first_column
|
125
|
+
@row_count = @rows.size
|
126
|
+
@column_count = @columns.size
|
127
|
+
end
|
128
|
+
|
129
|
+
self << cell
|
130
|
+
end
|
131
|
+
|
132
|
+
# Supports setting multiple properties at once.
|
133
|
+
#
|
134
|
+
# table.cells.style(:padding => 0, :border_width => 2)
|
135
|
+
#
|
136
|
+
# is the same as:
|
137
|
+
#
|
138
|
+
# table.cells.padding = 0
|
139
|
+
# table.cells.border_width = 2
|
140
|
+
#
|
141
|
+
# You can also pass a block, which will be called for each cell in turn.
|
142
|
+
# This allows you to set more complicated properties:
|
143
|
+
#
|
144
|
+
# table.cells.style { |cell| cell.border_width += 12 }
|
145
|
+
#
|
146
|
+
def style(options={}, &block)
|
147
|
+
each do |cell|
|
148
|
+
next if cell.is_a?(Cell::SpanDummy)
|
149
|
+
cell.style(options, &block)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns the total width of all columns in the selected set.
|
154
|
+
#
|
155
|
+
def width
|
156
|
+
widths = {}
|
157
|
+
each do |cell|
|
158
|
+
per_cell_width = cell.width_ignoring_span.to_f / cell.colspan
|
159
|
+
cell.colspan.times do |n|
|
160
|
+
widths[cell.column+n] = [widths[cell.column+n], per_cell_width].
|
161
|
+
compact.max
|
162
|
+
end
|
163
|
+
end
|
164
|
+
widths.values.inject(0, &:+)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns minimum width required to contain cells in the set.
|
168
|
+
#
|
169
|
+
def min_width
|
170
|
+
aggregate_cell_values(:column, :avg_spanned_min_width, :max)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Returns maximum width that can contain cells in the set.
|
174
|
+
#
|
175
|
+
def max_width
|
176
|
+
aggregate_cell_values(:column, :max_width_ignoring_span, :max)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns the total height of all rows in the selected set.
|
180
|
+
#
|
181
|
+
def height
|
182
|
+
aggregate_cell_values(:row, :height_ignoring_span, :max)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Supports setting arbitrary properties on a group of cells.
|
186
|
+
#
|
187
|
+
# table.cells.row(3..6).background_color = 'cc0000'
|
188
|
+
#
|
189
|
+
def method_missing(id, *args, &block)
|
190
|
+
if id.to_s =~ /=\z/
|
191
|
+
each { |c| c.send(id, *args, &block) if c.respond_to?(id) }
|
192
|
+
else
|
193
|
+
super
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
protected
|
198
|
+
|
199
|
+
# Defers indexing until rows() or columns() is actually called on the
|
200
|
+
# Cells object. Without this, we would needlessly index the leaf nodes of
|
201
|
+
# the object graph, the ones that are only there to be iterated over.
|
202
|
+
#
|
203
|
+
# Make sure to call this before using @rows or @columns.
|
204
|
+
#
|
205
|
+
def index_cells
|
206
|
+
@rows = {}
|
207
|
+
@columns = {}
|
208
|
+
|
209
|
+
each do |cell|
|
210
|
+
@rows[cell.row] ||= []
|
211
|
+
@rows[cell.row] << cell
|
212
|
+
|
213
|
+
@columns[cell.column] ||= []
|
214
|
+
@columns[cell.column] << cell
|
215
|
+
end
|
216
|
+
|
217
|
+
@first_row = @rows.keys.min
|
218
|
+
@first_column = @columns.keys.min
|
219
|
+
|
220
|
+
@row_count = @rows.size
|
221
|
+
@column_count = @columns.size
|
222
|
+
|
223
|
+
@indexed = true
|
224
|
+
end
|
225
|
+
|
226
|
+
# Sum up a min/max value over rows or columns in the cells selected.
|
227
|
+
# Takes the min/max (per +aggregate+) of the result of sending +meth+ to
|
228
|
+
# each cell, grouped by +row_or_column+.
|
229
|
+
#
|
230
|
+
def aggregate_cell_values(row_or_column, meth, aggregate)
|
231
|
+
ColumnWidthCalculator.new(self).aggregate_cell_values(row_or_column, meth, aggregate)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Transforms +spec+, a column / row specification, into an object that
|
235
|
+
# can be compared against a row or column number using ===. Normalizes
|
236
|
+
# negative indices to be positive, given a total size of +total+. The
|
237
|
+
# first row/column is indicated by +first+; this value is considered row
|
238
|
+
# or column 0.
|
239
|
+
#
|
240
|
+
def transform_spec(spec, first, total)
|
241
|
+
case spec
|
242
|
+
when Range
|
243
|
+
transform_spec(spec.begin, first, total) ..
|
244
|
+
transform_spec(spec.end, first, total)
|
245
|
+
when Integer
|
246
|
+
spec < 0 ? (first + total + spec) : first + spec
|
247
|
+
when Enumerable
|
248
|
+
spec.map { |x| first + x }
|
249
|
+
else # pass through
|
250
|
+
raise "Don't understand spec #{spec.inspect}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|