hexapdf 0.43.0 → 0.44.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/examples/030-pdfa.rb +1 -0
  4. data/lib/hexapdf/composer.rb +1 -0
  5. data/lib/hexapdf/document/files.rb +7 -2
  6. data/lib/hexapdf/document/metadata.rb +12 -1
  7. data/lib/hexapdf/layout/box.rb +160 -61
  8. data/lib/hexapdf/layout/box_fitter.rb +1 -0
  9. data/lib/hexapdf/layout/column_box.rb +22 -24
  10. data/lib/hexapdf/layout/container_box.rb +2 -2
  11. data/lib/hexapdf/layout/frame.rb +13 -95
  12. data/lib/hexapdf/layout/image_box.rb +4 -4
  13. data/lib/hexapdf/layout/list_box.rb +11 -19
  14. data/lib/hexapdf/layout/style.rb +5 -1
  15. data/lib/hexapdf/layout/table_box.rb +48 -55
  16. data/lib/hexapdf/layout/text_box.rb +27 -42
  17. data/lib/hexapdf/parser.rb +5 -2
  18. data/lib/hexapdf/type/file_specification.rb +9 -5
  19. data/lib/hexapdf/type/graphics_state_parameter.rb +1 -1
  20. data/lib/hexapdf/version.rb +1 -1
  21. data/test/hexapdf/document/test_files.rb +5 -0
  22. data/test/hexapdf/document/test_metadata.rb +21 -0
  23. data/test/hexapdf/layout/test_box.rb +82 -37
  24. data/test/hexapdf/layout/test_box_fitter.rb +7 -0
  25. data/test/hexapdf/layout/test_column_box.rb +7 -13
  26. data/test/hexapdf/layout/test_container_box.rb +1 -1
  27. data/test/hexapdf/layout/test_frame.rb +0 -48
  28. data/test/hexapdf/layout/test_image_box.rb +14 -6
  29. data/test/hexapdf/layout/test_list_box.rb +25 -26
  30. data/test/hexapdf/layout/test_table_box.rb +39 -53
  31. data/test/hexapdf/layout/test_text_box.rb +38 -66
  32. data/test/hexapdf/test_composer.rb +6 -0
  33. data/test/hexapdf/test_parser.rb +8 -0
  34. data/test/hexapdf/type/test_file_specification.rb +2 -1
  35. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24f6839e903fd945678915625b1e2ef6a12221f29a82cf1c7a6d8bcea38af288
4
- data.tar.gz: 2f8309e2ef2406dd279e00643bf7fbf73ded63a1076c0067684a356fea8ee89e
3
+ metadata.gz: 3b30b54b90222fbcc5469b23153efc4b15083797f527af6c5b34b3f6e161832d
4
+ data.tar.gz: 969c67ab85591e3b7ccad17023bd3bc820ea5effa4c01e53d254915ab1626d71
5
5
  SHA512:
6
- metadata.gz: 8c6ddd8ec6f19d39daa9a6422fdb3595d255b528fb93ca7907ef12dab0eca23c74de9fcfc27d02c15b3a9d3c24d47c7f7db6ea472f4c1ed34c2b7c06d4a8a351
7
- data.tar.gz: 1d77c2da70d9048cbef6935afacde40fb6d4041414fce571a331a4bee33b0e3ee2ff59bb28eee02e515798c3c156a57f42f139356e946f64e878ea962756a1d8
6
+ metadata.gz: b60934dd6534ccd019953b278fa188b77996634b3e3878332302b9a2acccfd13b493b57432cff2113b7cc7727f028f24301c2d050f9b908c1b43cf66fbdeebfd
7
+ data.tar.gz: fdd792109306bc7cf0e30bb2da770e58e81e333843e3c785a9a31873a296b7d027121b467b54a80405332735e99bdaf6b0d167c9eaf08dbc04a26922b890bb51
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.44.0 - 2024-06-05
2
+
3
+ ### Added
4
+
5
+ * Support for specifying the MIME type when embedding files
6
+ * Support for adding custom XMP metadata
7
+
8
+ ### Changed
9
+
10
+ * **Breaking change**: Refactored the box implementation of the document layout
11
+ system
12
+
13
+ ### Fixed
14
+
15
+ * Parsing of invalid files with garbage bytes at the end
16
+
17
+
1
18
  ## 0.43.0 - 2024-05-26
