hexapdf 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/examples/027-composer_optional_content.rb +6 -4
  4. data/examples/030-pdfa.rb +13 -11
  5. data/lib/hexapdf/composer.rb +23 -0
  6. data/lib/hexapdf/content/canvas.rb +3 -3
  7. data/lib/hexapdf/content/canvas_composer.rb +1 -0
  8. data/lib/hexapdf/document/files.rb +7 -2
  9. data/lib/hexapdf/document/layout.rb +15 -3
  10. data/lib/hexapdf/document/metadata.rb +12 -1
  11. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  12. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  13. data/lib/hexapdf/layout/box.rb +180 -66
  14. data/lib/hexapdf/layout/box_fitter.rb +1 -0
  15. data/lib/hexapdf/layout/column_box.rb +18 -28
  16. data/lib/hexapdf/layout/container_box.rb +6 -6
  17. data/lib/hexapdf/layout/frame.rb +13 -94
  18. data/lib/hexapdf/layout/image_box.rb +4 -4
  19. data/lib/hexapdf/layout/list_box.rb +13 -31
  20. data/lib/hexapdf/layout/style.rb +8 -4
  21. data/lib/hexapdf/layout/table_box.rb +55 -58
  22. data/lib/hexapdf/layout/text_box.rb +84 -71
  23. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  24. data/lib/hexapdf/layout/text_layouter.rb +7 -8
  25. data/lib/hexapdf/parser.rb +5 -2
  26. data/lib/hexapdf/rectangle.rb +4 -4
  27. data/lib/hexapdf/type/file_specification.rb +9 -5
  28. data/lib/hexapdf/type/form.rb +2 -2
  29. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  30. data/lib/hexapdf/version.rb +1 -1
  31. data/test/hexapdf/content/test_canvas_composer.rb +13 -8
  32. data/test/hexapdf/document/test_files.rb +5 -0
  33. data/test/hexapdf/document/test_layout.rb +16 -0
  34. data/test/hexapdf/document/test_metadata.rb +21 -0
  35. data/test/hexapdf/layout/test_box.rb +93 -37
  36. data/test/hexapdf/layout/test_box_fitter.rb +7 -0
  37. data/test/hexapdf/layout/test_column_box.rb +7 -13
  38. data/test/hexapdf/layout/test_container_box.rb +1 -1
  39. data/test/hexapdf/layout/test_frame.rb +7 -46
  40. data/test/hexapdf/layout/test_image_box.rb +14 -6
  41. data/test/hexapdf/layout/test_list_box.rb +26 -27
  42. data/test/hexapdf/layout/test_table_box.rb +47 -54
  43. data/test/hexapdf/layout/test_text_box.rb +83 -83
  44. data/test/hexapdf/test_composer.rb +20 -5
  45. data/test/hexapdf/test_parser.rb +8 -0
  46. data/test/hexapdf/test_serializer.rb +1 -0
  47. data/test/hexapdf/type/test_file_specification.rb +2 -1
  48. metadata +2 -2
@@ -61,9 +61,9 @@ module HexaPDF
61
61
  # instantiated from the common convenience method HexaPDF::Document::Layout#box. To use this
62
62
  # facility subclasses need to be registered with the configuration option 'layout.boxes.map'.
63
63
  #
64
- # The methods #supports_position_flow?, #empty?, #fit or #fit_content, #split or #split_content,
65
- # and #draw or #draw_content need to be customized according to the subclass's use case (also
66
- # see the documentation of the methods besides the informatione below):
64
+ # The methods #supports_position_flow?, #empty?, #fit_content, #split_content, and #draw_content
65
+ # need to be customized according to the subclass's use case (also see the documentation of the
66
+ # methods besides the information below):
67
67
  #
68
68
  # #supports_position_flow?::
69
69
  # If the subclass supports the value :flow of the 'position' style property, this method
@@ -72,25 +72,22 @@ module HexaPDF
72
72
  # #empty?::
73
73
  # This method should return +true+ if the subclass won't draw anything when #draw is called.
74
74
  #
75
- # #fit::
76
- # This method should return +true+ if fitting was successful. Additionally, the
77
- # @fit_successful instance variable needs to be set to the fit result as it is used in
78
- # #split.
75
+ # #fit_content::
76
+ # This method determines whether the box fits into the available region and should set the
77
+ # status of #fit_result appropriately.
79
78
  #
