hexapdf 0.43.0 → 0.44.0

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