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.
Files changed (163) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +80 -0
  3. data/COPYING +2 -0
  4. data/GPLv2 +339 -0
  5. data/GPLv3 +674 -0
  6. data/LICENSE +65 -0
  7. data/LICENSE-prawn-svg +21 -0
  8. data/README.md +148 -0
  9. data/data/fonts/Courier-Bold.afm +342 -0
  10. data/data/fonts/Courier-BoldOblique.afm +342 -0
  11. data/data/fonts/Courier-Oblique.afm +342 -0
  12. data/data/fonts/Courier.afm +342 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/lib/gamba/annotations.rb +21 -0
  25. data/lib/gamba/combine.rb +307 -0
  26. data/lib/gamba/document/bounding_box.rb +193 -0
  27. data/lib/gamba/document/column_box.rb +91 -0
  28. data/lib/gamba/document/internals.rb +264 -0
  29. data/lib/gamba/document/span.rb +25 -0
  30. data/lib/gamba/document.rb +268 -0
  31. data/lib/gamba/encoding.rb +13 -0
  32. data/lib/gamba/errors.rb +39 -0
  33. data/lib/gamba/font.rb +228 -0
  34. data/lib/gamba/fonts/afm.rb +171 -0
  35. data/lib/gamba/fonts/otf.rb +50 -0
  36. data/lib/gamba/fonts/ttf.rb +413 -0
  37. data/lib/gamba/graphic_state.rb +84 -0
  38. data/lib/gamba/graphics/color.rb +174 -0
  39. data/lib/gamba/graphics/patterns.rb +100 -0
  40. data/lib/gamba/graphics/style.rb +159 -0
  41. data/lib/gamba/graphics.rb +212 -0
  42. data/lib/gamba/grid.rb +216 -0
  43. data/lib/gamba/images/image.rb +271 -0
  44. data/lib/gamba/images/png_decoder.rb +65 -0
  45. data/lib/gamba/images.rb +115 -0
  46. data/lib/gamba/measurement_extensions.rb +40 -0
  47. data/lib/gamba/measurements.rb +62 -0
  48. data/lib/gamba/outline.rb +148 -0
  49. data/lib/gamba/page.rb +125 -0
  50. data/lib/gamba/page_geometry.rb +27 -0
  51. data/lib/gamba/pdf/decryption.rb +165 -0
  52. data/lib/gamba/pdf/outline_node.rb +53 -0
  53. data/lib/gamba/pdf/reader.rb +616 -0
  54. data/lib/gamba/pdf/reference.rb +31 -0
  55. data/lib/gamba/pdf/writer.rb +105 -0
  56. data/lib/gamba/pdf.rb +87 -0
  57. data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
  58. data/lib/gamba/pdf_a.rb +132 -0
  59. data/lib/gamba/repeater.rb +79 -0
  60. data/lib/gamba/security.rb +111 -0
  61. data/lib/gamba/stamp.rb +76 -0
  62. data/lib/gamba/table/cell/kinds.rb +246 -0
  63. data/lib/gamba/table/cell.rb +331 -0
  64. data/lib/gamba/table/cells.rb +208 -0
  65. data/lib/gamba/table.rb +298 -0
  66. data/lib/gamba/text/box.rb +16 -0
  67. data/lib/gamba/text/formatted/arranger.rb +217 -0
  68. data/lib/gamba/text/formatted/box.rb +312 -0
  69. data/lib/gamba/text/formatted/fragment.rb +205 -0
  70. data/lib/gamba/text/formatted/line_wrap.rb +205 -0
  71. data/lib/gamba/text/formatted/parser.rb +150 -0
  72. data/lib/gamba/text/formatted/wrap.rb +103 -0
  73. data/lib/gamba/text.rb +310 -0
  74. data/lib/gamba/transformation_stack.rb +43 -0
  75. data/lib/gamba/true_type/cff_subset.rb +157 -0
  76. data/lib/gamba/true_type/file.rb +372 -0
  77. data/lib/gamba/true_type/open_type_layout.rb +189 -0
  78. data/lib/gamba/true_type/simple_subsets.rb +108 -0
  79. data/lib/gamba/true_type/subset.rb +206 -0
  80. data/lib/gamba/version.rb +10 -0
  81. data/lib/gamba/view.rb +28 -0
  82. data/lib/gamba.rb +124 -0
  83. data/lib/pdf/core.rb +4 -0
  84. data/lib/prawn/measurement_extensions.rb +4 -0
  85. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  86. data/lib/prawn/svg/attributes/mask.rb +16 -0
  87. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  88. data/lib/prawn/svg/attributes/space.rb +10 -0
  89. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  90. data/lib/prawn/svg/attributes/transform.rb +10 -0
  91. data/lib/prawn/svg/attributes.rb +6 -0
  92. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  93. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  94. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  95. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  96. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  97. data/lib/prawn/svg/color.rb +240 -0
  98. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  99. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  100. data/lib/prawn/svg/css/font_parser.rb +46 -0
  101. data/lib/prawn/svg/css/selector_parser.rb +180 -0
  102. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  103. data/lib/prawn/svg/css/values_parser.rb +68 -0
  104. data/lib/prawn/svg/document.rb +169 -0
  105. data/lib/prawn/svg/elements/anchor.rb +10 -0
  106. data/lib/prawn/svg/elements/base.rb +315 -0
  107. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  108. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  109. data/lib/prawn/svg/elements/circle.rb +19 -0
  110. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  111. data/lib/prawn/svg/elements/container.rb +10 -0
  112. data/lib/prawn/svg/elements/direct_render_base.rb +28 -0
  113. data/lib/prawn/svg/elements/ellipse.rb +20 -0
  114. data/lib/prawn/svg/elements/gradient.rb +183 -0
  115. data/lib/prawn/svg/elements/ignored.rb +5 -0
  116. data/lib/prawn/svg/elements/image.rb +178 -0
  117. data/lib/prawn/svg/elements/line.rb +27 -0
  118. data/lib/prawn/svg/elements/marker.rb +72 -0
  119. data/lib/prawn/svg/elements/mask.rb +65 -0
  120. data/lib/prawn/svg/elements/path.rb +294 -0
  121. data/lib/prawn/svg/elements/pattern.rb +150 -0
  122. data/lib/prawn/svg/elements/polygon.rb +25 -0
  123. data/lib/prawn/svg/elements/polyline.rb +23 -0
  124. data/lib/prawn/svg/elements/rect.rb +64 -0
  125. data/lib/prawn/svg/elements/root.rb +22 -0
  126. data/lib/prawn/svg/elements/switch.rb +92 -0
  127. data/lib/prawn/svg/elements/text.rb +99 -0
  128. data/lib/prawn/svg/elements/text_component.rb +352 -0
  129. data/lib/prawn/svg/elements/text_node.rb +308 -0
  130. data/lib/prawn/svg/elements/text_path.rb +153 -0
  131. data/lib/prawn/svg/elements/use.rb +142 -0
  132. data/lib/prawn/svg/elements/viewport.rb +29 -0
  133. data/lib/prawn/svg/elements.rb +46 -0
  134. data/lib/prawn/svg/extension.rb +25 -0
  135. data/lib/prawn/svg/font.rb +20 -0
  136. data/lib/prawn/svg/font_metrics.rb +144 -0
  137. data/lib/prawn/svg/font_registry.rb +315 -0
  138. data/lib/prawn/svg/funciri.rb +14 -0
  139. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  140. data/lib/prawn/svg/gradients.rb +53 -0
  141. data/lib/prawn/svg/interface.rb +72 -0
  142. data/lib/prawn/svg/length.rb +43 -0
  143. data/lib/prawn/svg/link_renderer.rb +40 -0
  144. data/lib/prawn/svg/loaders/data.rb +17 -0
  145. data/lib/prawn/svg/loaders/file.rb +111 -0
  146. data/lib/prawn/svg/loaders/web.rb +24 -0
  147. data/lib/prawn/svg/paint.rb +67 -0
  148. data/lib/prawn/svg/pathable.rb +153 -0
  149. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  150. data/lib/prawn/svg/pdfmatrix.rb +39 -0
  151. data/lib/prawn/svg/percentage.rb +24 -0
  152. data/lib/prawn/svg/properties.rb +329 -0
  153. data/lib/prawn/svg/renderer.rb +249 -0
  154. data/lib/prawn/svg/state.rb +24 -0
  155. data/lib/prawn/svg/transform_parser.rb +78 -0
  156. data/lib/prawn/svg/ttc.rb +35 -0
  157. data/lib/prawn/svg/ttf.rb +95 -0
  158. data/lib/prawn/svg/url_loader.rb +46 -0
  159. data/lib/prawn/svg/version.rb +5 -0
  160. data/lib/prawn/table.rb +4 -0
  161. data/lib/prawn-svg.rb +52 -0
  162. data/lib/prawn.rb +3 -0
  163. 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
@@ -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