80
- # The default implementation provides code common to most use-cases and delegates the
81
- # specifics to the #fit_content method which needs to return +true+ if fitting was
82
- # successful.
79
+ # It is called from the #fit method which should not be overridden in most cases. The
80
+ # default implementations of both methods provide code common to all use-cases and delegates
81
+ # the specifics to the subclass-specific #fit_content method.
83
82
  #
84
- # #split::
85
- # This method splits the content so that the current region is used as good as possible. The
86
- # default implementation should be fine for most use-cases, so only #split_content needs to
87
- # be implemented. The method #create_split_box should be used for getting a basic cloned
88
- # box.
83
+ # #split_content::
84
+ # This method is called from #split which handles the common cases based on the status of
85
+ # the #fit_result. It needs to handle the case when only some part of the box fits. The
86
+ # method #create_split_box should be used for getting a basic cloned box.
89
87
  #
90
- # #draw::
91
- # This method draws the content and the default implementation already handles things like
92
- # drawing the border and background. So it should not be overridden. The box specific
93
- # drawing commands should be implemented in the #draw_content method.
88
+ # #draw_content::
89
+ # This method draws the box specific content and is called from #draw which already handles
90
+ # things like drawing the border and background. So #draw should usually not be overridden.
94
91
  #
95
92
  # This base class provides various private helper methods for use in the above methods:
96
93
  #
@@ -118,6 +115,104 @@ module HexaPDF
118
115
 
119
116
  include HexaPDF::Utils
120
117
 
118
+ # Stores the result of fitting a box in a frame.
119
+ class FitResult
120
+
121
+ # The box that was fitted into the frame.
122
+ attr_accessor :box
123
+
124
+ # The frame into which the box was fitted.
125
+ attr_accessor :frame
126
+
127
+ # The horizontal position where the box will be drawn.
128
+ attr_accessor :x
129
+
130
+ # The vertical position where the box will be drawn.
131
+ attr_accessor :y
132
+
133
+ # The rectangle (a Geom2D::Rectangle object) that will be removed from the frame when
134
+ # drawing the box.
135
+ attr_accessor :mask
136
+
137
+ # The status result of fitting the box in the frame.
138
+ #
139
+ # Allowed values are:
140
+ #
141
+ # +:failure+:: (default) Indicates fitting the box has failed.
142
+ # +:success+:: Indicates that the box was completely fitted.
143
+ # +:overflow+:: Indicates that only a part of the box was fitted.
144
+ attr_reader :status
145
+
146
+ # Initializes the result object for the given box and, optionally, frame.
147
+ def initialize(box, frame: nil)
148
+ @box = box
149
+ reset(frame)
150
+ end
151
+
152
+ # Resets the result object.
153
+ def reset(frame)
154
+ @frame = frame
155
+ @x = @y = @mask = nil
156
+ @status = :failure
157
+ self
158
+ end
159
+
160
+ # Sets the result status to success.
161
+ def success!
162
+ @status = :success
163
+ end
164
+
165
+ # Returns +true+ if fitting was successful.
166
+ def success?
167
+ @status == :success
168
+ end
169
+
170
+ # Sets the result status to overflow.
171
+ def overflow!
172
+ @status = :overflow
173
+ end
174
+
175
+ # Returns +true+ if only parts of the box were fitted.
176
+ def overflow?
177
+ @status == :overflow
178
+ end
179
+
180
+ # Returns +true+ if fitting was a failure.
181
+ def failure?
182
+ @status == :failure
183
+ end
184
+
185
+ # Draws the #box onto the canvas at (#x + *dx*, #y + *dy*).
186
+ #
187
+ # The relative offset (dx, dy) is useful when rendering results that were accumulated and
188
+ # then need to be moved because the container holding them changes its position.
189
+ #
190
+ # The configuration option "debug" can be used to add visual debug output with respect to
191
+ # box placement.
192
+ def draw(canvas, dx: 0, dy: 0)
193
+ return if box.height == 0 || box.width == 0
194
+ doc = canvas.context.document
195
+ if doc.config['debug']
196
+ name = (frame.parent_boxes + [box]).map do |box|
197
+ box.class.to_s.sub(/.*::/, '')
198
+ end.join('-') << "##{box.object_id}"
199
+ name = "#{name} (#{(x + dx).to_i},#{(y + dy).to_i}-#{mask.width.to_i}x#{mask.height.to_i})"
200
+ ocg = doc.optional_content.ocg(name)
201
+ canvas.optional_content(ocg) do
202
+ canvas.translate(dx, dy) do
203
+ canvas.fill_color("green").stroke_color("darkgreen").
204
+ opacity(fill_alpha: 0.1, stroke_alpha: 0.2).
205
+ draw(:geom2d, object: mask, path_only: true).fill_stroke
206
+ end
207
+ end
208
+ page = "Page #{canvas.context.index + 1}" rescue "XObject"
209
+ doc.optional_content.default_configuration.add_ocg_to_ui(ocg, path: ['Debug', page])
210
+ end
211
+ box.draw(canvas, x + dx, y + dy)
212
+ end
213
+
214
+ end
215
+
121
216
  # Creates a new Box object, using the provided block as drawing block (see ::new).
