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,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
|