hexapdf 0.23.0 → 0.24.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 +66 -0
- data/LICENSE +1 -1
- data/Rakefile +1 -1
- data/examples/016-frame_automatic_box_placement.rb +7 -2
- data/examples/017-frame_text_flow.rb +10 -18
- data/examples/020-column_box.rb +40 -0
- data/examples/021-list_box.rb +26 -0
- data/lib/hexapdf/cli/batch.rb +1 -1
- data/lib/hexapdf/cli/command.rb +1 -1
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/fonts.rb +1 -1
- data/lib/hexapdf/cli/form.rb +2 -2
- data/lib/hexapdf/cli/image2pdf.rb +1 -1
- data/lib/hexapdf/cli/images.rb +1 -1
- data/lib/hexapdf/cli/info.rb +2 -2
- data/lib/hexapdf/cli/inspect.rb +2 -2
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/cli/optimize.rb +1 -1
- data/lib/hexapdf/cli/split.rb +1 -1
- data/lib/hexapdf/cli/watermark.rb +1 -1
- data/lib/hexapdf/cli.rb +1 -1
- data/lib/hexapdf/composer.rb +45 -126
- data/lib/hexapdf/configuration.rb +17 -1
- data/lib/hexapdf/content/canvas.rb +1 -1
- data/lib/hexapdf/content/color_space.rb +1 -1
- data/lib/hexapdf/content/graphic_object/arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object/geom2d.rb +2 -1
- data/lib/hexapdf/content/graphic_object/solid_arc.rb +1 -1
- data/lib/hexapdf/content/graphic_object.rb +1 -1
- data/lib/hexapdf/content/graphics_state.rb +1 -1
- data/lib/hexapdf/content/operator.rb +1 -1
- data/lib/hexapdf/content/parser.rb +1 -1
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/content/transformation_matrix.rb +1 -1
- data/lib/hexapdf/content.rb +1 -1
- data/lib/hexapdf/data_dir.rb +1 -1
- data/lib/hexapdf/dictionary.rb +1 -1
- data/lib/hexapdf/dictionary_fields.rb +1 -1
- data/lib/hexapdf/document/files.rb +1 -1
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/document/images.rb +1 -1
- data/lib/hexapdf/document/layout.rb +397 -0
- data/lib/hexapdf/document/pages.rb +17 -1
- data/lib/hexapdf/document/signatures.rb +5 -4
- data/lib/hexapdf/document.rb +8 -1
- data/lib/hexapdf/encryption/aes.rb +1 -1
- data/lib/hexapdf/encryption/arc4.rb +1 -1
- data/lib/hexapdf/encryption/fast_aes.rb +1 -1
- data/lib/hexapdf/encryption/fast_arc4.rb +30 -21
- 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 +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/encryption.rb +1 -1
- data/lib/hexapdf/error.rb +1 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/filter/crypt.rb +1 -1
- data/lib/hexapdf/filter/encryption.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 +1 -1
- data/lib/hexapdf/font/cmap/parser.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +1 -1
- data/lib/hexapdf/font/cmap.rb +1 -1
- data/lib/hexapdf/font/encoding/base.rb +1 -1
- data/lib/hexapdf/font/encoding/difference_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/glyph_list.rb +2 -2
- data/lib/hexapdf/font/encoding/mac_expert_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding/mac_roman_encoding.rb +1 -1
- 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 +1 -1
- data/lib/hexapdf/font/encoding/zapf_dingbats_encoding.rb +1 -1
- data/lib/hexapdf/font/encoding.rb +1 -1
- data/lib/hexapdf/font/invalid_glyph.rb +1 -1
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/font.rb +1 -1
- data/lib/hexapdf/font/true_type/optimizer.rb +1 -1
- data/lib/hexapdf/font/true_type/subsetter.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -1
- data/lib/hexapdf/font/true_type/table/directory.rb +1 -1
- data/lib/hexapdf/font/true_type/table/glyf.rb +1 -1
- data/lib/hexapdf/font/true_type/table/head.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hhea.rb +1 -1
- data/lib/hexapdf/font/true_type/table/hmtx.rb +1 -1
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/loca.rb +1 -1
- data/lib/hexapdf/font/true_type/table/maxp.rb +1 -1
- data/lib/hexapdf/font/true_type/table/name.rb +1 -1
- data/lib/hexapdf/font/true_type/table/os2.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type.rb +1 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +1 -1
- data/lib/hexapdf/font/type1/afm_parser.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/pfb_parser.rb +1 -1
- data/lib/hexapdf/font/type1.rb +1 -1
- data/lib/hexapdf/font/type1_wrapper.rb +1 -1
- data/lib/hexapdf/font_loader/from_configuration.rb +1 -1
- data/lib/hexapdf/font_loader/from_file.rb +1 -1
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/font_loader.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/pdf.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +1 -1
- data/lib/hexapdf/image_loader.rb +1 -1
- data/lib/hexapdf/importer.rb +1 -1
- data/lib/hexapdf/layout/box.rb +121 -22
- data/lib/hexapdf/layout/box_fitter.rb +136 -0
- data/lib/hexapdf/layout/column_box.rb +247 -0
- data/lib/hexapdf/layout/frame.rb +155 -139
- data/lib/hexapdf/layout/image_box.rb +19 -4
- data/lib/hexapdf/layout/inline_box.rb +1 -1
- data/lib/hexapdf/layout/line.rb +1 -1
- data/lib/hexapdf/layout/list_box.rb +355 -0
- data/lib/hexapdf/layout/numeric_refinements.rb +1 -1
- data/lib/hexapdf/layout/style.rb +5 -1
- data/lib/hexapdf/layout/text_box.rb +20 -9
- data/lib/hexapdf/layout/text_fragment.rb +3 -2
- data/lib/hexapdf/layout/text_layouter.rb +17 -2
- data/lib/hexapdf/layout/text_shaper.rb +1 -1
- data/lib/hexapdf/layout/width_from_polygon.rb +12 -7
- data/lib/hexapdf/layout.rb +4 -1
- data/lib/hexapdf/name_tree_node.rb +1 -1
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/object.rb +1 -1
- data/lib/hexapdf/parser.rb +1 -8
- 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 +1 -1
- data/lib/hexapdf/serializer.rb +1 -1
- data/lib/hexapdf/stream.rb +1 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/task/optimize.rb +1 -1
- data/lib/hexapdf/task.rb +1 -1
- data/lib/hexapdf/tokenizer.rb +1 -1
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
- 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 +1 -1
- data/lib/hexapdf/type/acro_form/form.rb +1 -1
- data/lib/hexapdf/type/acro_form/signature_field.rb +1 -1
- 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 +1 -1
- 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 +1 -1
- data/lib/hexapdf/type/annotations.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +1 -1
- data/lib/hexapdf/type/embedded_file.rb +1 -1
- data/lib/hexapdf/type/file_specification.rb +1 -1
- data/lib/hexapdf/type/font.rb +1 -1
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_true_type.rb +1 -1
- data/lib/hexapdf/type/font_type0.rb +1 -1
- data/lib/hexapdf/type/font_type1.rb +1 -1
- 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/names.rb +1 -1
- data/lib/hexapdf/type/object_stream.rb +1 -1
- data/lib/hexapdf/type/page.rb +1 -1
- data/lib/hexapdf/type/page_tree_node.rb +19 -2
- data/lib/hexapdf/type/resources.rb +1 -1
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -1
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +1 -1
- data/lib/hexapdf/type/signature/handler.rb +1 -1
- data/lib/hexapdf/type/signature/verification_result.rb +1 -1
- data/lib/hexapdf/type/signature.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 +1 -1
- data/lib/hexapdf/type.rb +1 -1
- data/lib/hexapdf/utils/bit_field.rb +1 -1
- data/lib/hexapdf/utils/bit_stream.rb +1 -1
- data/lib/hexapdf/utils/graphics_helpers.rb +1 -1
- data/lib/hexapdf/utils/lru_cache.rb +1 -1
- data/lib/hexapdf/utils/math_helpers.rb +1 -1
- data/lib/hexapdf/utils/object_hash.rb +1 -1
- data/lib/hexapdf/utils/pdf_doc_encoding.rb +1 -1
- data/lib/hexapdf/utils/sorted_tree_node.rb +1 -1
- data/lib/hexapdf/version.rb +2 -2
- data/lib/hexapdf/writer.rb +9 -7
- data/lib/hexapdf/xref_section.rb +1 -1
- data/lib/hexapdf.rb +1 -1
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +1 -1
- data/test/hexapdf/document/test_destinations.rb +1 -1
- data/test/hexapdf/document/test_images.rb +1 -1
- data/test/hexapdf/document/test_layout.rb +264 -0
- data/test/hexapdf/document/test_pages.rb +9 -0
- data/test/hexapdf/document/test_signatures.rb +10 -3
- data/test/hexapdf/encryption/test_security_handler.rb +1 -1
- data/test/hexapdf/font/encoding/test_glyph_list.rb +4 -0
- data/test/hexapdf/layout/test_box.rb +53 -3
- data/test/hexapdf/layout/test_box_fitter.rb +62 -0
- data/test/hexapdf/layout/test_column_box.rb +159 -0
- data/test/hexapdf/layout/test_frame.rb +99 -38
- data/test/hexapdf/layout/test_image_box.rb +1 -1
- data/test/hexapdf/layout/test_list_box.rb +249 -0
- data/test/hexapdf/layout/test_text_box.rb +17 -2
- data/test/hexapdf/layout/test_text_fragment.rb +1 -1
- data/test/hexapdf/layout/test_text_layouter.rb +42 -17
- data/test/hexapdf/layout/test_width_from_polygon.rb +13 -0
- data/test/hexapdf/test_composer.rb +11 -0
- data/test/hexapdf/test_dictionary_fields.rb +9 -9
- data/test/hexapdf/test_document.rb +4 -4
- data/test/hexapdf/test_filter.rb +1 -1
- data/test/hexapdf/test_parser.rb +0 -2
- data/test/hexapdf/test_revisions.rb +2 -2
- data/test/hexapdf/test_serializer.rb +1 -5
- data/test/hexapdf/test_writer.rb +58 -3
- data/test/hexapdf/type/test_page_tree_node.rb +21 -1
- data/test/hexapdf/type/test_trailer.rb +3 -3
- data/test/test_helper.rb +5 -1
- metadata +28 -3
data/lib/hexapdf/layout/box.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# This file is part of HexaPDF.
|
|
5
5
|
#
|
|
6
6
|
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|
|
7
|
-
# Copyright (C) 2014-
|
|
7
|
+
# Copyright (C) 2014-2022 Thomas Leitner
|
|
8
8
|
#
|
|
9
9
|
# HexaPDF is free software: you can redistribute it and/or modify it
|
|
10
10
|
# under the terms of the GNU Affero General Public License version 3 as
|
|
@@ -40,6 +40,8 @@ module HexaPDF
|
|
|
40
40
|
|
|
41
41
|
# The base class for all layout boxes.
|
|
42
42
|
#
|
|
43
|
+
# == Box Model
|
|
44
|
+
#
|
|
43
45
|
# HexaPDF uses the following box model:
|
|
44
46
|
#
|
|
45
47
|
# * Each box can specify a width and height. Padding and border are inside, the margin outside
|
|
@@ -49,12 +51,35 @@ module HexaPDF
|
|
|
49
51
|
# the content box without padding and the border.
|
|
50
52
|
#
|
|
51
53
|
# * If width or height is set to zero, they are determined automatically during layouting.
|
|
54
|
+
#
|
|
55
|
+
#
|
|
56
|
+
# == Subclasses
|
|
57
|
+
#
|
|
58
|
+
# Each subclass should only take keyword arguments on initialization so that the boxes can be
|
|
59
|
+
# instantiated from the common convenience method HexaPDF::Document::Layout#box. To use this
|
|
60
|
+
# facility subclasses need to be registered with the configuration option 'layout.boxes.map'.
|
|
61
|
+
#
|
|
62
|
+
# The methods #fit, #split or #split_content, and #draw or #draw_content need to be customized
|
|
63
|
+
# according to the subclass's use case.
|
|
64
|
+
#
|
|
65
|
+
# #fit:: This method should return +true+ if fitting was successful. Additionally, the
|
|
66
|
+
# @fit_successful instance variable needs to be set to the fit result as it is used in
|
|
67
|
+
# #split.
|
|
68
|
+
#
|
|
69
|
+
# #split:: This method splits the content so that the available space is used as good as
|
|
70
|
+
# possible. The default implementation should be fine for most use-cases, so only
|
|
71
|
+
# #split_content needs to be implemented. The method #create_split_box should be used
|
|
72
|
+
# for getting a basic cloned box.
|
|
73
|
+
#
|
|
74
|
+
# #draw:: This method draws the content and the default implementation already handles things
|
|
75
|
+
# like drawing the border and background. Therefore it's best to implement #draw_content
|
|
76
|
+
# which should just draw the content.
|
|
52
77
|
class Box
|
|
53
78
|
|
|
54
79
|
# Creates a new Box object, using the provided block as drawing block (see ::new).
|
|
55
80
|
#
|
|
56
81
|
# If +content_box+ is +true+, the width and height are taken to mean the content width and
|
|
57
|
-
# height and the style's padding and border are
|
|
82
|
+
# height and the style's padding and border are added to them appropriately.
|
|
58
83
|
#
|
|
59
84
|
# The +style+ argument defines the Style object (see Style::create for details) for the box.
|
|
60
85
|
# Any additional keyword arguments have to be style properties and are applied to the style
|
|
@@ -102,6 +127,18 @@ module HexaPDF
|
|
|
102
127
|
@height = @initial_height = height
|
|
103
128
|
@style = Style.create(style)
|
|
104
129
|
@draw_block = block
|
|
130
|
+
@fit_successful = false
|
|
131
|
+
@split_box = false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Returns +true+ if this is a split box, i.e. the rest of another box after it was split.
|
|
135
|
+
def split_box?
|
|
136
|
+
@split_box
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Returns +false+ since a basic box doesn't support the 'position' style property value :flow.
|
|
140
|
+
def supports_position_flow?
|
|
141
|
+
false
|
|
105
142
|
end
|
|
106
143
|
|
|
107
144
|
# The width of the content box, i.e. without padding and/or borders.
|
|
@@ -123,16 +160,16 @@ module HexaPDF
|
|
|
123
160
|
def fit(available_width, available_height, _frame)
|
|
124
161
|
@width = (@initial_width > 0 ? @initial_width : available_width)
|
|
125
162
|
@height = (@initial_height > 0 ? @initial_height : available_height)
|
|
126
|
-
@width <= available_width && @height <= available_height
|
|
163
|
+
@fit_successful = (@width <= available_width && @height <= available_height)
|
|
127
164
|
end
|
|
128
165
|
|
|
129
166
|
# Tries to split the box into two, the first of which needs to fit into the available space,
|
|
130
167
|
# and returns the parts as array.
|
|
131
168
|
#
|
|
132
|
-
#
|
|
133
|
-
# a part of the box may still fit. Note that #fit
|
|
134
|
-
#
|
|
135
|
-
# the available space, +nil+ should be returned as the first array element.
|
|
169
|
+
# If the first item in the result array is not +nil+, it needs to be this box and it means
|
|
170
|
+
# that even when #fit fails, a part of the box may still fit. Note that #fit should not be
|
|
171
|
+
# called before #draw on the first box since it is already fitted. If not even a part of this
|
|
172
|
+
# box fits into the available space, +nil+ should be returned as the first array element.
|
|
136
173
|
#
|
|
137
174
|
# Possible return values:
|
|
138
175
|
#
|
|
@@ -140,15 +177,24 @@ module HexaPDF
|
|
|
140
177
|
# [nil, self]:: The box can't be split or no part of the box fits into the available space.
|
|
141
178
|
# [self, new_box]:: A part of the box fits and a new box is returned for the rest.
|
|
142
179
|
#
|
|
143
|
-
# This default implementation provides
|
|
144
|
-
|
|
145
|
-
|
|
180
|
+
# This default implementation provides the basic functionality based on the #fit result that
|
|
181
|
+
# should be sufficient for most subclasses; only #split_content needs to be implemented if
|
|
182
|
+
# necessary.
|
|
183
|
+
def split(available_width, available_height, frame)
|
|
184
|
+
if @fit_successful
|
|
185
|
+
[self, nil]
|
|
186
|
+
elsif (style.position != :flow && (@width > available_width || @height > available_height)) ||
|
|
187
|
+
content_height == 0 || content_width == 0
|
|
188
|
+
[nil, self]
|
|
189
|
+
else
|
|
190
|
+
split_content(available_width, available_height, frame)
|
|
191
|
+
end
|
|
146
192
|
end
|
|
147
193
|
|
|
148
194
|
# Draws the content of the box onto the canvas at the position (x, y).
|
|
149
195
|
#
|
|
150
196
|
# The coordinate system is translated so that the origin is at the bottom left corner of the
|
|
151
|
-
# **content box** during the drawing operations.
|
|
197
|
+
# **content box** during the drawing operations when +@draw_block+ is used.
|
|
152
198
|
#
|
|
153
199
|
# The block specified when creating the box is invoked with the canvas and the box as
|
|
154
200
|
# arguments. Subclasses can specify an on-demand drawing method by setting the +@draw_block+
|
|
@@ -165,11 +211,7 @@ module HexaPDF
|
|
|
165
211
|
style.underlays.draw(canvas, x, y, self) if style.underlays?
|
|
166
212
|
style.border.draw(canvas, x, y, width, height) if style.border?
|
|
167
213
|
|
|
168
|
-
|
|
169
|
-
cy = y
|
|
170
|
-
(cx += style.padding.left; cy += style.padding.bottom) if style.padding?
|
|
171
|
-
(cx += style.border.width.left; cy += style.border.width.bottom) if style.border?
|
|
172
|
-
draw_content(canvas, cx, cy)
|
|
214
|
+
draw_content(canvas, x + reserved_width_left, y + reserved_height_bottom)
|
|
173
215
|
|
|
174
216
|
style.overlays.draw(canvas, x, y, self) if style.overlays?
|
|
175
217
|
end
|
|
@@ -187,17 +229,47 @@ module HexaPDF
|
|
|
187
229
|
|
|
188
230
|
# Returns the width that is reserved by the padding and border style properties.
|
|
189
231
|
def reserved_width
|
|
190
|
-
|
|
191
|
-
result += style.padding.left + style.padding.right if style.padding?
|
|
192
|
-
result += style.border.width.left + style.border.width.right if style.border?
|
|
193
|
-
result
|
|
232
|
+
reserved_width_left + reserved_width_right
|
|
194
233
|
end
|
|
195
234
|
|
|
196
235
|
# Returns the height that is reserved by the padding and border style properties.
|
|
197
236
|
def reserved_height
|
|
237
|
+
reserved_height_top + reserved_height_bottom
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Returns the width that is reserved by the padding and the border style properties on the
|
|
241
|
+
# left side of the box.
|
|
242
|
+
def reserved_width_left
|
|
198
243
|
result = 0
|
|
199
|
-
result += style.padding.
|
|
200
|
-
result += style.border.width.
|
|
244
|
+
result += style.padding.left if style.padding?
|
|
245
|
+
result += style.border.width.left if style.border?
|
|
246
|
+
result
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Returns the width that is reserved by the padding and the border style properties on the
|
|
250
|
+
# right side of the box.
|
|
251
|
+
def reserved_width_right
|
|
252
|
+
result = 0
|
|
253
|
+
result += style.padding.right if style.padding?
|
|
254
|
+
result += style.border.width.right if style.border?
|
|
255
|
+
result
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Returns the height that is reserved by the padding and the border style properties on the
|
|
259
|
+
# top side of the box.
|
|
260
|
+
def reserved_height_top
|
|
261
|
+
result = 0
|
|
262
|
+
result += style.padding.top if style.padding?
|
|
263
|
+
result += style.border.width.top if style.border?
|
|
264
|
+
result
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Returns the height that is reserved by the padding and the border style properties on the
|
|
268
|
+
# bottom side of the box.
|
|
269
|
+
def reserved_height_bottom
|
|
270
|
+
result = 0
|
|
271
|
+
result += style.padding.bottom if style.padding?
|
|
272
|
+
result += style.border.width.bottom if style.border?
|
|
201
273
|
result
|
|
202
274
|
end
|
|
203
275
|
|
|
@@ -209,6 +281,33 @@ module HexaPDF
|
|
|
209
281
|
end
|
|
210
282
|
end
|
|
211
283
|
|
|
284
|
+
# Splits the content of the box.
|
|
285
|
+
#
|
|
286
|
+
# This is just a stub implementation, returning [nil, self] since we can't know how to split
|
|
287
|
+
# the content when it didn't fit.
|
|
288
|
+
#
|
|
289
|
+
# Subclasses that support splitting content need to provide an appropriate implementation and
|
|
290
|
+
# use #create_split_box to create a cloned box to supply as the second argument.
|
|
291
|
+
def split_content(_available_width, _available_height, _frame)
|
|
292
|
+
[nil, self]
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Creates a new box based on this one and resets the data back to their original values.
|
|
296
|
+
#
|
|
297
|
+
# The variable +@split_box+ is set to +split_box_value+ (defaults to +true+) to make the new
|
|
298
|
+
# box aware that it is a split box. If needed, subclasses can set the variable to other truthy
|
|
299
|
+
# values to convey more meaning.
|
|
300
|
+
#
|
|
301
|
+
# This method should be used by subclasses to create their split box.
|
|
302
|
+
def create_split_box(split_box_value: true)
|
|
303
|
+
box = clone
|
|
304
|
+
box.instance_variable_set(:@width, @initial_width)
|
|
305
|
+
box.instance_variable_set(:@height, @initial_height)
|
|
306
|
+
box.instance_variable_set(:@fit_successful, nil)
|
|
307
|
+
box.instance_variable_set(:@split_box, split_box_value)
|
|
308
|
+
box
|
|
309
|
+
end
|
|
310
|
+
|
|
212
311
|
end
|
|
213
312
|
|
|
214
313
|
end
|
|
@@ -0,0 +1,136 @@
|
|
|
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-2022 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
|
+
module HexaPDF
|
|
38
|
+
module Layout
|
|
39
|
+
|
|
40
|
+
# A BoxFitter instance contains an array of Frame objects and allows placing boxes one after the
|
|
41
|
+
# other in them. Such functionality is useful, for example, for boxes that provide multiple
|
|
42
|
+
# frames for content.
|
|
43
|
+
#
|
|
44
|
+
# == Usage
|
|
45
|
+
#
|
|
46
|
+
# * First one needs to add the frame objects via #<< or provide them on initialization.
|
|
47
|
+
#
|
|
48
|
+
# * Then use the #fit method to fit boxes one after the other. No drawing is done.
|
|
49
|
+
#
|
|
50
|
+
# * Once all boxes have been fitted, the #fit_results, #remaining_boxes and #fit_successful?
|
|
51
|
+
# methods can be used to get the result:
|
|
52
|
+
#
|
|
53
|
+
# - If there are no remaining boxes, all boxes were successfully fitted into the frames.
|
|
54
|
+
# - If there are remaining boxes but no fit results, the first box could not be fitted.
|
|
55
|
+
# - If there are remaining boxes and fit results, some boxes were able to fit.
|
|
56
|
+
class BoxFitter
|
|
57
|
+
|
|
58
|
+
# The array of frames inside of which the boxes should be laid out.
|
|
59
|
+
#
|
|
60
|
+
# Use #<< to add additional frames.
|
|
61
|
+
attr_reader :frames
|
|
62
|
+
|
|
63
|
+
# The Frame::FitResult objects for the successfully fitted objects in the order the boxes were
|
|
64
|
+
# fitted.
|
|
65
|
+
attr_reader :fit_results
|
|
66
|
+
|
|
67
|
+
# The boxes that could not be fitted into the frames.
|
|
68
|
+
attr_reader :remaining_boxes
|
|
69
|
+
|
|
70
|
+
# Creates a new BoxFitter object for the given +frames+.
|
|
71
|
+
def initialize(frames = [])
|
|
72
|
+
@frames = []
|
|
73
|
+
@content_heights = []
|
|
74
|
+
@initial_frame_y = []
|
|
75
|
+
@frame_index = 0
|
|
76
|
+
@fit_results = []
|
|
77
|
+
@remaining_boxes = []
|
|
78
|
+
|
|
79
|
+
frames.each {|frame| self << frame }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Add the given frame to the list of frames.
|
|
83
|
+
def <<(frame)
|
|
84
|
+
@frames << frame
|
|
85
|
+
@initial_frame_y << frame.y
|
|
86
|
+
@content_heights << 0
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Fits the given box at the current location.
|
|
90
|
+
def fit(box)
|
|
91
|
+
unless @remaining_boxes.empty?
|
|
92
|
+
@remaining_boxes << box
|
|
93
|
+
return
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
while (current_frame = @frames[@frame_index])
|
|
97
|
+
result = current_frame.fit(box)
|
|
98
|
+
if result.success?
|
|
99
|
+
current_frame.remove_area(result.mask)
|
|
100
|
+
@content_heights[@frame_index] = [@content_heights[@frame_index],
|
|
101
|
+
@initial_frame_y[@frame_index] - result.mask[0].y].max
|
|
102
|
+
@fit_results << result
|
|
103
|
+
box = nil
|
|
104
|
+
break
|
|
105
|
+
elsif current_frame.full?
|
|
106
|
+
@frame_index += 1
|
|
107
|
+
else
|
|
108
|
+
draw_box, box = current_frame.split(result)
|
|
109
|
+
if draw_box
|
|
110
|
+
current_frame.remove_area(result.mask)
|
|
111
|
+
@content_heights[@frame_index] = [@content_heights[@frame_index],
|
|
112
|
+
@initial_frame_y[@frame_index] - result.mask[0].y].max
|
|
113
|
+
@fit_results << result
|
|
114
|
+
elsif !current_frame.find_next_region
|
|
115
|
+
@frame_index += 1
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
@remaining_boxes << box if box
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns an array with the heights of the content of each frame.
|
|
124
|
+
def content_heights
|
|
125
|
+
@content_heights
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Returns +true+ if all boxes were successfully fitted.
|
|
129
|
+
def fit_successful?
|
|
130
|
+
@remaining_boxes.empty?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,247 @@
|
|
|
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-2022 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
|
+
require 'hexapdf/layout/box'
|
|
37
|
+
require 'hexapdf/layout/box_fitter'
|
|
38
|
+
|
|
39
|
+
module HexaPDF
|
|
40
|
+
module Layout
|
|
41
|
+
|
|
42
|
+
# A ColumnBox arranges boxes in one or more columns.
|
|
43
|
+
#
|
|
44
|
+
# The number and width of the columns as well as the size of the gap between the columns can be
|
|
45
|
+
# modified. Additionally, the contents can either fill the columns one after the other or the
|
|
46
|
+
# columns can be made equally high.
|
|
47
|
+
#
|
|
48
|
+
# If the column box has padding and/or borders specified, they are handled like with any other
|
|
49
|
+
# box. This means they are around all columns and their contents and are not used separately for
|
|
50
|
+
# each column.
|
|
51
|
+
#
|
|
52
|
+
# The following style properties are used (additionally to those used by the parent class):
|
|
53
|
+
#
|
|
54
|
+
# Style#position::
|
|
55
|
+
# If this is set to :flow, the frames created for the columns will take the shape of the
|
|
56
|
+
# frame into account. This also means that the +available_width+ and +available_height+
|
|
57
|
+
# arguments are ignored.
|
|
58
|
+
class ColumnBox < Box
|
|
59
|
+
|
|
60
|
+
# The child boxes of this ColumnBox. They need to be finalized before #fit is called.
|
|
61
|
+
attr_reader :children
|
|
62
|
+
|
|
63
|
+
# The columns definition.
|
|
64
|
+
#
|
|
65
|
+
# This is an array containing the widths of the columns. The size of the array is the number
|
|
66
|
+
# of columns.
|
|
67
|
+
#
|
|
68
|
+
# If a negative integer is used for the width, the column is auto-sized. Such columns split
|
|
69
|
+
# the remaining width (after substracting the widths of the fixed columns) proportionally
|
|
70
|
+
# among them. For example, if the definition is [-1, -2, -2], the first column is a fifth of
|
|
71
|
+
# the width and the other columns are each two fifth of the width.
|
|
72
|
+
#
|
|
73
|
+
# Examples:
|
|
74
|
+
#
|
|
75
|
+
# #>pdf-composer
|
|
76
|
+
# composer.box(:column, columns: 2, gaps: 10,
|
|
77
|
+
# children: [composer.document.layout.lorem_ipsum_box])
|
|
78
|
+
#
|
|
79
|
+
# ---
|
|
80
|
+
#
|
|
81
|
+
# #>pdf-composer
|
|
82
|
+
# composer.box(:column, columns: [50, -2, -1], gaps: [10, 5],
|
|
83
|
+
# children: [composer.document.layout.lorem_ipsum_box])
|
|
84
|
+
attr_reader :columns
|
|
85
|
+
|
|
86
|
+
# The size of the gaps between the columns.
|
|
87
|
+
#
|
|
88
|
+
# This is an array containing the width of the gaps. If there are more gaps than numbers in
|
|
89
|
+
# the array, the array is cycled.
|
|
90
|
+
#
|
|
91
|
+
# Examples: see #columns
|
|
92
|
+
attr_reader :gaps
|
|
93
|
+
|
|
94
|
+
# Determines whether the columns should all be equally high or not.
|
|
95
|
+
#
|
|
96
|
+
# Examples:
|
|
97
|
+
#
|
|
98
|
+
# #>pdf-composer
|
|
99
|
+
# composer.box(:column, children: [composer.document.layout.lorem_ipsum_box])
|
|
100
|
+
#
|
|
101
|
+
# ---
|
|
102
|
+
#
|
|
103
|
+
# #>pdf-composer
|
|
104
|
+
# composer.box(:column, equal_height: false,
|
|
105
|
+
# children: [composer.document.layout.lorem_ipsum_box])
|
|
106
|
+
attr_reader :equal_height
|
|
107
|
+
|
|
108
|
+
# Creates a new ColumnBox object for the given child boxes in +children+.
|
|
109
|
+
#
|
|
110
|
+
# +columns+::
|
|
111
|
+
#
|
|
112
|
+
# Can either simply integer specify the number of columns or be a full column definition
|
|
113
|
+
# (see #columns for details).
|
|
114
|
+
#
|
|
115
|
+
# +gaps+::
|
|
116
|
+
# Can either be a simply integer specifying the width between two columns or a full gap
|
|
117
|
+
# definition (see #gap for details).
|
|
118
|
+
#
|
|
119
|
+
# +equal_height+::
|
|
120
|
+
# If +true+, the #fit method tries to balance the columns in terms of their height.
|
|
121
|
+
# Otherwise the columns are filled from the left.
|
|
122
|
+
def initialize(children: [], columns: 2, gaps: 36, equal_height: true, **kwargs)
|
|
123
|
+
super(**kwargs)
|
|
124
|
+
@children = children
|
|
125
|
+
@columns = (columns.kind_of?(Array) ? columns : [-1] * columns)
|
|
126
|
+
@gaps = (gaps.kind_of?(Array) ? gaps : [gaps])
|
|
127
|
+
@equal_height = equal_height
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns +true+ as the 'position' style property value :flow is supported.
|
|
131
|
+
def supports_position_flow?
|
|
132
|
+
true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Fits the column box into the available space.
|
|
136
|
+
#
|
|
137
|
+
# If the style property 'position' is set to :flow, the columns might not be rectangles but
|
|
138
|
+
# arbitrary (sets of) polygons since the +frame+s shape is taken into account.
|
|
139
|
+
def fit(available_width, available_height, frame)
|
|
140
|
+
initial_fit_successful = (@equal_height ? nil : false)
|
|
141
|
+
tries = 0
|
|
142
|
+
@width = if style.position == :flow
|
|
143
|
+
(@initial_width > 0 ? @initial_width : frame.width) - reserved_width
|
|
144
|
+
else
|
|
145
|
+
(@initial_width > 0 ? @initial_width : available_width) - reserved_width
|
|
146
|
+
end
|
|
147
|
+
height = if style.position == :flow
|
|
148
|
+
(@initial_height > 0 ? @initial_height : frame.height) - reserved_height
|
|
149
|
+
else
|
|
150
|
+
(@initial_height > 0 ? @initial_height : available_height) - reserved_height
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
columns = calculate_columns(@width)
|
|
154
|
+
return false if columns.empty?
|
|
155
|
+
|
|
156
|
+
left = (style.position == :flow ? frame.left : frame.x) + reserved_width_left
|
|
157
|
+
top = (style.position == :flow ? frame.bottom + frame.height : frame.y) - reserved_height_top
|
|
158
|
+
successful_height = height
|
|
159
|
+
unsuccessful_height = 0
|
|
160
|
+
|
|
161
|
+
while true
|
|
162
|
+
@box_fitter = BoxFitter.new
|
|
163
|
+
|
|
164
|
+
columns.each do |col_x, column_width|
|
|
165
|
+
column_left = left + col_x
|
|
166
|
+
column_bottom = top - height
|
|
167
|
+
if style.position == :flow
|
|
168
|
+
rect = Geom2D::Polygon([column_left, column_bottom],
|
|
169
|
+
[column_left + column_width, column_bottom],
|
|
170
|
+
[column_left + column_width, column_bottom + height],
|
|
171
|
+
[column_left, column_bottom + height])
|
|
172
|
+
shape = Geom2D::Algorithms::PolygonOperation.run(frame.shape, rect, :intersection)
|
|
173
|
+
end
|
|
174
|
+
column_frame = Frame.new(column_left, column_bottom, column_width, height, shape: shape)
|
|
175
|
+
@box_fitter << column_frame
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
children.each {|box| @box_fitter.fit(box) }
|
|
179
|
+
|
|
180
|
+
fit_successful = @box_fitter.fit_successful?
|
|
181
|
+
initial_fit_successful = fit_successful if initial_fit_successful.nil?
|
|
182
|
+
|
|
183
|
+
if fit_successful
|
|
184
|
+
successful_height = height if successful_height > height
|
|
185
|
+
elsif unsuccessful_height < height
|
|
186
|
+
unsuccessful_height = height
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
break if !initial_fit_successful || tries > 40 ||
|
|
190
|
+
(fit_successful && successful_height - unsuccessful_height < 10)
|
|
191
|
+
|
|
192
|
+
height = if successful_height - unsuccessful_height <= 5
|
|
193
|
+
successful_height
|
|
194
|
+
else
|
|
195
|
+
(successful_height + unsuccessful_height) / 2.0
|
|
196
|
+
end
|
|
197
|
+
tries += 1
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
@width = columns[-1].sum + reserved_width
|
|
201
|
+
@height = @box_fitter.content_heights.max + reserved_height
|
|
202
|
+
|
|
203
|
+
@box_fitter.fit_successful?
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
private
|
|
207
|
+
|
|
208
|
+
# Calculates the x-coordinates and widths of all columns based on the given total available
|
|
209
|
+
# width.
|
|
210
|
+
#
|
|
211
|
+
# If it is not possible to fit all columns into the given +width+, an empty array is returned.
|
|
212
|
+
def calculate_columns(width)
|
|
213
|
+
number_of_columns = @columns.size
|
|
214
|
+
gaps = @gaps.cycle.take(number_of_columns - 1)
|
|
215
|
+
fixed_width, variable_width = @columns.partition(&:positive?).map {|c| c.sum(&:abs) }
|
|
216
|
+
rest_width = width - fixed_width - gaps.sum
|
|
217
|
+
return [] if rest_width <= 0
|
|
218
|
+
|
|
219
|
+
variable_width_unit = rest_width / variable_width.to_f
|
|
220
|
+
position = 0
|
|
221
|
+
@columns.map.with_index do |column, index|
|
|
222
|
+
result = if column > 0
|
|
223
|
+
[position, column]
|
|
224
|
+
else
|
|
225
|
+
[position, column.abs * variable_width_unit]
|
|
226
|
+
end
|
|
227
|
+
position += result[1] + (gaps[index] || 0)
|
|
228
|
+
result
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Splits the content of the column box. This method is called from Box#split.
|
|
233
|
+
def split_content(_available_width, _available_height, _frame)
|
|
234
|
+
box = create_split_box
|
|
235
|
+
box.instance_variable_set(:@children, @box_fitter.remaining_boxes)
|
|
236
|
+
[self, box]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Draws the child boxes onto the canvas at position [x, y].
|
|
240
|
+
def draw_content(canvas, _x, _y)
|
|
241
|
+
@box_fitter.fit_results.each {|result| result.draw(canvas) }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
end
|
|
247
|
+
end
|