122
217
  #
123
218
  # If +content_box+ is +true+, the width and height are taken to mean the content width and
@@ -143,10 +238,15 @@ module HexaPDF
143
238
  # The height of the box, including padding and/or borders.
144
239
  attr_reader :height
145
240
 
241
+ # The FitResult instance holding the result after a call to #fit.
242
+ attr_reader :fit_result
243
+
146
244
  # The style to be applied.
147
245
  #
148
246
  # Only the following properties are used:
149
247
  #
248
+ # * Style#position
249
+ # * Style#overflow
150
250
  # * Style#background_color
151
251
  # * Style#background_alpha
152
252
  # * Style#padding
@@ -190,7 +290,7 @@ module HexaPDF
190
290
  @style = Style.create(style)
191
291
  @properties = properties || {}
192
292
  @draw_block = block
193
- @fit_successful = false
293
+ @fit_result = FitResult.new(self)
194
294
  @split_box = false
195
295
  end
196
296
 
@@ -217,78 +317,91 @@ module HexaPDF
217
317
  height < 0 ? 0 : height
218
318
  end
219
319
 
220
- # Fits the box into the *frame* and returns +true+ if fitting was successful.
320
+ # Fits the box into the *frame* and returns the #fit_result.
221
321
  #
222
322
  # The arguments +available_width+ and +available_height+ are the width and height of the
223
323
  # current region of the frame, adjusted for this box. The frame itself is provided as third
224
324
  # argument.
225
325
  #
226
- # The default implementation uses the given available width and height for the box width and
227
- # height if they were initially set to 0. Otherwise the intially specified dimensions are
228
- # used. Then the #fit_content method is called which allows sub-classes to fit their content.
326
+ # If the box uses flow positioning, the width is set to the frame's width and the height to
327
+ # the remaining height in the frame. Otherwise the given available width and height are used
328
+ # for the width and height if they were initially set to 0. Otherwise the intially specified
329
+ # dimensions are used. The method returns early if the thus configured box already doesn't
330
+ # fit. Otherwise, the #fit_content method is called which allows sub-classes to fit their
331
+ # content.
229
332
  #
230
333
  # The following variables are set that may later be used during splitting or drawing:
231
334
  #
232
335
  # * (@fit_x, @fit_y): The lower-left corner of the content box where fitting was done. Can be
233
- # used to adjust the drawing position in #draw/#draw_content if necessary.
234
- # * @fit_successful: +true+ if fitting was successful.
336
+ # used to adjust the drawing position in #draw_content if necessary.
235
337
  def fit(available_width, available_height, frame)
236
- @width = (@initial_width > 0 ? @initial_width : available_width)
237
- @height = (@initial_height > 0 ? @initial_height : available_height)
238
- @fit_successful = float_compare(@width, available_width) <= 0 &&
239
- float_compare(@height, available_height) <= 0
240
- return unless @fit_successful
241
-
242
- @fit_successful = fit_content(available_width, available_height, frame)
338
+ @fit_result.reset(frame)
339
+ position_flow = supports_position_flow? && style.position == :flow
340
+ @width = if @initial_width > 0
341
+ @initial_width
342
+ elsif position_flow
343
+ frame.width
344
+ else
345
+ available_width
346
+ end
347
+ @height = if @initial_height > 0
348
+ @initial_height
349
+ elsif position_flow
350
+ frame.y - frame.bottom
351
+ else
352
+ available_height
353
+ end
354
+ return @fit_result if !position_flow && (float_compare(@width, available_width) > 0 ||
355
+ float_compare(@height, available_height) > 0)
356
+
357
+ fit_content(available_width, available_height, frame)
243
358
 
