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,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prawn
4
+ class Table
5
+ class Cell
6
+ class Text < Cell
7
+ TextOptions = %i[inline_format kerning size align valign rotate rotate_around leading single_line skip_encoding overflow min_font_size].freeze # rubocop:disable Naming/ConstantName -- Prawn::Table's public name
8
+
9
+ TextOptions.each do |option|
10
+ define_method(:"#{option}=") { |value| @text_options[option] = value }
11
+ define_method(option) { @text_options[option] }
12
+ end
13
+
14
+ attr_writer :font, :text_color
15
+
16
+ def initialize(pdf, point, options = {})
17
+ @text_options = {}
18
+ super
19
+ end
20
+
21
+ def font
22
+ with_font { @pdf.font }
23
+ end
24
+
25
+ def font_style=(style)
26
+ @text_options[:style] = style
27
+ end
28
+
29
+ def natural_content_width
30
+ @natural_content_width ||= [styled_width_of(@content), @pdf.bounds.width].min
31
+ end
32
+
33
+ def natural_content_height
34
+ with_font do
35
+ @laid_out_box = text_box(width: spanned_content_width + FPTolerance)
36
+ @laid_out_box.render(dry_run: true, keep_layout: true)
37
+ @layout_inputs = layout_inputs
38
+ @laid_out_box.height + @laid_out_box.line_gap
39
+ end
40
+ end
41
+
42
+ def draw_content
43
+ with_font do
44
+ @pdf.move_down((@pdf.font.line_gap + @pdf.font.descender) / 2)
45
+ with_text_color do
46
+ if laid_out_box_reusable?
47
+ @laid_out_box.render_laid_out([0, @pdf.cursor])
48
+ else
49
+ text_box(width: spanned_content_width + FPTolerance, height: spanned_content_height + FPTolerance, at: [0, @pdf.cursor]).render
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def set_width_constraints
56
+ unless @min_width
57
+ @min_width = padding_left + padding_right + [natural_content_width, styled_width_of("M")].min
58
+ super
59
+ end
60
+ end
61
+
62
+ protected
63
+
64
+ def with_font
65
+ @pdf.save_font do
66
+ options = {}
67
+ options[:style] = @text_options[:style] if @text_options[:style]
68
+ options[:style] ||= @pdf.font.options[:style] if @pdf.font.options[:style]
69
+ @pdf.font(@font || @pdf.font.family, options)
70
+ yield
71
+ end
72
+ end
73
+
74
+ def with_text_color
75
+ return yield unless @text_color
76
+
77
+ begin
78
+ old_color = @pdf.fill_color || "000000"
79
+ @pdf.fill_color(@text_color)
80
+ yield
81
+ ensure
82
+ @pdf.fill_color(old_color)
83
+ end
84
+ end
85
+
86
+ def text_box(extra_options = {})
87
+ inline_format = @text_options[:inline_format]
88
+ if inline_format
89
+ options = @text_options.except(:inline_format).merge(extra_options).merge(document: @pdf)
90
+ array = @pdf.text_formatter.format(@content, *(inline_format.is_a?(Array) ? inline_format : []))
91
+ Prawn::Text::Formatted::Box.new(array, options)
92
+ else
93
+ Prawn::Text::Box.new(@content, @text_options.merge(extra_options).merge(document: @pdf))
94
+ end
95
+ end
96
+
97
+ def styled_width_of(text)
98
+ @pdf.width_of(text, @text_options)
99
+ end
100
+
101
+ private
102
+
103
+ # A cell is laid out while the table is sized, then drawn: the layout is drawn again unless
104
+ # before_rendering_page, the document or a truncating height changed what the cell would wrap.
105
+ def laid_out_box_reusable?
106
+ @laid_out_box&.layout_reusable? && @laid_out_box.height <= spanned_content_height + FPTolerance &&
107
+ @layout_inputs == layout_inputs
108
+ end
109
+
110
+ def layout_inputs
111
+ [@content.dup, spanned_content_width, @text_options.dup, @pdf.font, @pdf.font_size, @pdf.default_leading,
112
+ @pdf.character_spacing, @pdf.text_direction, @pdf.fallback_fonts, @pdf.default_kerning?]
113
+ end
114
+ end
115
+
116
+ class Subtable < Cell
117
+ attr_reader :subtable
118
+
119
+ def initialize(pdf, point, options = {})
120
+ super
121
+ @subtable = options[:content]
122
+ @padding = [0, 0, 0, 0]
123
+ end
124
+
125
+ def text_color=(color)
126
+ @subtable.cells.text_color = color
127
+ end
128
+
129
+ def natural_content_width
130
+ @subtable.cells.width
131
+ end
132
+
133
+ def min_width
134
+ @subtable.cells.min_width
135
+ end
136
+
137
+ def max_width
138
+ @subtable.cells.max_width
139
+ end
140
+
141
+ def natural_content_height
142
+ @subtable.cells.height
143
+ end
144
+
145
+ def draw_content
146
+ @subtable.draw
147
+ end
148
+ end
149
+
150
+ class Image < Cell
151
+ def initialize(pdf, point, options = {})
152
+ @image_options = {}
153
+ super
154
+ @pdf_object, @image_info = @pdf.build_image_object(@file)
155
+ @natural_width, @natural_height = @image_info.calc_image_dimensions(@image_options)
156
+ end
157
+
158
+ def image=(file)
159
+ @file = file
160
+ end
161
+
162
+ {scale: :scale, fit: :fit, image_height: :height, image_width: :width, position: :position, vposition: :vposition}.each do |setter, option|
163
+ define_method(:"#{setter}=") { |value| @image_options[option] = value }
164
+ end
165
+
166
+ def natural_content_width
167
+ @natural_width
168
+ end
169
+
170
+ def natural_content_height
171
+ @natural_height
172
+ end
173
+
174
+ def draw_content
175
+ @pdf.embed_image(@pdf_object, @image_info, @image_options)
176
+ end
177
+ end
178
+
179
+ # Stands in the place a spanning cell covers beyond its first row and column, passing on the styles of
180
+ # its right and bottom edges.
181
+ class SpanDummy < Cell
182
+ def initialize(pdf, master_cell)
183
+ super(pdf, [0, pdf.cursor])
184
+ @master_cell = master_cell
185
+ @padding = [0, 0, 0, 0]
186
+ end
187
+
188
+ def natural_content_height
189
+ 0
190
+ end
191
+
192
+ def natural_content_width
193
+ 0
194
+ end
195
+
196
+ def avg_spanned_min_width
197
+ @master_cell.avg_spanned_min_width
198
+ end
199
+
200
+ def draw_borders(_point)
201
+ end
202
+
203
+ def draw_bounded_content(_point)
204
+ end
205
+
206
+ def padding_right=(value)
207
+ @master_cell.padding_right = value if rightmost?
208
+ end
209
+
210
+ def padding_bottom=(value)
211
+ @master_cell.padding_bottom = value if bottommost?
212
+ end
213
+
214
+ def border_right_color=(value)
215
+ @master_cell.border_right_color = value if rightmost?
216
+ end
217
+
218
+ def border_bottom_color=(value)
219
+ @master_cell.border_bottom_color = value if bottommost?
220
+ end
221
+
222
+ def border_right_width=(value)
223
+ @master_cell.border_right_width = value if rightmost?
224
+ end
225
+
226
+ def border_bottom_width=(value)
227
+ @master_cell.border_bottom_width = value if bottommost?
228
+ end
229
+
230
+ def background_color
231
+ @master_cell.background_color
232
+ end
233
+
234
+ private
235
+
236
+ def rightmost?
237
+ @column == @master_cell.column + @master_cell.colspan - 1
238
+ end
239
+
240
+ def bottommost?
241
+ @row == @master_cell.row + @master_cell.rowspan - 1
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,331 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Prawn
6
+ class Document
7
+ def cell(options = {})
8
+ cell = Table::Cell.make(self, options.delete(:content), options)
9
+ cell.draw
10
+ cell
11
+ end
12
+
13
+ def make_cell(content, options = {})
14
+ Table::Cell.make(self, content, options)
15
+ end
16
+
17
+ # Runs the block, then puts back the listed attributes as they were.
18
+ def mask(*fields)
19
+ stored = fields.to_h { |field| [field, public_send(field)] }
20
+ yield
21
+ stored.each { |field, value| public_send(:"#{field}=", value) }
22
+ end
23
+ end
24
+
25
+ class Table
26
+ class Cell
27
+ # Extra width given to a cell's content box, so text measured to fit exactly is not wrapped.
28
+ FPTolerance = 1 # rubocop:disable Naming/ConstantName -- Prawn::Table's public name
29
+ SIDE_INDEX = {top: 0, right: 1, bottom: 2, left: 3}.freeze
30
+
31
+ attr_reader :padding, :colspan, :rowspan, :dummy_cells, :border_widths, :border_colors, :border_lines
32
+ attr_writer :height
33
+ attr_accessor :borders, :content, :background_color
34
+
35
+ def self.make(pdf, content, options = {})
36
+ at = options.delete(:at) || [0, pdf.cursor]
37
+ content = content.to_s if content.nil? || content.is_a?(Numeric) || content.is_a?(Date)
38
+ if content.is_a?(Hash)
39
+ return Cell::Image.new(pdf, at, content) if content[:image]
40
+
41
+ options.update(content)
42
+ content = options[:content]
43
+ else
44
+ options[:content] = content
45
+ end
46
+ options[:content] = content = "" if content.nil?
47
+
48
+ case content
49
+ when Cell then content
50
+ when String then Cell::Text.new(pdf, at, options)
51
+ when Table then Cell::Subtable.new(pdf, at, options)
52
+ when Array then Cell::Subtable.new(pdf, at, options.merge(content: Table.new(options[:content], pdf, {})))
53
+ else raise Errors::UnrecognizedTableContent
54
+ end
55
+ end
56
+
57
+ def self.draw_cells(cells)
58
+ cells.each do |cell, point|
59
+ cell.set_width_constraints
60
+ cell.draw_background(point)
61
+ end
62
+ cells.each do |cell, point|
63
+ cell.draw_borders(point)
64
+ cell.draw_bounded_content(point)
65
+ end
66
+ end
67
+
68
+ def initialize(pdf, point, options = {})
69
+ @pdf = pdf
70
+ @point = point
71
+ @padding = [5, 5, 5, 5]
72
+ @borders = %i[top bottom left right]
73
+ @border_widths = [1] * 4
74
+ @border_colors = ["000000"] * 4
75
+ @border_lines = [:solid] * 4
76
+ @colspan = 1
77
+ @rowspan = 1
78
+ @dummy_cells = []
79
+ options.each { |key, value| send(:"#{key}=", value) }
80
+ @initializer_run = true
81
+ end
82
+
83
+ def min_width_ignoring_span
84
+ set_width_constraints
85
+ @min_width
86
+ end
87
+
88
+ def min_width
89
+ return min_width_ignoring_span if @colspan == 1
90
+
91
+ min_widths = Hash.new(0)
92
+ dummy_cells.each { |dummy| min_widths[dummy.column] = [min_widths[dummy.column], dummy.min_width].max }
93
+ min_widths[column] = [min_widths[column], min_width_ignoring_span].max
94
+ min_widths.values.sum
95
+ end
96
+
97
+ def avg_spanned_min_width
98
+ min_width.to_f / colspan
99
+ end
100
+
101
+ def max_width_ignoring_span
102
+ set_width_constraints
103
+ @max_width
104
+ end
105
+
106
+ def max_width
107
+ return max_width_ignoring_span if @colspan == 1
108
+
109
+ max_widths = Hash.new(0)
110
+ dummy_cells.each { |dummy| max_widths[dummy.column] = [max_widths[dummy.column], dummy.max_width].min }
111
+ max_widths[column] = [max_widths[column], max_width_ignoring_span].min
112
+ max_widths.values.sum
113
+ end
114
+
115
+ def style(options = {}, &block)
116
+ options.each { |key, value| send(:"#{key}=", value) if respond_to?(:"#{key}=") }
117
+ block&.call(self)
118
+ end
119
+
120
+ def width_ignoring_span
121
+ @width || (content_width + padding_left + padding_right)
122
+ end
123
+
124
+ def width
125
+ return width_ignoring_span if @colspan == 1 && @rowspan == 1
126
+
127
+ column_widths = Hash.new(0)
128
+ dummy_cells.each { |dummy| column_widths[dummy.column] = [column_widths[dummy.column], dummy.width].max }
129
+ column_widths[column] = [column_widths[column], width_ignoring_span].max
130
+ column_widths.values.sum
131
+ end
132
+
133
+ def width=(width)
134
+ @width = @min_width = @max_width = width
135
+ end
136
+
137
+ def content_width
138
+ return @width - padding_left - padding_right if @width
139
+
140
+ natural_content_width
141
+ end
142
+
143
+ def spanned_content_width
144
+ width - padding_left - padding_right
145
+ end
146
+
147
+ def natural_content_width
148
+ raise NotImplementedError, "subclasses must implement natural_content_width"
149
+ end
150
+
151
+ def height_ignoring_span
152
+ @height || (content_height + padding_top + padding_bottom)
153
+ end
154
+
155
+ def height
156
+ return height_ignoring_span if @colspan == 1 && @rowspan == 1
157
+
158
+ row_heights = Hash.new(0)
159
+ dummy_cells.each { |dummy| row_heights[dummy.row] = [row_heights[dummy.row], dummy.height].max }
160
+ row_heights[row] = [row_heights[row], height_ignoring_span].max
161
+ row_heights.values.sum
162
+ end
163
+
164
+ def content_height
165
+ return @height - padding_top - padding_bottom if @height
166
+
167
+ natural_content_height
168
+ end
169
+
170
+ def spanned_content_height
171
+ height - padding_top - padding_bottom
172
+ end
173
+
174
+ def natural_content_height
175
+ raise NotImplementedError, "subclasses must implement natural_content_height"
176
+ end
177
+
178
+ def colspan=(span)
179
+ raise Errors::InvalidTableSpan, "colspan must be provided in the table's structure, never in the initialization block. See Prawn's documentation for details." if @initializer_run
180
+
181
+ @colspan = span
182
+ end
183
+
184
+ def rowspan=(span)
185
+ raise Errors::InvalidTableSpan, "rowspan must be provided in the table's structure, never in the initialization block. See Prawn's documentation for details." if @initializer_run
186
+
187
+ @rowspan = span
188
+ end
189
+
190
+ def draw(point = [x, y])
191
+ Cell.draw_cells([[self, point]])
192
+ end
193
+
194
+ def draw_bounded_content(point)
195
+ @pdf.float do
196
+ @pdf.bounding_box([point[0] + padding_left, point[1] - padding_top],
197
+ width: spanned_content_width + FPTolerance, height: spanned_content_height + FPTolerance) { draw_content }
198
+ end
199
+ end
200
+
201
+ def x
202
+ @point[0]
203
+ end
204
+
205
+ def x=(value)
206
+ @point[0] = value
207
+ end
208
+
209
+ def relative_x
210
+ x + @pdf.bounds.left_side - @pdf.bounds.absolute_left
211
+ end
212
+
213
+ def y
214
+ @point[1]
215
+ end
216
+
217
+ def y=(value)
218
+ @point[1] = value
219
+ end
220
+
221
+ def relative_y(offset = 0)
222
+ y + offset - @pdf.bounds.absolute_bottom
223
+ end
224
+
225
+ def padding=(padding)
226
+ @padding = four_sides(padding, nil => [0, 0, 0, 0], Numeric => [padding] * 4) do
227
+ raise ArgumentError, ":padding must be a number or an array [v,h] or [t,r,b,l]"
228
+ end
229
+ end
230
+
231
+ %i[top right bottom left].each_with_index do |side, index|
232
+ define_method(:"padding_#{side}") { @padding[index] }
233
+ define_method(:"padding_#{side}=") { |value| @padding[index] = value }
234
+ define_method(:"border_#{side}_color") { @border_colors[index] }
235
+ define_method(:"border_#{side}_color=") { |value| @border_colors[index] = value }
236
+ define_method(:"border_#{side}_width") { @borders.include?(side) ? @border_widths[index] : 0 }
237
+ define_method(:"border_#{side}_width=") { |value| @border_widths[index] = value }
238
+ define_method(:"border_#{side}_line") { @borders.include?(side) ? @border_lines[index] : 0 }
239
+ define_method(:"border_#{side}_line=") { |value| @border_lines[index] = value }
240
+ end
241
+
242
+ def border_color=(color)
243
+ @border_colors = four_sides(color, nil => ["000000"] * 4, String => [color] * 4) do
244
+ raise ArgumentError, ":border_color must be a string or an array [v,h] or [t,r,b,l]"
245
+ end
246
+ end
247
+ alias_method :border_colors=, :border_color=
248
+
249
+ # Prawn-table resets a nil border width to four "000000": kept for compatibility.
250
+ def border_width=(width)
251
+ @border_widths = four_sides(width, nil => ["000000"] * 4, Numeric => [width] * 4) do
252
+ raise ArgumentError, ":border_width must be a string or an array [v,h] or [t,r,b,l]"
253
+ end
254
+ end
255
+ alias_method :border_widths=, :border_width=
256
+
257
+ def border_line=(line)
258
+ @border_lines =
259
+ if line.nil? then [:solid] * 4
260
+ elsif line.length == 1 then [line[0]] * 4
261
+ else four_sides(line) { raise ArgumentError, "border_line must be one of :solid, :dashed, :dotted or an array [v,h] or [t,r,b,l]" }
262
+ end
263
+ end
264
+ alias_method :border_lines=, :border_line=
265
+
266
+ def set_width_constraints
267
+ @min_width ||= padding_left + padding_right
268
+ @max_width ||= @pdf.bounds.width
269
+ end
270
+
271
+ def draw_background(point)
272
+ return unless background_color
273
+
274
+ @pdf.mask(:fill_color) do
275
+ @pdf.fill_color background_color
276
+ @pdf.fill_rectangle point, width, height
277
+ end
278
+ end
279
+
280
+ def draw_borders(point)
281
+ x, y = point
282
+ @pdf.mask(:line_width, :stroke_color) do
283
+ @borders.each do |border|
284
+ index = SIDE_INDEX[border]
285
+ border_width = @border_widths[index]
286
+ next if border_width <= 0
287
+
288
+ from, to =
289
+ case border
290
+ when :top then [[x, y], [x + width, y]]
291
+ when :bottom then [[x, y - height], [x + width, y - height]]
292
+ when :left then [[x, y + (border_top_width / 2.0)], [x, y - height - (border_bottom_width / 2.0)]]
293
+ when :right then [[x + width, y + (border_top_width / 2.0)], [x + width, y - height - (border_bottom_width / 2.0)]]
294
+ end
295
+ case @border_lines[index]
296
+ when :dashed then @pdf.dash border_width * 4
297
+ when :dotted then @pdf.dash border_width, space: border_width * 2
298
+ when :solid then nil
299
+ else raise ArgumentError, "border_line must be :solid, :dotted or :dashed"
300
+ end
301
+ @pdf.line_width = border_width
302
+ @pdf.stroke_color = @border_colors[index]
303
+ @pdf.stroke_line(from, to)
304
+ @pdf.undash if @pdf.dashed?
305
+ end
306
+ end
307
+ end
308
+
309
+ def draw_content
310
+ raise NotImplementedError, "subclasses must implement draw_content"
311
+ end
312
+
313
+ private
314
+
315
+ # A value for each side, CSS style: one for all, [vertical, horizontal], [top, horizontal, bottom] or four.
316
+ def four_sides(value, special_cases = {})
317
+ special_cases.each { |kind, sides| return sides if kind.nil? ? value.nil? : kind === value }
318
+ case value.length
319
+ when 2 then [value[0], value[1], value[0], value[1]]
320
+ when 3 then [value[0], value[1], value[2], value[1]]
321
+ when 4 then [value[0], value[1], value[2], value[3]]
322
+ else yield
323
+ end
324
+ end
325
+
326
+ module InTable
327
+ attr_accessor :row, :column
328
+ end
329
+ end
330
+ end
331
+ end