2
19
 
3
20
  ### Added
data/examples/030-pdfa.rb CHANGED
@@ -8,6 +8,7 @@
8
8
  # Usage:
9
9
  # : `ruby pdfa.rb`
10
10
  #
11
+
11
12
  require 'hexapdf'
12
13
 
13
14
  HexaPDF::Composer.create('pdfa.pdf') do |composer|
@@ -425,6 +425,7 @@ module HexaPDF
425
425
  if draw_box
426
426
  @frame.draw(@canvas, result)
427
427
  drawn_on_page = true
428
+ (box = draw_box; break) unless box
428
429
  elsif !@frame.find_next_region
429
430
  unless drawn_on_page
430
431
  raise HexaPDF::Error, "Box doesn't fit on empty page"
@@ -70,6 +70,9 @@ module HexaPDF
70
70
  # description::
71
71
  # A description of the file.
72
72
  #
73
+ # mime_type::
74
+ # The MIME type that should be set for embedded files (so only used if +embed+ is +true+).
75
+ #
73
76
  # embed::
74
77
  # When an IO object is given, it is always embedded and this option is ignored.
75
78
  #
@@ -77,7 +80,7 @@ module HexaPDF
77
80
  # only a reference to it is stored.
78
81
  #
79
82
  # See: HexaPDF::Type::FileSpecification
80
- def add(file_or_io, name: nil, description: nil, embed: true)
83
+ def add(file_or_io, name: nil, description: nil, mime_type: nil, embed: true)
81
84
  name ||= File.basename(file_or_io) if file_or_io.kind_of?(String)
82
85
  if name.nil?
83
86
  raise ArgumentError, "The name argument is mandatory when given an IO object"
@@ -86,7 +89,9 @@ module HexaPDF
86
89
  spec = @document.add({Type: :Filespec})
87
90
  spec.path = name
88
91
  spec[:Desc] = description if description
89
- spec.embed(file_or_io, name: name, register: true) if embed || !file_or_io.kind_of?(String)
92
+ if embed || !file_or_io.kind_of?(String)
93
+ spec.embed(file_or_io, name: name, mime_type: mime_type, register: true)
94
+ end
90
95
  spec
91
96
  end
92
97
 
@@ -161,6 +161,7 @@ module HexaPDF
161
161
  @properties = PREDEFINED_PROPERTIES.transform_values(&:dup)
162
162
  @default_language = document.catalog[:Lang] || 'x-default'
163
163
  @metadata = Hash.new {|h, k| h[k] = {} }
164
+ @custom_metadata = []
164
165
  write_info_dict(true)
165
166
  write_metadata_stream(true)
166
167
  @document.register_listener(:complete_objects, &method(:write_metadata))
@@ -248,6 +249,16 @@ module HexaPDF
248
249
  end
249
250
  end
250
251
 
252
+ # Adds the given +data+ string as custom metadata to the XMP document.
253
+ #
254
+ # The +data+ string must contain a fully valid 'rdf:Description' element.
255
+ #
256
+ # Using this method allows adding metadata like PDF/A schema definitions for which there is no
257
+ # direct support by HexaPDF.
258
+ def custom_metadata(data)
259
+ @custom_metadata << data
260
+ end
261
+
251
262
  # :call-seq:
252
263
  # metadata.delete
253
264
  # metadata.delete(ns_prefix)
@@ -469,7 +480,7 @@ module HexaPDF
469
480
  <?xpacket begin="\u{FEFF}" id="#{SecureRandom.uuid.tr('-', '')}"?>
470
481
  <x:xmpmeta xmlns:x="adobe:ns:meta/">