244
359
  @fit_x = frame.x + reserved_width_left
245
360
  @fit_y = frame.y - @height + reserved_height_bottom
246
361
 
247
- @fit_successful
362
+ @fit_result
248
363
  end
249
364
 
250
365
  # Tries to split the box into two, the first of which needs to fit into the current region of
251
- # the frame, and returns the parts as array.
366
+ # the frame, and returns the parts as array. The method #fit needs to be called before this
367
+ # method to correctly set-up the #fit_result.
252
368
  #
253
369
  # If the first item in the result array is not +nil+, it needs to be this box and it means
254
370
  # that even when #fit fails, a part of the box may still fit. Note that #fit should not be
255
- # called before #draw on the first box since it is already fitted. If not even a part of this
256
- # box fits into the current region, +nil+ should be returned as the first array element.
371
+ # called again before #draw on the first box since it is already fitted. If not even a part of
372
+ # this box fits into the current region, +nil+ should be returned as the first array element.
257
373
  #
258
374
  # Possible return values:
259
375
  #
260
- # [self]:: The box fully fits into the current region.
376
+ # [self, nil]:: The box fully fits into the current region.
261
377
  # [nil, self]:: The box can't be split or no part of the box fits into the current region.
262
378
  # [self, new_box]:: A part of the box fits and a new box is returned for the rest.
263
379
  #
264
- # This default implementation provides the basic functionality based on the #fit result that
265
- # should be sufficient for most subclasses; only #split_content needs to be implemented if
266
- # necessary.
267
- def split(available_width, available_height, frame)
268
- if @fit_successful
269
- [self, nil]
270
- elsif (style.position != :flow &&
271
- (float_compare(@width, available_width) > 0 ||
272
- float_compare(@height, available_height) > 0)) ||
273
- content_height == 0 || content_width == 0
274
- [nil, self]
275
- else
276
- split_content(available_width, available_height, frame)
380
+ # This default implementation provides the basic functionality based on the status of the
381
+ # #fit_result that should be sufficient for most subclasses; only #split_content needs to be
382
+ # implemented if necessary.
383
+ def split
384
+ case @fit_result.status
385
+ when :overflow then (@initial_height > 0 ? [self, nil] : split_content)
386
+ when :failure then [nil, self]
387
+ when :success then [self, nil]
277
388
  end
278
389
  end
279
390
 
280
391
  # Draws the content of the box onto the canvas at the position (x, y).
281
392
  #
282
- # The coordinate system is translated so that the origin is at the bottom left corner of the
283
- # **content box** during the drawing operations when +@draw_block+ is used.
393
+ # When +@draw_block+ is used (the block specified when creating the box), the coordinate
394
+ # system is translated so that the origin is at the bottom left corner of the **content box**.
284
395
  #
285
- # The block specified when creating the box is invoked with the canvas and the box as
286
- # arguments. Subclasses can specify an on-demand drawing method by setting the +@draw_block+
287
- # instance variable to +nil+ or a valid block. This is useful to avoid unnecessary set-up
288
- # operations when the block does nothing.
289
- #
290
- # Alternatively, if a #draw_content method is defined, this method is called.
396
+ # Subclasses should not rely on the +@draw_block+ but implement the #draw_content method. The
397
+ # coordinates passed to it are also modified to represent the bottom-left corner of the
398
+ # content box but the coordinate system is not translated.
291
399
  def draw(canvas, x, y)
400
+ if @fit_result.overflow? && @initial_height > 0 && style.overflow == :error
401
+ raise HexaPDF::Error, "Box with limited height doesn't completely fit and " \
402
+ "style property overflow is set to :error"
403
+ end
404
+
292
405
  if (oc = properties['optional_content'])
293
406
  canvas.optional_content(oc)
294
407
  end
