hexapdf 0.32.2 → 0.33.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 +4 -4
- data/CHANGELOG.md +63 -1
- data/README.md +9 -0
- data/examples/002-graphics.rb +15 -17
- data/examples/003-arcs.rb +9 -9
- data/examples/009-text_layouter_alignment.rb +1 -1
- data/examples/010-text_layouter_inline_boxes.rb +2 -2
- data/examples/011-text_layouter_line_wrapping.rb +1 -1
- data/examples/012-text_layouter_styling.rb +7 -7
- data/examples/013-text_layouter_shapes.rb +1 -1
- data/examples/014-text_in_polygon.rb +1 -1
- data/examples/015-boxes.rb +8 -7
- data/examples/016-frame_automatic_box_placement.rb +2 -2
- data/examples/017-frame_text_flow.rb +2 -1
- data/examples/018-composer.rb +1 -1
- data/examples/020-column_box.rb +2 -1
- data/examples/025-table_box.rb +46 -0
- data/lib/hexapdf/cli/command.rb +5 -2
- data/lib/hexapdf/cli/form.rb +5 -5
- data/lib/hexapdf/cli/inspect.rb +3 -3
- data/lib/hexapdf/composer.rb +104 -52
- data/lib/hexapdf/configuration.rb +44 -39
- data/lib/hexapdf/content/canvas.rb +393 -267
- data/lib/hexapdf/content/color_space.rb +72 -25
- data/lib/hexapdf/content/graphic_object/arc.rb +57 -24
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +66 -23
- data/lib/hexapdf/content/graphic_object/geom2d.rb +47 -6
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +58 -36
- data/lib/hexapdf/content/graphic_object.rb +6 -7
- data/lib/hexapdf/content/graphics_state.rb +54 -45
- data/lib/hexapdf/content/operator.rb +52 -54
- data/lib/hexapdf/content/parser.rb +2 -2
- data/lib/hexapdf/content/processor.rb +15 -15
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/content.rb +5 -0
- data/lib/hexapdf/dictionary.rb +6 -5
- data/lib/hexapdf/dictionary_fields.rb +42 -14
- data/lib/hexapdf/digital_signature/cms_handler.rb +2 -2
- data/lib/hexapdf/digital_signature/handler.rb +1 -1
- data/lib/hexapdf/digital_signature/pkcs1_handler.rb +2 -3
- data/lib/hexapdf/digital_signature/signature.rb +6 -6
- data/lib/hexapdf/digital_signature/signatures.rb +13 -12
- data/lib/hexapdf/digital_signature/signing/default_handler.rb +14 -5
- data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +2 -4
- data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +4 -4
- data/lib/hexapdf/digital_signature/signing.rb +4 -0
- data/lib/hexapdf/digital_signature/verification_result.rb +2 -2
- data/lib/hexapdf/digital_signature.rb +7 -2
- data/lib/hexapdf/document/destinations.rb +12 -11
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/layout.rb +167 -39
- data/lib/hexapdf/document/pages.rb +3 -2
- data/lib/hexapdf/document.rb +89 -55
- data/lib/hexapdf/encryption/aes.rb +5 -5
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +2 -2
- data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
- data/lib/hexapdf/encryption/identity.rb +1 -1
- data/lib/hexapdf/encryption/ruby_aes.rb +1 -1
- data/lib/hexapdf/encryption/ruby_arc4.rb +1 -1
- data/lib/hexapdf/encryption/security_handler.rb +31 -24
- data/lib/hexapdf/encryption/standard_security_handler.rb +45 -36
- data/lib/hexapdf/encryption.rb +7 -2
- data/lib/hexapdf/error.rb +18 -0
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/pass_through.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +1 -1
- data/lib/hexapdf/filter/run_length_decode.rb +1 -1
- data/lib/hexapdf/filter.rb +55 -6
- data/lib/hexapdf/font/cmap/parser.rb +2 -2
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +2 -2
- data/lib/hexapdf/font/encoding/standard_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/symbol_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/win_ansi_encoding.rb +3 -3
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +3 -0
- data/lib/hexapdf/font/true_type_wrapper.rb +17 -4
- data/lib/hexapdf/font/type1_wrapper.rb +19 -4
- data/lib/hexapdf/font_loader/from_configuration.rb +5 -2
- data/lib/hexapdf/font_loader/from_file.rb +5 -5
- data/lib/hexapdf/font_loader/standard14.rb +3 -3
- data/lib/hexapdf/font_loader.rb +3 -0
- data/lib/hexapdf/image_loader/jpeg.rb +2 -2
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/importer.rb +13 -0
- data/lib/hexapdf/layout/box.rb +9 -2
- data/lib/hexapdf/layout/box_fitter.rb +2 -2
- data/lib/hexapdf/layout/column_box.rb +18 -4
- data/lib/hexapdf/layout/frame.rb +30 -12
- data/lib/hexapdf/layout/image_box.rb +5 -0
- data/lib/hexapdf/layout/inline_box.rb +1 -0
- data/lib/hexapdf/layout/list_box.rb +17 -1
- data/lib/hexapdf/layout/page_style.rb +4 -4
- data/lib/hexapdf/layout/style.rb +18 -3
- data/lib/hexapdf/layout/table_box.rb +682 -0
- data/lib/hexapdf/layout/text_box.rb +5 -3
- data/lib/hexapdf/layout/text_fragment.rb +1 -1
- data/lib/hexapdf/layout/text_layouter.rb +12 -4
- data/lib/hexapdf/layout.rb +1 -0
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +18 -7
- data/lib/hexapdf/parser.rb +7 -7
- data/lib/hexapdf/pdf_array.rb +1 -1
- data/lib/hexapdf/rectangle.rb +1 -1
- data/lib/hexapdf/reference.rb +1 -1
- data/lib/hexapdf/revision.rb +1 -1
- data/lib/hexapdf/revisions.rb +3 -3
- data/lib/hexapdf/serializer.rb +15 -15
- data/lib/hexapdf/stream.rb +4 -2
- data/lib/hexapdf/tokenizer.rb +14 -14
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +22 -22
- data/lib/hexapdf/type/acro_form/button_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/choice_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/field.rb +2 -2
- data/lib/hexapdf/type/acro_form/form.rb +1 -1
- data/lib/hexapdf/type/acro_form/signature_field.rb +4 -4
- data/lib/hexapdf/type/acro_form/text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form/variable_text_field.rb +1 -1
- data/lib/hexapdf/type/acro_form.rb +1 -1
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/actions/go_to.rb +1 -1
- data/lib/hexapdf/type/actions/go_to_r.rb +1 -1
- data/lib/hexapdf/type/actions/launch.rb +1 -1
- data/lib/hexapdf/type/actions/uri.rb +1 -1
- data/lib/hexapdf/type/actions.rb +1 -1
- data/lib/hexapdf/type/annotation.rb +3 -3
- data/lib/hexapdf/type/annotations/link.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/annotations/text.rb +1 -1
- data/lib/hexapdf/type/annotations/widget.rb +2 -2
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +3 -3
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +2 -2
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +2 -2
- data/lib/hexapdf/type/font_type0.rb +3 -3
- data/lib/hexapdf/type/font_type3.rb +1 -1
- data/lib/hexapdf/type/form.rb +1 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
- data/lib/hexapdf/type/icon_fit.rb +1 -1
- data/lib/hexapdf/type/image.rb +1 -1
- data/lib/hexapdf/type/info.rb +1 -1
- data/lib/hexapdf/type/mark_information.rb +1 -1
- data/lib/hexapdf/type/names.rb +2 -2
- data/lib/hexapdf/type/object_stream.rb +2 -1
- data/lib/hexapdf/type/outline.rb +1 -1
- data/lib/hexapdf/type/outline_item.rb +1 -1
- data/lib/hexapdf/type/page.rb +19 -10
- data/lib/hexapdf/type/page_label.rb +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +1 -1
- data/lib/hexapdf/type/resources.rb +1 -1
- data/lib/hexapdf/type/trailer.rb +2 -2
- data/lib/hexapdf/type/viewer_preferences.rb +1 -1
- data/lib/hexapdf/type/xref_stream.rb +2 -2
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +4 -4
- data/lib/hexapdf/xref_section.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_endpoint_arc.rb +11 -1
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +7 -0
- data/test/hexapdf/content/test_canvas.rb +0 -1
- data/test/hexapdf/digital_signature/test_signatures.rb +22 -0
- data/test/hexapdf/document/test_files.rb +2 -2
- data/test/hexapdf/document/test_layout.rb +98 -0
- data/test/hexapdf/encryption/test_security_handler.rb +12 -11
- data/test/hexapdf/encryption/test_standard_security_handler.rb +35 -23
- data/test/hexapdf/font/test_true_type_wrapper.rb +18 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +15 -1
- data/test/hexapdf/layout/test_box.rb +1 -1
- data/test/hexapdf/layout/test_column_box.rb +65 -21
- data/test/hexapdf/layout/test_frame.rb +14 -14
- data/test/hexapdf/layout/test_image_box.rb +4 -0
- data/test/hexapdf/layout/test_inline_box.rb +5 -0
- data/test/hexapdf/layout/test_list_box.rb +40 -6
- data/test/hexapdf/layout/test_page_style.rb +3 -2
- data/test/hexapdf/layout/test_style.rb +50 -0
- data/test/hexapdf/layout/test_table_box.rb +722 -0
- data/test/hexapdf/layout/test_text_box.rb +18 -0
- data/test/hexapdf/layout/test_text_layouter.rb +4 -0
- data/test/hexapdf/test_dictionary_fields.rb +4 -1
- data/test/hexapdf/test_document.rb +1 -0
- data/test/hexapdf/test_filter.rb +8 -0
- data/test/hexapdf/test_importer.rb +9 -0
- data/test/hexapdf/test_object.rb +16 -5
- data/test/hexapdf/test_stream.rb +7 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +13 -5
- data/test/hexapdf/type/acro_form/test_form.rb +4 -3
- data/test/hexapdf/type/test_page.rb +18 -4
- metadata +17 -8
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
# -*- encoding: utf-8; frozen_string_literal: true -*-
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# This file is part of HexaPDF.
|
|
5
|
+
#
|
|
6
|
+
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
+
# Copyright (C) 2014-2023 Thomas Leitner
|
|
8
|
+
#
|
|
9
|
+
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU Affero General Public License version 3 as
|
|
11
|
+
# published by the Free Software Foundation with the addition of the
|
|
12
|
+
# following permission added to Section 15 as permitted in Section 7(a):
|
|
13
|
+
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
|
|
14
|
+
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
|
|
15
|
+
# INFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
16
|
+
#
|
|
17
|
+
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
|
|
18
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
20
|
+
# License for more details.
|
|
21
|
+
#
|
|
22
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
23
|
+
# along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
|
|
24
|
+
#
|
|
25
|
+
# The interactive user interfaces in modified source and object code
|
|
26
|
+
# versions of HexaPDF must display Appropriate Legal Notices, as required
|
|
27
|
+
# under Section 5 of the GNU Affero General Public License version 3.
|
|
28
|
+
#
|
|
29
|
+
# In accordance with Section 7(b) of the GNU Affero General Public
|
|
30
|
+
# License, a covered work must retain the producer line in every PDF that
|
|
31
|
+
# is created or manipulated using HexaPDF.
|
|
32
|
+
#
|
|
33
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
|
+
#++
|
|
36
|
+
|
|
37
|
+
require 'hexapdf/layout/box'
|
|
38
|
+
require 'hexapdf/layout/frame'
|
|
39
|
+
|
|
40
|
+
module HexaPDF
|
|
41
|
+
module Layout
|
|
42
|
+
|
|
43
|
+
# A TableBox allows placing boxes in a table.
|
|
44
|
+
#
|
|
45
|
+
# A table box instance can be fit into a rectangular area. The widths of the columns is
|
|
46
|
+
# determined by the #column_widths definition. This means that there is no auto-sizing
|
|
47
|
+
# supported.
|
|
48
|
+
#
|
|
49
|
+
# If some rows don't fit into the provided area, the table is split. The style of the original
|
|
50
|
+
# table is also applied to the split box.
|
|
51
|
+
#
|
|
52
|
+
#
|
|
53
|
+
# == Table Cell
|
|
54
|
+
#
|
|
55
|
+
# Each table cell is a Box instance and can have an associated style, e.g. for creating borders
|
|
56
|
+
# around the cell contents. It is also possible to create cells that span more than one row or
|
|
57
|
+
# column. By default a cell has a solid, black, 1pt border and a padding of 5pt on all sides.
|
|
58
|
+
#
|
|
59
|
+
# It is important to note that the drawing of cell borders (just the drawing, size calculations
|
|
60
|
+
# are done as usual) are handled differently from standard box borders. While standard box
|
|
61
|
+
# borders are drawn inside the box, cell borders are drawn on the bounds of the box. This means
|
|
62
|
+
# that, visually, the borders of adjoining cells overlap, with the borders of cells to the right
|
|
63
|
+
# and bottom being on top.
|
|
64
|
+
#
|
|
65
|
+
# To make sure that the cell borders are not outside of the table's bounds, the left and top
|
|
66
|
+
# border widths of the top-left cell and the right and bottom border widths of the bottom-right
|
|
67
|
+
# cell are taken into account when calculating the available space.
|
|
68
|
+
#
|
|
69
|
+
#
|
|
70
|
+
# == Examples
|
|
71
|
+
#
|
|
72
|
+
# Let's start with a basic table:
|
|
73
|
+
#
|
|
74
|
+
# #>pdf-composer
|
|
75
|
+
# cells = [[layout.text('A'), layout.text('B')],
|
|
76
|
+
# [layout.text('C'), layout.text('D')]]
|
|
77
|
+
# composer.table(cells)
|
|
78
|
+
#
|
|
79
|
+
# The HexaPDF::Document::Layout#table_box method accepts the cells as positional argument
|
|
80
|
+
# instead of as keyword argument but all other arguments of ::new work the same.
|
|
81
|
+
#
|
|
82
|
+
# While the table box itself only allows box instances as cell contents, the layout helper
|
|
83
|
+
# method also allows text which it transforms to text boxes. So this is the same as the above:
|
|
84
|
+
#
|
|
85
|
+
# #>pdf-composer
|
|
86
|
+
# composer.table([['A', 'B'], ['C', 'D']])
|
|
87
|
+
#
|
|
88
|
+
# The style of the cells can be customized, e.g. to avoid drawing borders:
|
|
89
|
+
#
|
|
90
|
+
# #>pdf-composer
|
|
91
|
+
# cells = [[layout.text('A'), layout.text('B')],
|
|
92
|
+
# [layout.text('C'), layout.text('D')]]
|
|
93
|
+
# composer.table(cells, cell_style: {border: {width: 0}})
|
|
94
|
+
#
|
|
95
|
+
# If the table doesn't fit completely, it is automatically split (in this case, the last row
|
|
96
|
+
# gets moved to the second column):
|
|
97
|
+
#
|
|
98
|
+
# #>pdf-composer
|
|
99
|
+
# cells = [[layout.text('A'), layout.text('B')],
|
|
100
|
+
# [layout.text('C'), layout.text('D')],
|
|
101
|
+
# [layout.text('E'), layout.text('F')]]
|
|
102
|
+
# composer.column(height: 50) {|col| col.table(cells) }
|
|
103
|
+
#
|
|
104
|
+
# It is also possible to use row and column spans:
|
|
105
|
+
#
|
|
106
|
+
# #>pdf-composer
|
|
107
|
+
# cells = [[{content: layout.text('A'), col_span: 2}, {content: layout.text('B'), row_span: 2}],
|
|
108
|
+
# [{content: layout.text('C'), col_span: 2, row_span: 2}],
|
|
109
|
+
# [layout.text('D')]]
|
|
110
|
+
# composer.table(cells)
|
|
111
|
+
#
|
|
112
|
+
# Each table can have header rows and footer rows which are shown for all split parts:
|
|
113
|
+
#
|
|
114
|
+
# #>pdf-composer
|
|
115
|
+
# header = lambda {|tb| [[{content: layout.text('Header', align: :center), col_span: 2}]] }
|
|
116
|
+
# footer = lambda {|tb| [[layout.text('left'), layout.text('right', align: :right)]] }
|
|
117
|
+
# cells = [[layout.text('A'), layout.text('B')],
|
|
118
|
+
# [layout.text('C'), layout.text('D')],
|
|
119
|
+
# [layout.text('E'), layout.text('F')]]
|
|
120
|
+
# composer.column(height: 90) {|col| col.table(cells, header: header, footer: footer) }
|
|
121
|
+
#
|
|
122
|
+
# The cells can be styled using a callable object for more complex styling:
|
|
123
|
+
#
|
|
124
|
+
# #>pdf-composer
|
|
125
|
+
# cells = [[layout.text('A'), layout.text('B')],
|
|
126
|
+
# [layout.text('C'), layout.text('D')]]
|
|
127
|
+
# block = lambda do |cell|
|
|
128
|
+
# cell.style.background_color =
|
|
129
|
+
# (cell.row == 0 && cell.column == 0 ? 'ffffaa' : 'ffffee')
|
|
130
|
+
# end
|
|
131
|
+
# composer.table(cells, cell_style: block)
|
|
132
|
+
class TableBox < Box
|
|
133
|
+
|
|
134
|
+
# Represents a single cell of the table.
|
|
135
|
+
#
|
|
136
|
+
# A cell is a container box that fits and draws its children with a BoxFitter. Its dimensions
|
|
137
|
+
# (width and height) are not determined by its children but by the table layout algorithm.
|
|
138
|
+
# Furthermore, its style can be used for drawing e.g. a cell border.
|
|
139
|
+
#
|
|
140
|
+
# Cell borders work similar to the separated borders model of CSS, i.e. each cell has its own
|
|
141
|
+
# borders that do not overlap.
|
|
142
|
+
class Cell < Box
|
|
143
|
+
|
|
144
|
+
# The x-coordinate of the cell's top-left corner.
|
|
145
|
+
#
|
|
146
|
+
# The coordinate is relative to the table's content rectangle, with positive x-axis going to
|
|
147
|
+
# the right and positive y-axis going to the bottom.
|
|
148
|
+
#
|
|
149
|
+
# This value is set by the parent Cells object during fitting and may therefore only be
|
|
150
|
+
# relied on afterwards.
|
|
151
|
+
attr_accessor :left
|
|
152
|
+
|
|
153
|
+
# The y-coordinate of the cell's top-left corner.
|
|
154
|
+
#
|
|
155
|
+
# The coordinate is relative to the table's content rectangle, with positive x-axis going to
|
|
156
|
+
# the right and positive y-axis going to the bottom.
|
|
157
|
+
#
|
|
158
|
+
# This value is set by the parent Cells object during fitting and may therefore only be
|
|
159
|
+
# relied on afterwards.
|
|
160
|
+
attr_accessor :top
|
|
161
|
+
|
|
162
|
+
# The preferred width of the cell, determined during #fit.
|
|
163
|
+
attr_reader :preferred_width
|
|
164
|
+
|
|
165
|
+
# The preferred height of the cell, determined during #fit.
|
|
166
|
+
attr_reader :preferred_height
|
|
167
|
+
|
|
168
|
+
# The 0-based row number of the cell.
|
|
169
|
+
attr_reader :row
|
|
170
|
+
|
|
171
|
+
# The 0-based column number of the cell.
|
|
172
|
+
attr_reader :column
|
|
173
|
+
|
|
174
|
+
# The number of rows this cell spans.
|
|
175
|
+
attr_reader :row_span
|
|
176
|
+
|
|
177
|
+
# The number of columns this cell spans.
|
|
178
|
+
attr_reader :col_span
|
|
179
|
+
|
|
180
|
+
# The boxes to layout inside this cell.
|
|
181
|
+
#
|
|
182
|
+
# This may either be +nil+ (if the cell has no content), a single Box instance or an array
|
|
183
|
+
# of Box instances.
|
|
184
|
+
attr_accessor :children
|
|
185
|
+
|
|
186
|
+
# Creates a new Cell instance.
|
|
187
|
+
def initialize(row:, column:, children: nil, row_span: nil, col_span: nil, **kwargs)
|
|
188
|
+
super(**kwargs, width: 0, height: 0)
|
|
189
|
+
@children = children
|
|
190
|
+
@row = row
|
|
191
|
+
@column = column
|
|
192
|
+
@row_span = row_span || 1
|
|
193
|
+
@col_span = col_span || 1
|
|
194
|
+
style.border.width.set(1) unless style.border?
|
|
195
|
+
style.border.draw_on_bounds = true
|
|
196
|
+
style.padding.set(5) unless style.padding?
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Returns +true+ if the cell has no content.
|
|
200
|
+
def empty?
|
|
201
|
+
super && (!@fit_results || @fit_results.empty?)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Updates the height of the box to the given value.
|
|
205
|
+
#
|
|
206
|
+
# The +height+ has to be greater than or equal to the fitted height.
|
|
207
|
+
def update_height(height)
|
|
208
|
+
if height < @height
|
|
209
|
+
raise HexaPDF::Error, "Given height needs to be at least as big as fitted height"
|
|
210
|
+
end
|
|
211
|
+
@height = height
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Fits the children of the table cell into the given rectangular area.
|
|
215
|
+
def fit(available_width, available_height, _frame)
|
|
216
|
+
@width = available_width
|
|
217
|
+
width = available_width - reserved_width
|
|
218
|
+
height = available_height - reserved_height
|
|
219
|
+
return false if width <= 0 || height <= 0
|
|
220
|
+
|
|
221
|
+
frame = Frame.new(0, 0, width, height)
|
|
222
|
+
case children
|
|
223
|
+
when Box
|
|
224
|
+
fit_result = frame.fit(children)
|
|
225
|
+
@preferred_width = fit_result.x + fit_result.box.width + reserved_width
|
|
226
|
+
@height = @preferred_height = fit_result.box.height + reserved_height
|
|
227
|
+
@fit_results = [fit_result]
|
|
228
|
+
@fit_successful = fit_result.success?
|
|
229
|
+
when Array
|
|
230
|
+
box_fitter = BoxFitter.new([frame])
|
|
231
|
+
children.each {|box| box_fitter.fit(box) }
|
|
232
|
+
max_x_result = box_fitter.fit_results.max_by {|result| result.x + result.box.width }
|
|
233
|
+
@preferred_width = max_x_result.x + max_x_result.box.width + reserved_width
|
|
234
|
+
@height = @preferred_height = box_fitter.content_heights[0] + reserved_height
|
|
235
|
+
@fit_results = box_fitter.fit_results
|
|
236
|
+
@fit_successful = box_fitter.fit_successful?
|
|
237
|
+
else
|
|
238
|
+
@preferred_width = reserved_width
|
|
239
|
+
@height = @preferred_height = reserved_height
|
|
240
|
+
@fit_results = []
|
|
241
|
+
@fit_successful = true
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# :nodoc:
|
|
246
|
+
def inspect
|
|
247
|
+
"<Cell (#{row},#{column}) #{row_span}x#{col_span} #{Array(children).map(&:class)}>"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
private
|
|
251
|
+
|
|
252
|
+
# Draws the content of the cell.
|
|
253
|
+
def draw_content(canvas, x, y)
|
|
254
|
+
return if @fit_results.empty?
|
|
255
|
+
|
|
256
|
+
# available_width is always equal to content_width but we need to adjust for the
|
|
257
|
+
# difference in the y direction between fitting and drawing
|
|
258
|
+
y -= (@fit_results[0].available_height - content_height)
|
|
259
|
+
@fit_results.each do |fit_result|
|
|
260
|
+
fit_result.x += x
|
|
261
|
+
fit_result.y += y
|
|
262
|
+
fit_result.draw(canvas)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Represents the cells of a TableBox.
|
|
269
|
+
#
|
|
270
|
+
# This class is a wrapper around an array of arrays and provides some utility methods for
|
|
271
|
+
# managing and styling the cells.
|
|
272
|
+
#
|
|
273
|
+
# == Table data transformation into correct form
|
|
274
|
+
#
|
|
275
|
+
# One of the main purposes of this class is to transform the cell data provided on
|
|
276
|
+
# initialization into the representation a TableBox instance can work with.
|
|
277
|
+
#
|
|
278
|
+
# The +data+ argument for ::new is an array of arrays representing the rows of the table. Each
|
|
279
|
+
# row array may contain one of the following items:
|
|
280
|
+
#
|
|
281
|
+
# * A single Box instance defining the content of the cell.
|
|
282
|
+
#
|
|
283
|
+
# * An array of Box instances defining the content of the cell.
|
|
284
|
+
#
|
|
285
|
+
# * A hash which defines the content of the cell as well as, optionally, additional
|
|
286
|
+
# information through the following keys:
|
|
287
|
+
#
|
|
288
|
+
# +:content+:: The content for the cell. This may be a single Box or an array of Box
|
|
289
|
+
# instances.
|
|
290
|
+
#
|
|
291
|
+
# +:row_span+:: An integer specifying the number of rows this cell should span.
|
|
292
|
+
#
|
|
293
|
+
# +:col_span+:: An integer specifying the number of columsn this cell should span.
|
|
294
|
+
#
|
|
295
|
+
# +:properties+:: A hash of properties (see Box#properties) to be set on the cell itself.
|
|
296
|
+
#
|
|
297
|
+
# All other key-value pairs are taken to be cell styling information (like
|
|
298
|
+
# +:background_color+) and assigned to the cell style.
|
|
299
|
+
#
|
|
300
|
+
# Additionally, the first item in the +data+ argument is treated specially if it is not an
|
|
301
|
+
# array:
|
|
302
|
+
#
|
|
303
|
+
# * If it is a hash, it is assumed to be style properties to be set on all created cell
|
|
304
|
+
# instances.
|
|
305
|
+
#
|
|
306
|
+
# * If it is a callable object, it needs to accept a cell as argument and is called for all
|
|
307
|
+
# created cell instances.
|
|
308
|
+
#
|
|
309
|
+
# Any properties or styling information retrieved from the respective item in +data+ takes
|
|
310
|
+
# precedence over the above globally specified information.
|
|
311
|
+
#
|
|
312
|
+
# Here is an example input data array:
|
|
313
|
+
#
|
|
314
|
+
# data = [[box1, {col_span: 2, content: box2}, box3],
|
|
315
|
+
# [box4, box5, {col_span: 2, row_span: 2, content: [box6.1, box6.2]}],
|
|
316
|
+
# [box7, box8]]
|
|
317
|
+
#
|
|
318
|
+
# And this is what the table will look like:
|
|
319
|
+
#
|
|
320
|
+
# | box1 | box2 | box 3 |
|
|
321
|
+
# | box4 | box5 | box6.1 box6.2 |
|
|
322
|
+
# | box7 | box8 | |
|
|
323
|
+
class Cells
|
|
324
|
+
|
|
325
|
+
# Creates a new Cells instance with the given +data+ which cannot be changed afterwards.
|
|
326
|
+
#
|
|
327
|
+
# The optional +cell_style+ argument can either be a hash of style properties to be assigned
|
|
328
|
+
# to every cell or a block accepting a cell for more control over e.g. style assignment. If
|
|
329
|
+
# the +data+ has such a cell style as its first item, the +cell_style+ argument is not used.
|
|
330
|
+
#
|
|
331
|
+
# See the class documentation for details on the +data+ argument.
|
|
332
|
+
def initialize(data, cell_style: nil)
|
|
333
|
+
@cells = []
|
|
334
|
+
@number_of_columns = 0
|
|
335
|
+
assign_data(data, cell_style)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Returns the cell (a Cell instance) in the given row and column.
|
|
339
|
+
#
|
|
340
|
+
# Note that the same cell instance may be returned for different (row, column) arguments if
|
|
341
|
+
# the cell spans more than one row and/or column.
|
|
342
|
+
def [](row, column)
|
|
343
|
+
@cells[row]&.[](column)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Returns the number of rows.
|
|
347
|
+
def number_of_rows
|
|
348
|
+
@cells.size
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Returns the number of columns.
|
|
352
|
+
def number_of_columns
|
|
353
|
+
@number_of_columns
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Iterates over each row.
|
|
357
|
+
def each_row(&block)
|
|
358
|
+
@cells.each(&block)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Applies the given style properties to all cells and optionally yields all cells for more
|
|
362
|
+
# complex customization.
|
|
363
|
+
def style(**properties, &block)
|
|
364
|
+
@cells.each do |columns|
|
|
365
|
+
columns.each do |cell|
|
|
366
|
+
cell.style.update(**properties)
|
|
367
|
+
block&.call(cell)
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Fits all rows starting from +start_row+ into an area with the given +available_height+,
|
|
373
|
+
# using the column information in +column_info+. Returns the used height as well as the row
|
|
374
|
+
# index of the last row that fit (which may be -1 if no row fits).
|
|
375
|
+
#
|
|
376
|
+
# The +column_info+ argument needs to be an array of arrays of the form [x_pos, width]
|
|
377
|
+
# containing the horizontal positions and widths of each column.
|
|
378
|
+
#
|
|
379
|
+
# The fitting of a cell is done through the Cell#fit method which stores the result in the
|
|
380
|
+
# cell itself. Furthermore, Cell#left and Cell#top are also assigned correctly.
|
|
381
|
+
def fit_rows(start_row, available_height, column_info)
|
|
382
|
+
height = available_height
|
|
383
|
+
last_fitted_row_index = -1
|
|
384
|
+
@cells[start_row..-1].each.with_index(start_row) do |columns, row_index|
|
|
385
|
+
row_fit = true
|
|
386
|
+
row_height = 0
|
|
387
|
+
columns.each_with_index do |cell, col_index|
|
|
388
|
+
next if cell.row != row_index || cell.column != col_index
|
|
389
|
+
available_cell_width = if cell.col_span > 1
|
|
390
|
+
column_info[cell.column, cell.col_span].map(&:last).sum
|
|
391
|
+
else
|
|
392
|
+
column_info[cell.column].last
|
|
393
|
+
end
|
|
394
|
+
unless cell.fit(available_cell_width, available_height, nil)
|
|
395
|
+
row_fit = false
|
|
396
|
+
break
|
|
397
|
+
end
|
|
398
|
+
cell.left = column_info[cell.column].first
|
|
399
|
+
cell.top = height - available_height
|
|
400
|
+
row_height = cell.preferred_height if row_height < cell.preferred_height
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
if row_fit
|
|
404
|
+
seen = {}
|
|
405
|
+
columns.each do |cell|
|
|
406
|
+
next if seen[cell]
|
|
407
|
+
cell.update_height(cell.row == row_index ? row_height : cell.height + row_height)
|
|
408
|
+
seen[cell] = true
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
last_fitted_row_index = row_index
|
|
412
|
+
available_height -= row_height
|
|
413
|
+
else
|
|
414
|
+
last_fitted_row_index = columns.min_by(&:row).row - 1 if height != available_height
|
|
415
|
+
break
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
[height - available_height, last_fitted_row_index]
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Draws the rows from +start_row+ to +end_row+ on the given +canvas+, with the top-left
|
|
422
|
+
# corner of the resulting table being at (+x+, +y+).
|
|
423
|
+
def draw_rows(start_row, end_row, canvas, x, y)
|
|
424
|
+
@cells[start_row..end_row].each.with_index(start_row) do |columns, row_index|
|
|
425
|
+
columns.each_with_index do |cell, col_index|
|
|
426
|
+
next if cell.row != row_index || cell.column != col_index
|
|
427
|
+
cell.draw(canvas, x + cell.left, y - cell.top - cell.height)
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
private
|
|
433
|
+
|
|
434
|
+
# Assigns the +data+ to the individual cells, taking row and column spans into account.
|
|
435
|
+
#
|
|
436
|
+
# For details on the +cell_style+ argument see ::new.
|
|
437
|
+
def assign_data(data, cell_style)
|
|
438
|
+
cell_style = data.shift unless data[0].kind_of?(Array)
|
|
439
|
+
cell_style_block = if cell_style.kind_of?(Hash)
|
|
440
|
+
lambda {|cell| cell.style.update(**cell_style) }
|
|
441
|
+
else
|
|
442
|
+
cell_style
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
data.each_with_index do |cols, row_index|
|
|
446
|
+
# Only add new row array if it hasn't been added due to row spans before
|
|
447
|
+
@cells << [] unless @cells[row_index]
|
|
448
|
+
row = @cells[row_index]
|
|
449
|
+
col_index = 0
|
|
450
|
+
|
|
451
|
+
cols.each do |content|
|
|
452
|
+
# Ignore already filled in cells due to row/col spans
|
|
453
|
+
col_index += 1 while row[col_index]
|
|
454
|
+
|
|
455
|
+
children = content
|
|
456
|
+
if content.kind_of?(Hash)
|
|
457
|
+
children = content.delete(:content)
|
|
458
|
+
row_span = content.delete(:row_span)
|
|
459
|
+
col_span = content.delete(:col_span)
|
|
460
|
+
properties = content.delete(:properties)
|
|
461
|
+
style = content
|
|
462
|
+
end
|
|
463
|
+
cell = Cell.new(children: children, row: row_index, column: col_index,
|
|
464
|
+
row_span: row_span, col_span: col_span)
|
|
465
|
+
cell_style_block&.call(cell)
|
|
466
|
+
cell.style.update(**style) if style
|
|
467
|
+
cell.properties.update(properties) if properties
|
|
468
|
+
|
|
469
|
+
row[col_index] = cell
|
|
470
|
+
if cell.row_span > 1 || cell.col_span > 1
|
|
471
|
+
row_index.upto(row_index + cell.row_span - 1) do |r|
|
|
472
|
+
@cells << [] unless @cells[r]
|
|
473
|
+
col_index.upto(col_index + cell.col_span - 1) do |c|
|
|
474
|
+
@cells[r][c] = cell
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
col_index += cell.col_span
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
@number_of_columns = col_index if @number_of_columns < col_index
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# The Cells instance containing the data of the table.
|
|
489
|
+
#
|
|
490
|
+
# If this is an instance that was split from another one, the cells contain *all* the rows,
|
|
491
|
+
# not just the ones for this split instance.
|
|
492
|
+
#
|
|
493
|
+
# Also see #start_row_index.
|
|
494
|
+
attr_reader :cells
|
|
495
|
+
|
|
496
|
+
# The Cells instance containing the header cells of the table.
|
|
497
|
+
#
|
|
498
|
+
# If this is a TableBox instance that was split from another one, the header cells are created
|
|
499
|
+
# again through the use of +header+ block supplied to ::new.
|
|
500
|
+
attr_reader :header_cells
|
|
501
|
+
|
|
502
|
+
# The Cells instance containing the footer cells of the table.
|
|
503
|
+
#
|
|
504
|
+
# If this is a TableBox instance that was split from another one, the footer cells are created
|
|
505
|
+
# again through the use of +footer+ block supplied to ::new.
|
|
506
|
+
attr_reader :footer_cells
|
|
507
|
+
|
|
508
|
+
# The column widths definition.
|
|
509
|
+
#
|
|
510
|
+
# See ::new for details.
|
|
511
|
+
attr_reader :column_widths
|
|
512
|
+
|
|
513
|
+
# The row index into the #cells from which this instance starts fitting the rows.
|
|
514
|
+
#
|
|
515
|
+
# This value is 0 if this instance was not split from another one. Otherwise, it contains the
|
|
516
|
+
# correct start index.
|
|
517
|
+
attr_reader :start_row_index
|
|
518
|
+
|
|
519
|
+
# This value is -1 if #fit was not yet called. Otherwise it contains the row index of the last
|
|
520
|
+
# row that could be fitted.
|
|
521
|
+
attr_reader :last_fitted_row_index
|
|
522
|
+
|
|
523
|
+
# Creates a new TableBox instance.
|
|
524
|
+
#
|
|
525
|
+
# +cells+::
|
|
526
|
+
#
|
|
527
|
+
# This needs to be an array of arrays containing the data of the table. See Cells for more
|
|
528
|
+
# information on the allowed contents.
|
|
529
|
+
#
|
|
530
|
+
# Alternatively, a Cells instance can be used. Note that in this case the +cell_style+
|
|
531
|
+
# argument is not used.
|
|
532
|
+
#
|
|
533
|
+
# +column_widths+::
|
|
534
|
+
#
|
|
535
|
+
# An array defining the width of the columns of the table. If not set, defaults to an
|
|
536
|
+
# empty array.
|
|
537
|
+
#
|
|
538
|
+
# Each entry in the array may either be a positive or negative number. A positive number
|
|
539
|
+
# sets a fixed width for the respective column.
|
|
540
|
+
#
|
|
541
|
+
# A negative number specifies that the respective column is auto-sized. Such columns split
|
|
542
|
+
# the remaining width (after substracting the widths of the fixed columns) proportionally
|
|
543
|
+
# among them. For example, if the column width definition is [-1, -2, -2], the first
|
|
544
|
+
# column is a fifth of the width and the other two columns are each two fifth of the
|
|
545
|
+
# width.
|
|
546
|
+
#
|
|
547
|
+
# If the +cells+ definition has more columns than specified by +column_widths+, the
|
|
548
|
+
# missing entries are assumed to be -1.
|
|
549
|
+
#
|
|
550
|
+
# +header+::
|
|
551
|
+
#
|
|
552
|
+
# A callable object that needs to accept this TableBox instance as argument and that
|
|
553
|
+
# returns an array of arrays containing the header rows.
|
|
554
|
+
#
|
|
555
|
+
# The header rows are shown for the table instance and all split boxes.
|
|
556
|
+
#
|
|
557
|
+
# +footer+::
|
|
558
|
+
#
|
|
559
|
+
# A callable object that needs to accept this TableBox instance as argument and that
|
|
560
|
+
# returns an array of arrays containing the footer rows.
|
|
561
|
+
#
|
|
562
|
+
# The footer rows are shown for the table instance and all split boxes.
|
|
563
|
+
#
|
|
564
|
+
# +cell_style+::
|
|
565
|
+
#
|
|
566
|
+
# Contains styling information that should be applied to all header, body and footer
|
|
567
|
+
# cells.
|
|
568
|
+
#
|
|
569
|
+
# This can either be a hash containing style properties or a callable object accepting a
|
|
570
|
+
# cell as argument.
|
|
571
|
+
def initialize(cells:, column_widths: nil, header: nil, footer: nil, cell_style: nil, **kwargs)
|
|
572
|
+
super(**kwargs)
|
|
573
|
+
@cell_style = cell_style
|
|
574
|
+
@cells = cells.kind_of?(Cells) ? cells : Cells.new(cells, cell_style: @cell_style)
|
|
575
|
+
@column_widths = column_widths || []
|
|
576
|
+
@start_row_index = 0
|
|
577
|
+
@last_fitted_row_index = -1
|
|
578
|
+
@header = header
|
|
579
|
+
@header_cells = Cells.new(header.call(self), cell_style: @cell_style) if header
|
|
580
|
+
@footer = footer
|
|
581
|
+
@footer_cells = Cells.new(footer.call(self), cell_style: @cell_style) if footer
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Returns +true+ if not a single row could be fit.
|
|
585
|
+
def empty?
|
|
586
|
+
super && (!@last_fitted_row_index || @last_fitted_row_index < 0)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Fits the table into the available space.
|
|
590
|
+
def fit(available_width, available_height, _frame)
|
|
591
|
+
return false if (@initial_width > 0 && @initial_width > available_width) ||
|
|
592
|
+
(@initial_height > 0 && @initial_height > available_height)
|
|
593
|
+
|
|
594
|
+
# Adjust reserved width/height to include space used by the edge cells for their border
|
|
595
|
+
# since cell borders are drawn on the bounds and not inside.
|
|
596
|
+
# This uses the top-left and bottom-right cells and so might not be correct in all cases.
|
|
597
|
+
@cell_tl_border_width = @cells[0, 0].style.border.width
|
|
598
|
+
cell_br_border_width = @cells[-1, -1].style.border.width
|
|
599
|
+
rw = reserved_width + (@cell_tl_border_width.left + cell_br_border_width.right) / 2.0
|
|
600
|
+
rh = reserved_height + (@cell_tl_border_width.top + cell_br_border_width.bottom) / 2.0
|
|
601
|
+
|
|
602
|
+
width = (@initial_width > 0 ? @initial_width : available_width) - rw
|
|
603
|
+
height = (@initial_height > 0 ? @initial_height : available_height) - rh
|
|
604
|
+
used_height = 0
|
|
605
|
+
columns = calculate_column_widths(width)
|
|
606
|
+
return false if columns.empty?
|
|
607
|
+
|
|
608
|
+
@special_cells_fit_not_successful = false
|
|
609
|
+
[@header_cells, @footer_cells].each do |special_cells|
|
|
610
|
+
next unless special_cells
|
|
611
|
+
special_used_height, last_fitted_row_index = special_cells.fit_rows(0, height, columns)
|
|
612
|
+
height -= special_used_height
|
|
613
|
+
used_height += special_used_height
|
|
614
|
+
@special_cells_fit_not_successful = (last_fitted_row_index != special_cells.number_of_rows - 1)
|
|
615
|
+
return false if @special_cells_fit_not_successful
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
main_used_height, @last_fitted_row_index = @cells.fit_rows(@start_row_index, height, columns)
|
|
619
|
+
used_height += main_used_height
|
|
620
|
+
|
|
621
|
+
@width = (@initial_width > 0 ? @initial_width : columns[-1].sum + rw)
|
|
622
|
+
@height = (@initial_height > 0 ? @initial_height : used_height + rh)
|
|
623
|
+
@fit_successful = (@last_fitted_row_index == @cells.number_of_rows - 1)
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
private
|
|
627
|
+
|
|
628
|
+
# Calculates and returns the x-coordinates and widths of all columns based on the given total
|
|
629
|
+
# available width.
|
|
630
|
+
#
|
|
631
|
+
# If it is not possible to fit all columns into the given +width+, an empty array is returned.
|
|
632
|
+
def calculate_column_widths(width)
|
|
633
|
+
@column_widths.concat([-1] * (@cells.number_of_columns - @column_widths.size))
|
|
634
|
+
fixed_width, variable_width = @column_widths.partition(&:positive?).map {|c| c.sum(&:abs) }
|
|
635
|
+
rest_width = width - fixed_width
|
|
636
|
+
return [] if rest_width <= 0
|
|
637
|
+
|
|
638
|
+
variable_width_unit = rest_width / variable_width.to_f
|
|
639
|
+
position = 0
|
|
640
|
+
@column_widths.map do |column|
|
|
641
|
+
result = column > 0 ? [position, column] : [position, column.abs * variable_width_unit]
|
|
642
|
+
position += result[1]
|
|
643
|
+
result
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# Splits the content of the column box. This method is called from Box#split.
|
|
648
|
+
def split_content(_available_width, _available_height, _frame)
|
|
649
|
+
if @special_cells_fit_not_successful || @last_fitted_row_index < 0
|
|
650
|
+
[nil, self]
|
|
651
|
+
else
|
|
652
|
+
box = create_split_box
|
|
653
|
+
box.instance_variable_set(:@start_row_index, @last_fitted_row_index + 1)
|
|
654
|
+
box.instance_variable_set(:@last_fitted_row_index, -1)
|
|
655
|
+
box.instance_variable_set(:@special_cells_fit_not_successful, nil)
|
|
656
|
+
header_cells = @header ? Cells.new(@header.call(self), cell_style: @cell_style) : nil
|
|
657
|
+
box.instance_variable_set(:@header_cells, header_cells)
|
|
658
|
+
footer_cells = @footer ? Cells.new(@footer.call(self), cell_style: @cell_style) : nil
|
|
659
|
+
box.instance_variable_set(:@footer_cells, footer_cells)
|
|
660
|
+
[self, box]
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# Draws the child boxes onto the canvas at position [x, y].
|
|
665
|
+
def draw_content(canvas, x, y)
|
|
666
|
+
x += @cell_tl_border_width.left / 2.0
|
|
667
|
+
y += content_height - @cell_tl_border_width.top / 2.0
|
|
668
|
+
if @header_cells
|
|
669
|
+
@header_cells.draw_rows(0, -1, canvas, x, y)
|
|
670
|
+
y -= @header_cells[-1, 0].top + @header_cells[-1, 0].height
|
|
671
|
+
end
|
|
672
|
+
@cells.draw_rows(@start_row_index, @last_fitted_row_index, canvas, x, y)
|
|
673
|
+
if @footer_cells
|
|
674
|
+
y -= @cells[@last_fitted_row_index, 0].top + @cells[@last_fitted_row_index, 0].height
|
|
675
|
+
@footer_cells.draw_rows(0, -1, canvas, x, y)
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
end
|
|
682
|
+
end
|