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,182 @@
1
+ # encoding: utf-8
2
+
3
+ module Prawn
4
+ class Table
5
+ # @private
6
+ class ColumnWidthCalculator
7
+ def initialize(cells)
8
+ @cells = cells
9
+
10
+ @widths_by_column = Hash.new(0)
11
+ @rows_with_a_span_dummy = Hash.new(false)
12
+
13
+ #calculate for each row if it includes a Cell:SpanDummy
14
+ @cells.each do |cell|
15
+ @rows_with_a_span_dummy[cell.row] = true if cell.is_a?(Cell::SpanDummy)
16
+ end
17
+ end
18
+
19
+ # does this row include a Cell:SpanDummy?
20
+ #
21
+ # @param row - the row that should be checked for Cell:SpanDummy elements
22
+ #
23
+ def has_a_span_dummy?(row)
24
+ @rows_with_a_span_dummy[row]
25
+ end
26
+
27
+ # helper method
28
+ # column widths are stored in the values array
29
+ # a cell may span cells whose value is only partly given
30
+ # this function handles this special case
31
+ #
32
+ # @param values - The columns widths calculated up until now
33
+ # @param cell - The current cell
34
+ # @param index - The current column
35
+ # @param meth - Meth (min/max); used to calculate values to be filled
36
+ #
37
+ def fill_values_if_needed(values, cell, index, meth)
38
+ #have all spanned indices been filled with a value?
39
+ #e.g. values[0], values[1] and values[2] don't return nil given a index of 0 and a colspan of 3
40
+ number_of_nil_values = 0
41
+ cell.colspan.times do |i|
42
+ number_of_nil_values += 1 if values[index+i].nil?
43
+ end
44
+
45
+ #nothing to do? because
46
+ #a) all values are filled
47
+ return values if number_of_nil_values == 0
48
+ #b) no values are filled
49
+ return values if number_of_nil_values == cell.colspan
50
+ #c) I am not sure why this line is needed FIXXME
51
+ #some test cases manage to this line even though there is no dummy cell in the row
52
+ #I'm not sure if this is a sign for a further underlying bug.
53
+ return values unless has_a_span_dummy?(cell.row)
54
+ #fill up the values array
55
+
56
+ #calculate the new sum
57
+ new_sum = cell.send(meth) * cell.colspan
58
+ #substract any calculated values
59
+ cell.colspan.times do |i|
60
+ new_sum -= values[index+i] unless values[index+i].nil?
61
+ end
62
+
63
+ #calculate value for the remaining - not yet filled - cells.
64
+ new_value = new_sum.to_f / number_of_nil_values
65
+ #fill the not yet filled cells
66
+ cell.colspan.times do |i|
67
+ values[index+i] = new_value if values[index+i].nil?
68
+ end
69
+ return values
70
+ end
71
+
72
+ def natural_widths
73
+ #calculate natural column width for all rows that do not include a span dummy
74
+ @cells.each do |cell|
75
+ unless has_a_span_dummy?(cell.row)
76
+ @widths_by_column[cell.column] =
77
+ [@widths_by_column[cell.column], cell.width.to_f].max
78
+ end
79
+ end
80
+
81
+ #integrate natural column widths for all rows that do include a span dummy
82
+ @cells.each do |cell|
83
+ next unless has_a_span_dummy?(cell.row)
84
+ #the width of a SpanDummy cell will be calculated by the "mother" cell
85
+ next if cell.is_a?(Cell::SpanDummy)
86
+
87
+ if cell.colspan == 1
88
+ @widths_by_column[cell.column] =
89
+ [@widths_by_column[cell.column], cell.width.to_f].max
90
+ else
91
+ #calculate the current with of all cells that will be spanned by the current cell
92
+ current_width_of_spanned_cells =
93
+ @widths_by_column.to_a[cell.column..(cell.column + cell.colspan - 1)]
94
+ .collect{|key, value| value}.inject(0, :+)
95
+
96
+ #update the Hash only if the new with is at least equal to the old one
97
+ #due to arithmetic errors we need to ignore a small difference in the new and the old sum
98
+ #the same had to be done in the column_widht_calculator#natural_width
99
+ update_hash = ((cell.width.to_f - current_width_of_spanned_cells) >
100
+ Prawn::FLOAT_PRECISION)
101
+
102
+ if update_hash
103
+ # Split the width of colspanned cells evenly by columns
104
+ width_per_column = cell.width.to_f / cell.colspan
105
+ # Update the Hash
106
+ cell.colspan.times do |i|
107
+ @widths_by_column[cell.column + i] = width_per_column
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ @widths_by_column.sort_by { |col, _| col }.map { |_, w| w }
114
+ end
115
+
116
+ # get column widths (either min or max depending on meth)
117
+ # used in cells.rb
118
+ #
119
+ # @param row_or_column - you may call this on either rows or columns
120
+ # @param meth - min/max
121
+ # @param aggregate - functions from cell.rb to be used to aggregate e.g. avg_spanned_min_width
122
+ #
123
+ def aggregate_cell_values(row_or_column, meth, aggregate)
124
+ values = {}
125
+
126
+ #calculate values for all cells that do not span accross multiple cells
127
+ #this ensures that we don't have a problem if the first line includes
128
+ #a cell that spans across multiple cells
129
+ @cells.each do |cell|
130
+ #don't take spanned cells
131
+ if cell.colspan == 1 and cell.class != Prawn::Table::Cell::SpanDummy
132
+ index = cell.send(row_or_column)
133
+ values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
134
+ end
135
+ end
136
+
137
+ # if there are only colspanned or rowspanned cells in a table
138
+ spanned_width_needs_fixing = true
139
+
140
+ @cells.each do |cell|
141
+ index = cell.send(row_or_column)
142
+ if cell.colspan > 1
143
+ #special treatment if some but not all spanned indices in the values array have been calculated
144
+ #only applies to rows
145
+ values = fill_values_if_needed(values, cell, index, meth) if row_or_column == :column
146
+ #calculate current (old) return value before we do anything
147
+ old_sum = 0
148
+ cell.colspan.times { |i|
149
+ old_sum += values[index+i] unless values[index+i].nil?
150
+ }
151
+
152
+ #calculate future return value
153
+ new_sum = cell.send(meth) * cell.colspan
154
+
155
+ #due to float rounding errors we need to ignore a small difference in the new
156
+ #and the old sum the same had to be done in
157
+ #the column_width_calculator#natural_width
158
+ spanned_width_needs_fixing = ((new_sum - old_sum) > Prawn::FLOAT_PRECISION)
159
+
160
+ if spanned_width_needs_fixing
161
+ #not entirely sure why we need this line, but with it the tests pass
162
+ values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
163
+ #overwrite the old values with the new ones, but only if all entries existed
164
+ entries_exist = true
165
+ cell.colspan.times { |i| entries_exist = false if values[index+i].nil? }
166
+ cell.colspan.times { |i|
167
+ values[index+i] = cell.send(meth) if entries_exist
168
+ }
169
+ end
170
+ else
171
+ if spanned_width_needs_fixing && cell.class == Prawn::Table::Cell::SpanDummy
172
+ values[index] = [values[index], cell.send(meth)].compact.send(aggregate)
173
+ end
174
+ end
175
+ end
176
+
177
+ return values.values.inject(0, &:+)
178
+ end
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Generates the Prawn by example manual.
4
+
5
+ require_relative "example_helper"
6
+
7
+ Encoding.default_external = Encoding::UTF_8
8
+
9
+ Prawn::ManualBuilder::Example.generate("manual.pdf",
10
+ :skip_page_creation => true, :page_size => "FOLIO") do
11
+
12
+ load_package "table"
13
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: UTF-8
2
+
3
+ require "prawn"
4
+ require_relative "../lib/prawn/table"
5
+
6
+ require "prawn/manual_builder"
7
+
8
+ Prawn::ManualBuilder.manual_dir = File.dirname(__FILE__)
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+ #
3
+ # All of the previous styling options we've seen deal with all the table cells
4
+ # at once.
5
+ #
6
+ # With initializer blocks we may deal with specific cells.
7
+ # A block passed to one of the table methods (<code>Prawn::Table.new</code>,
8
+ # <code>Prawn::Document#table</code>, <code>Prawn::Document#make_table</code>)
9
+ # will be called after cell setup but before layout. This is a very flexible way
10
+ # to specify styling and layout constraints.
11
+ #
12
+ # Just like the <code>Prawn::Document.generate</code> method, the table
13
+ # initializer blocks may be used with and without a block argument.
14
+ #
15
+ # The table class has three methods that are handy within an initializer block:
16
+ # <code>cells</code>, <code>rows</code> and <code>columns</code>. All three
17
+ # return an instance of <code>Prawn::Table::Cells</code> which represents
18
+ # a selection of cells.
19
+ #
20
+ # <code>cells</code> return all the table cells, while <code>rows</code> and
21
+ # <code>columns</code> accept a number or a range as argument which returns a
22
+ # single row/column or a range of rows/columns respectively. (<code>rows</code>
23
+ # and <code>columns</code> are also aliased as <code>row</code> and
24
+ # <code>column</code>)
25
+ #
26
+ # The <code>Prawn::Table::Cells</code> class also defines <code>rows</code> and
27
+ # <code>columns</code> so they may be chained to narrow the selection of cells.
28
+ #
29
+ # All of the cell styling options we've seen on previous examples may be set as
30
+ # properties of the selection of cells.
31
+ #
32
+ require File.expand_path(File.join(File.dirname(__FILE__),
33
+ %w[.. example_helper]))
34
+
35
+ filename = File.basename(__FILE__).gsub('.rb', '.pdf')
36
+ Prawn::ManualBuilder::Example.generate(filename) do
37
+ data = [ ["Header", "A " * 5, "B"],
38
+ ["Data row", "C", "D " * 5],
39
+ ["Another data row", "E", "F"]]
40
+
41
+ table(data) do
42
+ cells.padding = 12
43
+ cells.borders = []
44
+
45
+ row(0).borders = [:bottom]
46
+ row(0).border_width = 2
47
+ row(0).font_style = :bold
48
+
49
+ columns(0..1).borders = [:right]
50
+
51
+ row(0).columns(0..1).borders = [:bottom, :right]
52
+ end
53
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ #
3
+ # <code>Prawn::Table#initialize</code> takes a
4
+ # <code>:before_rendering_page</code> argument, to adjust the way an entire page
5
+ # of table cells is styled. This allows you to do things like draw a border
6
+ # around the entire table as displayed on a page.
7
+ #
8
+ # The callback is passed a Cells object that is numbered based on the order of
9
+ # the cells on the page (e.g., the first row on the page is
10
+ # <code>cells.row(0)</code>).
11
+ #
12
+ require File.expand_path(File.join(File.dirname(__FILE__),
13
+ %w[.. example_helper]))
14
+
15
+ filename = File.basename(__FILE__).gsub('.rb', '.pdf')
16
+ Prawn::ManualBuilder::Example.generate(filename) do
17
+ table([["foo", "bar", "baz"]] * 40) do |t|
18
+ t.cells.border_width = 1
19
+ t.before_rendering_page do |page|
20
+ page.row(0).border_top_width = 3
21
+ page.row(-1).border_bottom_width = 3
22
+ page.column(0).border_left_width = 3
23
+ page.column(-1).border_right_width = 3
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ #
3
+ # The <code>border_lines</code> option accepts an array with the styles of the
4
+ # border sides. The default is <code>[:solid, :solid, :solid, :solid]</code>.
5
+ #
6
+ # <code>border_lines</code> must be set to an array.
7
+ #
8
+ require File.expand_path(File.join(File.dirname(__FILE__),
9
+ %w[.. example_helper]))
10
+
11
+ filename = File.basename(__FILE__).gsub('.rb', '.pdf')
12
+ Prawn::ManualBuilder::Example.generate(filename) do
13
+ data = [ ["Look at how the cell border lines can be mixed", "", ""],
14
+ ["dotted top border", "", ""],
15
+ ["solid right border", "", ""],
16
+ ["dotted bottom border", "", ""],
17
+ ["dashed left border", "", ""]
18
+ ]
19
+
20
+ text "Cell :border_lines => [:dotted, :solid, :dotted, :dashed]"
21
+
22
+ table(data, :cell_style =>
23
+ { :border_lines => [:dotted, :solid, :dotted, :dashed] })
24
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ #
3
+ # The <code>borders</code> option accepts an array with the border sides that
4
+ # will be drawn. The default is <code>[:top, :bottom, :left, :right]</code>.
5
+ #
6
+ # <code>border_width</code> may be set with a numeric value.
7
+ #
8
+ # Both <code>border_color</code> and <code>background_color</code> accept an
9
+ # HTML like RGB color string ("FF0000")
10
+ #
11
+ require File.expand_path(File.join(File.dirname(__FILE__),
12
+ %w[.. example_helper]))
13
+
14
+ filename = File.basename(__FILE__).gsub('.rb', '.pdf')
15
+ Prawn::ManualBuilder::Example.generate(filename) do
16
+ data = [ ["Look at how the cells will look when styled", "", ""],
17
+ ["They probably won't look the same", "", ""]
18
+ ]
19
+
20
+ { :borders => [:top, :left],
21
+ :border_width => 3,
22
+ :border_color => "FF0000"}.each do |property, value|
23
+
24
+ text "Cell #{property}: #{value.inspect}"
25
+ table(data, :cell_style => {property => value})
26
+ move_down 20
27
+ end
28
+
29
+ text "Cell background_color: FFFFCC"
30
+ table(data, :cell_style => {:background_color => "FFFFCC"})
31
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ #
3
+ # To style all the table cells you can use the <code>:cell_style</code> option
4
+ # with the table methods. It accepts a hash with the cell style options.
5
+ #
6
+ # Some straightforward options are <code>width</code>, <code>height</code>,
7
+ # and <code>padding</code>. All three accept numeric values to set the property.
8
+ #
9
+ # <code>padding</code> also accepts a four number array that defines the padding
10
+ # in a CSS like syntax setting the top, right, bottom, left sequentially. The
11
+ # default is 5pt for all sides.
12
+ #
13
+ require File.expand_path(File.join(File.dirname(__FILE__),
14
+ %w[.. example_helper]))
15
+
16
+ filename = File.basename(__FILE__).gsub('.rb', '.pdf')
17
+ Prawn::ManualBuilder::Example.generate(filename) do
18
+ data = [ ["Look at how the cells will look when styled", "", ""],
19
+ ["They probably won't look the same", "", ""]
20
+ ]
21
+
22
+ {:width => 160, :height => 50, :padding => 12}.each do |property, value|
23
+ text "Cell's #{property}: #{value}"
24
+ table(data, :cell_style => {property => value})
25
+ move_down 20
26
+ end
27
+
28
+ text "Padding can also be set with an array: [0, 0, 0, 30]"
29
+ table(data, :cell_style => {:padding => [0, 0, 0, 30]})
30
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Text cells accept the following options: <code>align</code>,
4
+ # <code>font</code>, <code>font_style</code>, <code>inline_format</code>,
5
+ # <code>kerning</code>, <code>leading</code>, <code>min_font_size</code>,
6
+ # <code>overflow</code>, <code>rotate</code>, <code>rotate_around</code>,
7
+ # <code>single_line</code>, <code>size</code>, <code>text_color</code>,
8
+ # and <code>valign</code>.
9
+ #
10
+ # Most of these style options are direct translations from the text methods
11
+ # styling options.
12
+ #
13
+ require File.expand_path(File.join(File.dirname(__FILE__),
14
+ %w[.. example_helper]))
15
+
16
+ filename = File.basename(__FILE__).gsub('.rb', '.pdf')
17
+ Prawn::ManualBuilder::Example.generate(filename) do
18
+ data = [ ["Look at how the cells will look when styled", "", ""],
19
+ ["They probably won't look the same", "", ""]
20
+ ]
21
+
22
+ table data, :cell_style => { :font => "Times-Roman", :font_style => :italic }
23
+ move_down 20
24
+
25
+ table data, :cell_style => { :size => 18, :text_color => "346842" }
26
+ move_down 20
27
+
28
+ table [["Just <font size='18'>some</font> <b><i>inline</i></b>", "", ""],
29
+ ["<color rgb='FF00FF'>styles</color> being applied here", "", ""]],
30
+ :cell_style => { :inline_format => true }
31
+ move_down 20
32
+
33
+ table [["1", "2", "3", "rotate"]], :cell_style => { :rotate => 30 }
34
+ move_down 20
35
+
36
+ table data, :cell_style => { :overflow => :shrink_to_fit, :min_font_size => 8,
37
+ :width => 60, :height => 30 }
38
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Prawn will make its best attempt to identify the best width for the columns.
4
+ # If the end result isn't good, we can override it with some styling.
5
+ #
6
+ # Individual column widths can be set with the <code>:column_widths</code>
7
+ # option. Just provide an array with the sequential width values for the columns
8
+ # or a hash were each key-value pair represents the column 0-based index and its
9
+ # width.
10
+ #
11
+ require File.expand_path(File.join(File.dirname(__FILE__),
12
+ %w[.. example_helper]))
13
+
14
+ filename = File.basename(__FILE__).gsub('.rb', '.pdf')
15
+ Prawn::ManualBuilder::Example.generate(filename) do
16
+ data = [ ["this is not quite as long as the others",
17
+ "here we have a line that is long but with smaller words",
18
+ "this is so very looooooooooooooooooooooooooooooong"] ]
19
+
20
+ text "Prawn trying to guess the column widths"
21
+ table(data)
22
+ move_down 20
23
+
24
+ text "Manually setting all the column widths"
25
+ table(data, :column_widths => [100, 200, 240])
26
+ move_down 20
27
+
28
+ text "Setting only the last column width"
29
+ table(data, :column_widths => {2 => 240})
30
+ end