@@ -381,12 +494,13 @@ module HexaPDF
381
494
 
382
495
  # Fits the content of the box and returns whether fitting was successful.
383
496
  #
384
- # This is just a stub implementation that returns +true+. Subclasses should override it to
385
- # provide the box specific behaviour.
497
+ # This is just a stub implementation that sets the #fit_result status to success if the
498
+ # content rectangle is not degenerate. Subclasses should override it to provide the box
499
+ # specific behaviour.
386
500
  #
387
501
  # See #fit for details.
388
502
  def fit_content(_available_width, _available_height, _frame)
389
- true
503
+ fit_result.success! if content_width > 0 && content_height > 0
390
504
  end
391
505
 
392
506
  # Splits the content of the box.
@@ -395,12 +509,12 @@ module HexaPDF
395
509
  # the content when it didn't fit.
396
510
  #
397
511
  # Subclasses that support splitting content need to provide an appropriate implementation and
398
- # use #create_split_box to create a cloned box to supply as the second argument.
399
- def split_content(_available_width, _available_height, _frame)
512
+ # use #create_split_box to create a cloned box to supply as the second return argument.
513
+ def split_content
400
514
  [nil, self]
401
515
  end
402
516
 
403
- # Draws the content of the box at position [x, y] which is the bottom-left corner of the
517
+ # Draws the content of the box at position [x, y] which is the bottom left corner of the
404
518
  # content box.
405
519
  #
406
520
  # This implementation uses the drawing block provided on initialization, if set, to draw the
@@ -422,7 +536,7 @@ module HexaPDF
422
536
  box = clone
423
537
  box.instance_variable_set(:@width, @initial_width)
424
538
  box.instance_variable_set(:@height, @initial_height)
425
- box.instance_variable_set(:@fit_successful, nil)
539
+ box.instance_variable_set(:@fit_result, FitResult.new(box))
426
540
  box.instance_variable_set(:@split_box, split_box_value)
427
541
  box
428
542
  end
@@ -111,6 +111,7 @@ module HexaPDF
111
111
  @content_heights[@frame_index] = [@content_heights[@frame_index],
112
112
  @initial_frame_y[@frame_index] - result.mask.y].max
113
113
  @fit_results << result
114
+ break unless box
114
115
  elsif !current_frame.find_next_region
115
116
  @frame_index += 1
116
117
  end
@@ -138,31 +138,21 @@ module HexaPDF
138
138
  super && (!@box_fitter || @box_fitter.fit_results.empty?)
139
139
  end
140
140
 
141
+ private
142
+
141
143
  # Fits the column box into the current region of the frame.
142
144
  #
143
- # If the style property 'position' is set to :flow, the columns might not be rectangles but
144
- # arbitrary (sets of) polygons since the +frame+s shape is taken into account.
145
- def fit(available_width, available_height, frame)
146
- return false if @initial_height > available_height || @initial_width > available_width
147
-
145
+ def fit_content(_available_width, _available_height, frame)
148
146
  initial_fit_successful = (@equal_height && @columns.size > 1 ? nil : false)
149
147
  tries = 0
150
- @width = if style.position == :flow
151
- (@initial_width > 0 ? @initial_width : frame.width) - reserved_width
152
- else
153
- (@initial_width > 0 ? @initial_width : available_width) - reserved_width
154
- end
155
- height = if style.position == :flow
156
- (@initial_height > 0 ? @initial_height : frame.height) - reserved_height
157
- else
158
- (@initial_height > 0 ? @initial_height : available_height) - reserved_height
159
- end
148
+ width = @width - reserved_width
149
+ height = @height - reserved_height
160
150
 
161
- columns = calculate_columns(@width)
162
- return false if columns.empty?
151
+ columns = calculate_columns(width)
152
+ return if columns.empty?
163
153
 
164
154
  left = (style.position == :flow ? frame.left : frame.x) + reserved_width_left
165
- top = (style.position == :flow ? frame.bottom + frame.height : frame.y) - reserved_height_top
155
+ top = frame.y - reserved_height_top
166
156
  successful_height = height
167
157
  unsuccessful_height = 0
168
158
 
@@ -206,16 +196,16 @@ module HexaPDF
206
196
  tries += 1
207
197
  end
208
198
 