471
482
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
472
- #{data}
483
+ #{data}#{@custom_metadata.empty? ? '' : "\n#{@custom_metadata.join("\n")}"}
473
484
  </rdf:RDF>
474
485
  </x:xmpmeta>
475
486
  <?xpacket end="r"?>
@@ -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,7 +317,7 @@ 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
@@ -225,70 +325,68 @@ module HexaPDF
225
325
  #
226
326
  # The default implementation uses the given available width and height for the box width and
227
327
  # 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.
328
+ # used. The method returns early if the thus configured box already doesn't fit. Otherwise,
329
+ # the #fit_content method is called which allows sub-classes to fit their content.
229
330
  #
230
331
  # The following variables are set that may later be used during splitting or drawing:
231
332
  #
232
333
  # * (@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.
334
+ # used to adjust the drawing position in #draw_content if necessary.
235
335
  def fit(available_width, available_height, frame)
336
+ @fit_result.reset(frame)
236
337
  @width = (@initial_width > 0 ? @initial_width : available_width)
237
338
  @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
339
+ return @fit_result if style.position != :flow && (float_compare(@width, available_width) > 0 ||
340
+ float_compare(@height, available_height) > 0)
241
341
 
242
- @fit_successful = fit_content(available_width, available_height, frame)
342
+ fit_content(available_width, available_height, frame)
243
343
 
244
344
  @fit_x = frame.x + reserved_width_left
245
345
  @fit_y = frame.y - @height + reserved_height_bottom
246
346
 
247
- @fit_successful
347
+ @fit_result
248
348
  end
249
349
 
250
350
  # 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.
351
+ # the frame, and returns the parts as array. The method #fit needs to be called before this
352
+ # method to correctly set-up the #fit_result.
252
353
  #
253
354
  # If the first item in the result array is not +nil+, it needs to be this box and it means
254
355
  # 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.
356
+ # called again before #draw on the first box since it is already fitted. If not even a part of
357
+ # this box fits into the current region, +nil+ should be returned as the first array element.
257
358
  #
258
359
  # Possible return values:
259
360
  #
260
- # [self]:: The box fully fits into the current region.
361
+ # [self, nil]:: The box fully fits into the current region.
261
362
  # [nil, self]:: The box can't be split or no part of the box fits into the current region.
262
363
  # [self, new_box]:: A part of the box fits and a new box is returned for the rest.
263
364
  #
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)
365
+ # This default implementation provides the basic functionality based on the status of the
366
+ # #fit_result that should be sufficient for most subclasses; only #split_content needs to be
367
+ # implemented if necessary.
368
+ def split
369
+ case @fit_result.status
370
+ when :overflow then (@initial_height > 0 ? [self, nil] : split_content)
371
+ when :failure then [nil, self]
372
+ when :success then [self, nil]
277
373
  end
278
374
  end
279
375
 
280
376
  # Draws the content of the box onto the canvas at the position (x, y).
281
377
  #
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.
378
+ # When +@draw_block+ is used (the block specified when creating the box), the coordinate
379
+ # system is translated so that the origin is at the bottom left corner of the **content box**.
284
380
  #
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.
381
+ # Subclasses should not rely on the +@draw_block+ but implement the #draw_content method. The
382
+ # coordinates passed to it are also modified to represent the bottom left corner of the
383
+ # content box but the coordinate system is not translated.
291
384
  def draw(canvas, x, y)
385
+ if @fit_result.overflow? && @initial_height > 0 && style.overflow == :error
386
+ raise HexaPDF::Error, "Box with limited height doesn't completely fit and " \
387
+ "style property overflow is set to :error"
388
+ end
389
+
292
390
  if (oc = properties['optional_content'])
293
391
  canvas.optional_content(oc)
294
392
  end
@@ -381,12 +479,13 @@ module HexaPDF
381
479
 
382
480
  # Fits the content of the box and returns whether fitting was successful.
383
481
  #
