gamba 2.0.0
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/CHANGELOG.md +80 -0
- data/COPYING +2 -0
- data/GPLv2 +339 -0
- data/GPLv3 +674 -0
- data/LICENSE +65 -0
- data/LICENSE-prawn-svg +21 -0
- data/README.md +148 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/lib/gamba/annotations.rb +21 -0
- data/lib/gamba/combine.rb +307 -0
- data/lib/gamba/document/bounding_box.rb +193 -0
- data/lib/gamba/document/column_box.rb +91 -0
- data/lib/gamba/document/internals.rb +264 -0
- data/lib/gamba/document/span.rb +25 -0
- data/lib/gamba/document.rb +268 -0
- data/lib/gamba/encoding.rb +13 -0
- data/lib/gamba/errors.rb +39 -0
- data/lib/gamba/font.rb +228 -0
- data/lib/gamba/fonts/afm.rb +171 -0
- data/lib/gamba/fonts/otf.rb +50 -0
- data/lib/gamba/fonts/ttf.rb +413 -0
- data/lib/gamba/graphic_state.rb +84 -0
- data/lib/gamba/graphics/color.rb +174 -0
- data/lib/gamba/graphics/patterns.rb +100 -0
- data/lib/gamba/graphics/style.rb +159 -0
- data/lib/gamba/graphics.rb +212 -0
- data/lib/gamba/grid.rb +216 -0
- data/lib/gamba/images/image.rb +271 -0
- data/lib/gamba/images/png_decoder.rb +65 -0
- data/lib/gamba/images.rb +115 -0
- data/lib/gamba/measurement_extensions.rb +40 -0
- data/lib/gamba/measurements.rb +62 -0
- data/lib/gamba/outline.rb +148 -0
- data/lib/gamba/page.rb +125 -0
- data/lib/gamba/page_geometry.rb +27 -0
- data/lib/gamba/pdf/decryption.rb +165 -0
- data/lib/gamba/pdf/outline_node.rb +53 -0
- data/lib/gamba/pdf/reader.rb +616 -0
- data/lib/gamba/pdf/reference.rb +31 -0
- data/lib/gamba/pdf/writer.rb +105 -0
- data/lib/gamba/pdf.rb +87 -0
- data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
- data/lib/gamba/pdf_a.rb +132 -0
- data/lib/gamba/repeater.rb +79 -0
- data/lib/gamba/security.rb +111 -0
- data/lib/gamba/stamp.rb +76 -0
- data/lib/gamba/table/cell/kinds.rb +246 -0
- data/lib/gamba/table/cell.rb +331 -0
- data/lib/gamba/table/cells.rb +208 -0
- data/lib/gamba/table.rb +298 -0
- data/lib/gamba/text/box.rb +16 -0
- data/lib/gamba/text/formatted/arranger.rb +217 -0
- data/lib/gamba/text/formatted/box.rb +312 -0
- data/lib/gamba/text/formatted/fragment.rb +205 -0
- data/lib/gamba/text/formatted/line_wrap.rb +205 -0
- data/lib/gamba/text/formatted/parser.rb +150 -0
- data/lib/gamba/text/formatted/wrap.rb +103 -0
- data/lib/gamba/text.rb +310 -0
- data/lib/gamba/transformation_stack.rb +43 -0
- data/lib/gamba/true_type/cff_subset.rb +157 -0
- data/lib/gamba/true_type/file.rb +372 -0
- data/lib/gamba/true_type/open_type_layout.rb +189 -0
- data/lib/gamba/true_type/simple_subsets.rb +108 -0
- data/lib/gamba/true_type/subset.rb +206 -0
- data/lib/gamba/version.rb +10 -0
- data/lib/gamba/view.rb +28 -0
- data/lib/gamba.rb +124 -0
- data/lib/pdf/core.rb +4 -0
- data/lib/prawn/measurement_extensions.rb +4 -0
- data/lib/prawn/svg/attributes/clip_path.rb +46 -0
- data/lib/prawn/svg/attributes/mask.rb +16 -0
- data/lib/prawn/svg/attributes/opacity.rb +30 -0
- data/lib/prawn/svg/attributes/space.rb +10 -0
- data/lib/prawn/svg/attributes/stroke.rb +50 -0
- data/lib/prawn/svg/attributes/transform.rb +10 -0
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
- data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
- data/lib/prawn/svg/calculators/path_length.rb +168 -0
- data/lib/prawn/svg/calculators/pixels.rb +57 -0
- data/lib/prawn/svg/color.rb +240 -0
- data/lib/prawn/svg/css/font_face_parser.rb +72 -0
- data/lib/prawn/svg/css/font_family_parser.rb +39 -0
- data/lib/prawn/svg/css/font_parser.rb +46 -0
- data/lib/prawn/svg/css/selector_parser.rb +180 -0
- data/lib/prawn/svg/css/stylesheets.rb +233 -0
- data/lib/prawn/svg/css/values_parser.rb +68 -0
- data/lib/prawn/svg/document.rb +169 -0
- data/lib/prawn/svg/elements/anchor.rb +10 -0
- data/lib/prawn/svg/elements/base.rb +315 -0
- data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
- data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
- data/lib/prawn/svg/elements/circle.rb +19 -0
- data/lib/prawn/svg/elements/clip_path.rb +26 -0
- data/lib/prawn/svg/elements/container.rb +10 -0
- data/lib/prawn/svg/elements/direct_render_base.rb +28 -0
- data/lib/prawn/svg/elements/ellipse.rb +20 -0
- data/lib/prawn/svg/elements/gradient.rb +183 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +178 -0
- data/lib/prawn/svg/elements/line.rb +27 -0
- data/lib/prawn/svg/elements/marker.rb +72 -0
- data/lib/prawn/svg/elements/mask.rb +65 -0
- data/lib/prawn/svg/elements/path.rb +294 -0
- data/lib/prawn/svg/elements/pattern.rb +150 -0
- data/lib/prawn/svg/elements/polygon.rb +25 -0
- data/lib/prawn/svg/elements/polyline.rb +23 -0
- data/lib/prawn/svg/elements/rect.rb +64 -0
- data/lib/prawn/svg/elements/root.rb +22 -0
- data/lib/prawn/svg/elements/switch.rb +92 -0
- data/lib/prawn/svg/elements/text.rb +99 -0
- data/lib/prawn/svg/elements/text_component.rb +352 -0
- data/lib/prawn/svg/elements/text_node.rb +308 -0
- data/lib/prawn/svg/elements/text_path.rb +153 -0
- data/lib/prawn/svg/elements/use.rb +142 -0
- data/lib/prawn/svg/elements/viewport.rb +29 -0
- data/lib/prawn/svg/elements.rb +46 -0
- data/lib/prawn/svg/extension.rb +25 -0
- data/lib/prawn/svg/font.rb +20 -0
- data/lib/prawn/svg/font_metrics.rb +144 -0
- data/lib/prawn/svg/font_registry.rb +315 -0
- data/lib/prawn/svg/funciri.rb +14 -0
- data/lib/prawn/svg/gradient_renderer.rb +332 -0
- data/lib/prawn/svg/gradients.rb +53 -0
- data/lib/prawn/svg/interface.rb +72 -0
- data/lib/prawn/svg/length.rb +43 -0
- data/lib/prawn/svg/link_renderer.rb +40 -0
- data/lib/prawn/svg/loaders/data.rb +17 -0
- data/lib/prawn/svg/loaders/file.rb +111 -0
- data/lib/prawn/svg/loaders/web.rb +24 -0
- data/lib/prawn/svg/paint.rb +67 -0
- data/lib/prawn/svg/pathable.rb +153 -0
- data/lib/prawn/svg/pattern_renderer.rb +112 -0
- data/lib/prawn/svg/pdfmatrix.rb +39 -0
- data/lib/prawn/svg/percentage.rb +24 -0
- data/lib/prawn/svg/properties.rb +329 -0
- data/lib/prawn/svg/renderer.rb +249 -0
- data/lib/prawn/svg/state.rb +24 -0
- data/lib/prawn/svg/transform_parser.rb +78 -0
- data/lib/prawn/svg/ttc.rb +35 -0
- data/lib/prawn/svg/ttf.rb +95 -0
- data/lib/prawn/svg/url_loader.rb +46 -0
- data/lib/prawn/svg/version.rb +5 -0
- data/lib/prawn/table.rb +4 -0
- data/lib/prawn-svg.rb +52 -0
- data/lib/prawn.rb +3 -0
- metadata +263 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
class Table
|
|
5
|
+
# A selection of a table's cells, addressable by row and column, styled all at once.
|
|
6
|
+
class Cells < Array
|
|
7
|
+
def fits_on_current_page?(offset, reference_bounds)
|
|
8
|
+
return true if empty?
|
|
9
|
+
|
|
10
|
+
height_with_span < (self[0, 0].y + offset) - reference_bounds.absolute_bottom
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def rows(row_spec)
|
|
14
|
+
index_cells unless @indexed
|
|
15
|
+
row_spec = transform_spec(row_spec, @first_row, @row_count)
|
|
16
|
+
Cells.new(@rows[row_spec] ||= select { |cell| row_spec.respond_to?(:include?) ? row_spec.include?(cell.row) : row_spec === cell.row })
|
|
17
|
+
end
|
|
18
|
+
alias_method :row, :rows
|
|
19
|
+
|
|
20
|
+
def row_count
|
|
21
|
+
index_cells unless @indexed
|
|
22
|
+
@row_count
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def columns(column_spec)
|
|
26
|
+
index_cells unless @indexed
|
|
27
|
+
column_spec = transform_spec(column_spec, @first_column, @column_count)
|
|
28
|
+
Cells.new(@columns[column_spec] ||= select { |cell| column_spec.respond_to?(:include?) ? column_spec.include?(cell.column) : column_spec === cell.column })
|
|
29
|
+
end
|
|
30
|
+
alias_method :column, :columns
|
|
31
|
+
|
|
32
|
+
def column_count
|
|
33
|
+
index_cells unless @indexed
|
|
34
|
+
@column_count
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def filter(&)
|
|
38
|
+
Cells.new(select(&))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def [](row, column)
|
|
42
|
+
return nil if empty?
|
|
43
|
+
|
|
44
|
+
index_cells unless @indexed
|
|
45
|
+
row_cells = @rows[@first_row + row] || []
|
|
46
|
+
column_cells = @columns[@first_column + column] || []
|
|
47
|
+
if row_cells.length < column_cells.length
|
|
48
|
+
row_cells.find { _1.column == @first_column + column }
|
|
49
|
+
else
|
|
50
|
+
column_cells.find { _1.row == @first_row + row }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def []=(row, column, cell)
|
|
55
|
+
cell.extend(Cell::InTable)
|
|
56
|
+
cell.row = row
|
|
57
|
+
cell.column = column
|
|
58
|
+
if @indexed
|
|
59
|
+
(@rows[row] ||= []) << cell
|
|
60
|
+
(@columns[column] ||= []) << cell
|
|
61
|
+
@first_row = row if !@first_row || row < @first_row
|
|
62
|
+
@first_column = column if !@first_column || column < @first_column
|
|
63
|
+
@row_count = @rows.size
|
|
64
|
+
@column_count = @columns.size
|
|
65
|
+
end
|
|
66
|
+
self << cell
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def style(options = {}, &)
|
|
70
|
+
each { |cell| cell.style(options, &) unless cell.is_a?(Cell::SpanDummy) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def width
|
|
74
|
+
ColumnWidthCalculator.new(self).natural_widths.sum
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def min_width
|
|
78
|
+
aggregate_cell_values(:column, :avg_spanned_min_width, :max)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def max_width
|
|
82
|
+
aggregate_cell_values(:column, :max_width_ignoring_span, :max)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def height
|
|
86
|
+
aggregate_cell_values(:row, :height_ignoring_span, :max)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def height_with_span
|
|
90
|
+
aggregate_cell_values(:row, :height, :max)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Setters reach every cell that has them: cells.background_color = "eeeeee".
|
|
94
|
+
def method_missing(name, *arguments, &)
|
|
95
|
+
return super unless name.end_with?("=")
|
|
96
|
+
|
|
97
|
+
each { |cell| cell.public_send(name, *arguments, &) if cell.respond_to?(name) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def respond_to_missing?(name, include_private = false)
|
|
101
|
+
name.end_with?("=") || super
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
protected
|
|
105
|
+
|
|
106
|
+
def index_cells
|
|
107
|
+
@rows = {}
|
|
108
|
+
@columns = {}
|
|
109
|
+
each do |cell|
|
|
110
|
+
(@rows[cell.row] ||= []) << cell
|
|
111
|
+
(@columns[cell.column] ||= []) << cell
|
|
112
|
+
end
|
|
113
|
+
@first_row = @rows.keys.min
|
|
114
|
+
@first_column = @columns.keys.min
|
|
115
|
+
@row_count = @rows.size
|
|
116
|
+
@column_count = @columns.size
|
|
117
|
+
@indexed = true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def aggregate_cell_values(row_or_column, method, aggregate)
|
|
121
|
+
ColumnWidthCalculator.new(self).aggregate_cell_values(row_or_column, method, aggregate)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def transform_spec(spec, first, total)
|
|
125
|
+
case spec
|
|
126
|
+
when Range then transform_spec(spec.begin, first, total)..transform_spec(spec.end, first, total)
|
|
127
|
+
when Integer then spec.negative? ? first + total + spec : first + spec
|
|
128
|
+
when Enumerable then spec.map { first + _1 }
|
|
129
|
+
else raise "Don't understand spec #{spec.inspect}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Widths of columns, and sums of per-row or per-column cell values, accounting for cells spanning several.
|
|
135
|
+
class ColumnWidthCalculator
|
|
136
|
+
def initialize(cells)
|
|
137
|
+
@cells = cells
|
|
138
|
+
@widths_by_column = Hash.new(0)
|
|
139
|
+
@rows_with_a_span_dummy = Hash.new(false)
|
|
140
|
+
@cells.each { |cell| @rows_with_a_span_dummy[cell.row] = true if cell.is_a?(Cell::SpanDummy) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def has_a_span_dummy?(row)
|
|
144
|
+
@rows_with_a_span_dummy[row]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def fill_values_if_needed(values, cell, index, method)
|
|
148
|
+
missing_count = cell.colspan.times.count { |offset| values[index + offset].nil? }
|
|
149
|
+
return values if missing_count.zero? || missing_count == cell.colspan || !has_a_span_dummy?(cell.row)
|
|
150
|
+
|
|
151
|
+
new_sum = cell.send(method) * cell.colspan
|
|
152
|
+
cell.colspan.times { |offset| new_sum -= values[index + offset] unless values[index + offset].nil? }
|
|
153
|
+
new_value = new_sum.to_f / missing_count
|
|
154
|
+
cell.colspan.times { |offset| values[index + offset] = new_value if values[index + offset].nil? }
|
|
155
|
+
values
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def natural_widths
|
|
159
|
+
@cells.each do |cell|
|
|
160
|
+
@widths_by_column[cell.column] = [@widths_by_column[cell.column], cell.width.to_f].max unless has_a_span_dummy?(cell.row)
|
|
161
|
+
end
|
|
162
|
+
@cells.each do |cell|
|
|
163
|
+
next unless has_a_span_dummy?(cell.row)
|
|
164
|
+
next if cell.is_a?(Cell::SpanDummy)
|
|
165
|
+
|
|
166
|
+
if cell.colspan == 1
|
|
167
|
+
@widths_by_column[cell.column] = [@widths_by_column[cell.column], cell.width.to_f].max
|
|
168
|
+
else
|
|
169
|
+
current_width_of_spanned_cells = @widths_by_column.to_a[cell.column..(cell.column + cell.colspan - 1)].sum(&:last)
|
|
170
|
+
if (cell.width.to_f - current_width_of_spanned_cells) > FLOAT_PRECISION
|
|
171
|
+
cell.colspan.times { |offset| @widths_by_column[cell.column + offset] = cell.width.to_f / cell.colspan }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
@widths_by_column.sort_by { |column, _| column }.map { |_, width| width }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def aggregate_cell_values(row_or_column, method, aggregate)
|
|
179
|
+
values = {}
|
|
180
|
+
@cells.each do |cell|
|
|
181
|
+
next unless cell.colspan == 1 && cell.class != Cell::SpanDummy
|
|
182
|
+
|
|
183
|
+
index = cell.send(row_or_column)
|
|
184
|
+
values[index] = [values[index], cell.send(method)].compact.send(aggregate)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
spanned_width_needs_fixing = true
|
|
188
|
+
@cells.each do |cell|
|
|
189
|
+
index = cell.send(row_or_column)
|
|
190
|
+
if cell.colspan > 1
|
|
191
|
+
values = fill_values_if_needed(values, cell, index, method) if row_or_column == :column
|
|
192
|
+
old_sum = cell.colspan.times.inject(0) { |sum, offset| values[index + offset] ? sum + values[index + offset] : sum }
|
|
193
|
+
new_sum = cell.send(method) * cell.colspan
|
|
194
|
+
spanned_width_needs_fixing = (new_sum - old_sum) > FLOAT_PRECISION
|
|
195
|
+
if spanned_width_needs_fixing
|
|
196
|
+
values[index] = [values[index], cell.send(method)].compact.send(aggregate)
|
|
197
|
+
entries_exist = cell.colspan.times.none? { |offset| values[index + offset].nil? }
|
|
198
|
+
cell.colspan.times { |offset| values[index + offset] = cell.send(method) if entries_exist }
|
|
199
|
+
end
|
|
200
|
+
elsif spanned_width_needs_fixing && cell.instance_of?(Cell::SpanDummy)
|
|
201
|
+
values[index] = [values[index], cell.send(method)].compact.send(aggregate)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
values.values.sum
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
data/lib/gamba/table.rb
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
module Errors
|
|
5
|
+
InvalidTableData = Class.new(StandardError)
|
|
6
|
+
EmptyTable = Class.new(StandardError)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# A table of cells laid out in rows and columns, sized to its content, drawn across pages with its header
|
|
10
|
+
# repeated: prawn-table's API.
|
|
11
|
+
class Table
|
|
12
|
+
module Interface
|
|
13
|
+
def table(data, options = {}, &)
|
|
14
|
+
table = Table.new(data, self, options, &)
|
|
15
|
+
table.draw
|
|
16
|
+
table
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def make_table(data, options = {}, &)
|
|
20
|
+
Table.new(data, self, options, &)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :row_length, :column_length, :cells
|
|
25
|
+
attr_writer :width, :position, :header, :row_colors
|
|
26
|
+
|
|
27
|
+
def initialize(data, document, options = {}, &block)
|
|
28
|
+
@pdf = document
|
|
29
|
+
@cells = make_cells(data)
|
|
30
|
+
@header = false
|
|
31
|
+
options.each { |key, value| send(:"#{key}=", value) }
|
|
32
|
+
if block
|
|
33
|
+
(block.arity < 1) ? instance_eval(&block) : yield(self)
|
|
34
|
+
end
|
|
35
|
+
set_column_widths
|
|
36
|
+
set_row_heights
|
|
37
|
+
position_cells
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def before_rendering_page(&block)
|
|
41
|
+
@before_rendering_page = block
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def width
|
|
45
|
+
@width ||= [natural_width, @pdf.bounds.width].min
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def column_widths=(widths)
|
|
49
|
+
case widths
|
|
50
|
+
when Array then widths.each_with_index { |width, index| column(index).width = width }
|
|
51
|
+
when Hash then widths.each { |index, width| column(index).width = width }
|
|
52
|
+
when Numeric then cells.width = widths
|
|
53
|
+
else raise ArgumentError, "cannot interpret column widths"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def height
|
|
58
|
+
cells.height
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def cell_style=(style_hash)
|
|
62
|
+
cells.style(style_hash)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def style(stylable, style_hash = {}, &)
|
|
66
|
+
stylable.style(style_hash, &)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def rows(row_spec)
|
|
70
|
+
cells.rows(row_spec)
|
|
71
|
+
end
|
|
72
|
+
alias_method :row, :rows
|
|
73
|
+
|
|
74
|
+
def columns(column_spec)
|
|
75
|
+
cells.columns(column_spec)
|
|
76
|
+
end
|
|
77
|
+
alias_method :column, :columns
|
|
78
|
+
|
|
79
|
+
def draw
|
|
80
|
+
with_position do
|
|
81
|
+
reference_bounds = @pdf.reference_bounds
|
|
82
|
+
started_new_page_at_row = initial_row_on_initial_page
|
|
83
|
+
offset = @pdf.y
|
|
84
|
+
@header_row = header_rows if @header
|
|
85
|
+
cells_this_page = []
|
|
86
|
+
@cells.each do |cell|
|
|
87
|
+
if start_new_page?(cell, offset, reference_bounds)
|
|
88
|
+
cells_this_page, offset = ink_and_draw_cells_and_start_new_page(cells_this_page, cell)
|
|
89
|
+
started_new_page_at_row = cell.row
|
|
90
|
+
end
|
|
91
|
+
cell = set_background_color(cell, started_new_page_at_row)
|
|
92
|
+
cells_this_page << [cell, [cell.relative_x, cell.relative_y(offset)]]
|
|
93
|
+
end
|
|
94
|
+
ink_and_draw_cells(cells_this_page)
|
|
95
|
+
@pdf.move_cursor_to(@cells.last.relative_y(offset) - @cells.last.height)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def column_widths
|
|
100
|
+
@column_widths ||= begin
|
|
101
|
+
if width - cells.min_width < -FLOAT_PRECISION
|
|
102
|
+
raise Errors::CannotFit, "Table's width was set too small to contain its contents (min width #{cells.min_width}, requested #{width})"
|
|
103
|
+
end
|
|
104
|
+
if width - cells.max_width > FLOAT_PRECISION
|
|
105
|
+
raise Errors::CannotFit, "Table's width was set larger than its contents' maximum width (max width #{cells.max_width}, requested #{width})"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if width - natural_width < -FLOAT_PRECISION
|
|
109
|
+
ratio = (width - cells.min_width).to_f / (natural_width - cells.min_width)
|
|
110
|
+
(0...column_length).map do |index|
|
|
111
|
+
minimum = column(index).min_width
|
|
112
|
+
(ratio * (natural_column_widths[index] - minimum)) + minimum
|
|
113
|
+
end
|
|
114
|
+
elsif width - natural_width > FLOAT_PRECISION
|
|
115
|
+
ratio = (width - cells.width).to_f / (cells.max_width - cells.width)
|
|
116
|
+
(0...column_length).map do |index|
|
|
117
|
+
natural = natural_column_widths[index]
|
|
118
|
+
(ratio * (column(index).max_width - natural)) + natural
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
natural_column_widths
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def row_heights
|
|
127
|
+
@natural_row_heights ||= begin
|
|
128
|
+
heights_by_row = Hash.new(0)
|
|
129
|
+
cells.each do |cell|
|
|
130
|
+
next if cell.is_a?(Cell::SpanDummy)
|
|
131
|
+
|
|
132
|
+
height_per_row = cell.height.to_f / cell.rowspan
|
|
133
|
+
cell.rowspan.times { |offset| heights_by_row[cell.row + offset] = [heights_by_row[cell.row + offset], height_per_row].max }
|
|
134
|
+
end
|
|
135
|
+
heights_by_row.sort_by { |row, _| row }.map { |_, height| height }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
protected
|
|
140
|
+
|
|
141
|
+
def set_background_color(cell, started_new_page_at_row)
|
|
142
|
+
if @row_colors && (!@header || cell.row.positive?)
|
|
143
|
+
index = cell.row - [started_new_page_at_row, number_of_header_rows].max
|
|
144
|
+
cell.background_color ||= @row_colors[index % @row_colors.length]
|
|
145
|
+
end
|
|
146
|
+
cell
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def number_of_header_rows
|
|
150
|
+
return @header if @header.is_a?(Integer)
|
|
151
|
+
|
|
152
|
+
@header ? 1 : 0
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def start_new_page?(cell, offset, reference_bounds)
|
|
156
|
+
cell.column.zero? && cell.row.positive? && !row(cell.row).fits_on_current_page?(offset, reference_bounds)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def ink_and_draw_cells(cells_this_page, draw_cells = true)
|
|
160
|
+
ink_cells(cells_this_page)
|
|
161
|
+
Cell.draw_cells(cells_this_page) if draw_cells
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def ink_and_draw_cells_and_start_new_page(cells_this_page, cell)
|
|
165
|
+
ink_and_draw_cells(cells_this_page, @header_row.nil? || cells_this_page.size > @header_row.size)
|
|
166
|
+
@pdf.bounds.move_past_bottom
|
|
167
|
+
offset = @pdf.y - cell.y
|
|
168
|
+
cells_next_page = []
|
|
169
|
+
offset -= add_header(cell.row, cells_next_page)
|
|
170
|
+
[cells_next_page, offset]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def ink_cells(cells_this_page)
|
|
174
|
+
@before_rendering_page&.call(Cells.new(cells_this_page.map(&:first)))
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def initial_row_on_initial_page
|
|
178
|
+
return 0 if fits_on_page?(@pdf.bounds.height)
|
|
179
|
+
return -1 if fits_on_page?(row(0..number_of_header_rows).height, true)
|
|
180
|
+
|
|
181
|
+
@pdf.bounds.move_past_bottom
|
|
182
|
+
0
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def fits_on_page?(needed_height, use_reference_bounds = false)
|
|
186
|
+
bounds = use_reference_bounds ? @pdf.reference_bounds : @pdf.bounds
|
|
187
|
+
needed_height < @pdf.y - (bounds.absolute_bottom - FLOAT_PRECISION)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def header_rows
|
|
191
|
+
header_rows = Cells.new
|
|
192
|
+
number_of_header_rows.times do |row_index|
|
|
193
|
+
row(row_index).each { |cell| header_rows[cell.row, cell.column] = cell.dup }
|
|
194
|
+
end
|
|
195
|
+
header_rows
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def make_cells(data)
|
|
199
|
+
assert_proper_table_data(data)
|
|
200
|
+
cells = Cells.new
|
|
201
|
+
data.each_with_index do |row_cells, row_number|
|
|
202
|
+
column_number = 0
|
|
203
|
+
row_cells.each do |cell_data|
|
|
204
|
+
column_number += 1 until cells[row_number, column_number].nil?
|
|
205
|
+
cell = Cell.make(@pdf, cell_data)
|
|
206
|
+
cells[row_number, column_number] = cell
|
|
207
|
+
cell.rowspan.times do |row_offset|
|
|
208
|
+
cell.colspan.times do |column_offset|
|
|
209
|
+
next if row_offset.zero? && column_offset.zero?
|
|
210
|
+
if cells[row_number + row_offset, column_number + column_offset]
|
|
211
|
+
raise Errors::InvalidTableSpan, "Spans overlap at row #{row_number + row_offset}, column #{column_number + column_offset}."
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
dummy = Cell::SpanDummy.new(@pdf, cell)
|
|
215
|
+
cells[row_number + row_offset, column_number + column_offset] = dummy
|
|
216
|
+
cell.dummy_cells << dummy
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
column_number += cell.colspan
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
@row_length = cells.map { |cell| cell.row + cell.rowspan }.max
|
|
223
|
+
@column_length = cells.map { |cell| cell.column + cell.colspan }.max
|
|
224
|
+
cells
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def add_header(row_number, cells_this_page)
|
|
228
|
+
x_offset = @pdf.bounds.left_side - @pdf.bounds.absolute_left
|
|
229
|
+
header_height = 0
|
|
230
|
+
if row_number.positive? && @header
|
|
231
|
+
y = @pdf.cursor
|
|
232
|
+
number_of_header_rows.times do |header_index|
|
|
233
|
+
header_height += add_one_header_row(cells_this_page, x_offset, y - header_height, row_number - 1, header_index)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
header_height
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def add_one_header_row(page_of_cells, x_offset, y, row, row_of_header = nil)
|
|
240
|
+
rows_to_operate_on = row_of_header ? @header_row.rows(row_of_header) : @header_row
|
|
241
|
+
rows_to_operate_on.each do |cell|
|
|
242
|
+
cell.row = row
|
|
243
|
+
cell.dummy_cells.each { |dummy| (cell.rowspan > 1) ? dummy.row += row : dummy.row = row }
|
|
244
|
+
page_of_cells << [cell, [cell.x + x_offset, y]]
|
|
245
|
+
end
|
|
246
|
+
rows_to_operate_on.height
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def assert_proper_table_data(data)
|
|
250
|
+
if data.nil? || data.empty?
|
|
251
|
+
raise Errors::EmptyTable, "data must be a non-empty, non-nil, two dimensional array of cell-convertible objects"
|
|
252
|
+
end
|
|
253
|
+
raise Errors::InvalidTableData, "data must be a two dimensional array of cellable objects" unless data.all?(Array)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def natural_column_widths
|
|
257
|
+
@natural_column_widths ||= ColumnWidthCalculator.new(cells).natural_widths
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def natural_width
|
|
261
|
+
@natural_width ||= natural_column_widths.sum
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def set_column_widths
|
|
265
|
+
column_widths.each_with_index { |width, index| column(index).width = width }
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def set_row_heights
|
|
269
|
+
row_heights.each_with_index { |height, index| row(index).height = height }
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def position_cells
|
|
273
|
+
column_widths.each_with_object([0]) { |width, positions| positions << (positions.last + width) }[0..-2]
|
|
274
|
+
.each_with_index { |x, index| column(index).x = x }
|
|
275
|
+
row_heights.each_with_object([0]) { |height, positions| positions << (positions.last - height) }[0..-2]
|
|
276
|
+
.each_with_index { |y, index| row(index).y = y }
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def with_position
|
|
280
|
+
x =
|
|
281
|
+
case @position || :left
|
|
282
|
+
when :left then return yield
|
|
283
|
+
when :center then (@pdf.bounds.width - width) / 2.0
|
|
284
|
+
when :right then @pdf.bounds.width - width
|
|
285
|
+
when Numeric then @position
|
|
286
|
+
else raise ArgumentError, "unknown position #{@position.inspect}"
|
|
287
|
+
end
|
|
288
|
+
dy = @pdf.bounds.absolute_top - @pdf.y
|
|
289
|
+
final_y = nil
|
|
290
|
+
@pdf.bounding_box([x, @pdf.bounds.top], width:) do
|
|
291
|
+
@pdf.move_down dy
|
|
292
|
+
yield
|
|
293
|
+
final_y = @pdf.y
|
|
294
|
+
end
|
|
295
|
+
@pdf.y = final_y
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
module Text
|
|
5
|
+
# A text box of plain text: what does not fit comes back as a string.
|
|
6
|
+
class Box < Formatted::Box
|
|
7
|
+
def initialize(string, options = {})
|
|
8
|
+
super([{text: string}], options)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def render(flags = {})
|
|
12
|
+
super.map { _1[:text] }.join
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|