209
- @width = columns[-1].sum + reserved_width
210
- @height = (@initial_height > 0 ? @initial_height : @box_fitter.content_heights.max + reserved_height)
211
- @draw_pos_x = frame.x + reserved_width_left
212
- @draw_pos_y = frame.y - @height + reserved_height_bottom
199
+ update_content_width { columns[-1].sum }
200
+ update_content_height { @box_fitter.content_heights.max }
213
201
 
214
- @box_fitter.success?
202
+ if @box_fitter.success?
203
+ fit_result.success!
204
+ elsif !@box_fitter.fit_results.empty?
205
+ fit_result.overflow!
206
+ end
215
207
  end
216
208
 
217
- private
218
-
219
209
  # Calculates the x-coordinates and widths of all columns based on the given total available
220
210
  # width.
221
211
  #
@@ -241,7 +231,7 @@ module HexaPDF
241
231
  end
242
232
 
243
233
  # Splits the content of the column box. This method is called from Box#split.
244
- def split_content(_available_width, _available_height, _frame)
234
+ def split_content
245
235
  box = create_split_box
246
236
  box.instance_variable_set(:@children, @box_fitter.remaining_boxes)
247
237
  [self, box]
@@ -249,8 +239,8 @@ module HexaPDF
249
239
 
250
240
  # Draws the child boxes onto the canvas at position [x, y].
251
241
  def draw_content(canvas, x, y)
252
- if style.position != :flow && (x != @draw_pos_x || y != @draw_pos_y)
253
- canvas.translate(x - @draw_pos_x, y - @draw_pos_y) do
242
+ if style.position != :flow && (x != @fit_x || y != @fit_y)
243
+ canvas.translate(x - @fit_x, y - @fit_y) do
254
244
  @box_fitter.fit_results.each {|result| result.draw(canvas) }
255
245
  end
256
246
  else
@@ -52,7 +52,7 @@ module HexaPDF
52
52
  # setting the style properties 'mask_mode', 'align' and 'valign', it is possible to lay out the
53
53
  # children bottom to top, left to right, or right to left:
54
54
  #
55
- # * The standard top to bottom layout:
55
+ # * The standard top-to-bottom layout:
56
56
  #
57
57
  # #>pdf-composer100
58
58
  # composer.container do |container|
@@ -61,7 +61,7 @@ module HexaPDF
61
61
  # container.box(:base, height: 20, style: {background_color: "hp-blue-light"})
62
62
  # end
63
63
  #
64
- # * The bottom to top layout (using valign = :bottom to fill up from the bottom and mask_mode =
64
+ # * The bottom-to-top layout (using valign = :bottom to fill up from the bottom and mask_mode =
65
65
  # :fill_horizontal to only remove the area to the left and right of the box):
66
66
  #
67
67
  # #>pdf-composer100
@@ -74,7 +74,7 @@ module HexaPDF
74
74
  # mask_mode: :fill_horizontal, valign: :bottom})
75
75
  # end
76
76
  #
77
- # * The left to right layout (using mask_mode = :fill_vertical to fill the area to the top and
77
+ # * The left-to-right layout (using mask_mode = :fill_vertical to fill the area to the top and
78
78
  # bottom of the box):
79
79
  #
80
80
  # #>pdf-composer100
@@ -87,7 +87,7 @@ module HexaPDF
87
87
  # mask_mode: :fill_vertical})
88
88
  # end
89
89
  #
90
- # * The right to left layout (using align = :right to fill up from the right and mask_mode =
90
+ # * The right-to-left layout (using align = :right to fill up from the right and mask_mode =
91
91
  # :fill_vertical to fill the area to the top and bottom of the box):
92
92
  #
93
93
  # #>pdf-composer100
@@ -143,11 +143,11 @@ module HexaPDF
143
143
  children.empty? ? 0 : result.mask.x + result.mask.width - my_frame.left
144
144
  end
145
145
  update_content_height { @box_fitter.content_heights.max }
146
- true
146
+ fit_result.success!
147
147
  end
148
148
  end
149
149
 
150
- # Draws the image onto the canvas at position [x, y].
150
+ # Draws the children onto the canvas at position [x, y].
151
151
  def draw_content(canvas, x, y)
152
152
  dx = x - @fit_x
153
153
  dy = y - @fit_y