384
- # This is just a stub implementation that returns +true+. Subclasses should override it to
385
- # provide the box specific behaviour.
482
+ # This is just a stub implementation that sets the #fit_result status to success if the
483
+ # content rectangle is not degenerate. Subclasses should override it to provide the box
484
+ # specific behaviour.
386
485
  #
387
486
  # See #fit for details.
388
487
  def fit_content(_available_width, _available_height, _frame)
389
- true
488
+ fit_result.success! if content_width > 0 && content_height > 0
390
489
  end
391
490
 
392
491
  # Splits the content of the box.
@@ -395,12 +494,12 @@ module HexaPDF
395
494
  # the content when it didn't fit.
396
495
  #
397
496
  # 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)
497
+ # use #create_split_box to create a cloned box to supply as the second return argument.
498
+ def split_content
400
499
  [nil, self]
401
500
  end
402
501
 
403
- # Draws the content of the box at position [x, y] which is the bottom-left corner of the
502
+ # Draws the content of the box at position [x, y] which is the bottom left corner of the
404
503
  # content box.
405
504
  #
406
505
  # This implementation uses the drawing block provided on initialization, if set, to draw the
@@ -422,7 +521,7 @@ module HexaPDF
422
521
  box = clone
423
522
  box.instance_variable_set(:@width, @initial_width)
424
523
  box.instance_variable_set(:@height, @initial_height)
425
- box.instance_variable_set(:@fit_successful, nil)
524
+ box.instance_variable_set(:@fit_result, FitResult.new(box))
426
525
  box.instance_variable_set(:@split_box, split_box_value)
427
526
  box
428
527
  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,29 @@ 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
148
+ width = if style.position == :flow
149
+ (@initial_width > 0 ? @initial_width : frame.width) - reserved_width
150
+ else
151
+ (@initial_width > 0 ? @initial_width : available_width) - reserved_width
152
+ end
155
153
  height = if style.position == :flow
156
- (@initial_height > 0 ? @initial_height : frame.height) - reserved_height
154
+ (@initial_height > 0 ? @initial_height : frame.y - frame.bottom) - reserved_height
157
155
  else
158
156
  (@initial_height > 0 ? @initial_height : available_height) - reserved_height
159
157
  end
160
158
 
161
- columns = calculate_columns(@width)
162
- return false if columns.empty?
159
+ columns = calculate_columns(width)
160
+ return if columns.empty?
163
161
 
164
162
  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
163
+ top = frame.y - reserved_height_top
166
164
  successful_height = height
167
165
  unsuccessful_height = 0
168
166
 
@@ -206,16 +204,16 @@ module HexaPDF
206
204
  tries += 1
207
205
  end
208
206
 
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
207
+ update_content_width { columns[-1].sum }
208
+ update_content_height { @box_fitter.content_heights.max }
213
209
 
214
- @box_fitter.success?
210
+ if @box_fitter.success?
211
+ fit_result.success!
212
+ elsif !@box_fitter.fit_results.empty?
213
+ fit_result.overflow!
214
+ end
215
215
  end
216
216
 
217
- private
218
-
219
217
  # Calculates the x-coordinates and widths of all columns based on the given total available
220
218
  # width.
221
219
  #
@@ -241,7 +239,7 @@ module HexaPDF
241
239
  end
242
240
 
243
241
  # Splits the content of the column box. This method is called from Box#split.
244
- def split_content(_available_width, _available_height, _frame)
242
+ def split_content
245
243
  box = create_split_box
246
244
  box.instance_variable_set(:@children, @box_fitter.remaining_boxes)
247
245
  [self, box]
@@ -249,8 +247,8 @@ module HexaPDF
249
247
 
250
248
  # Draws the child boxes onto the canvas at position [x, y].
251
249
  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
250
+ if style.position != :flow && (x != @fit_x || y != @fit_y)
251
+ canvas.translate(x - @fit_x, y - @fit_y) do
254
252
  @box_fitter.fit_results.each {|result| result.draw(canvas) }
255
253
  end
256
254
  else
@@ -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