hexapdf 0.43.0 → 0.45.0

Sign up to get free protection for your applications and to get access to all the features.
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