hexapdf 0.44.0 → 0.46.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -47
  3. data/examples/019-acro_form.rb +5 -0
  4. data/examples/027-composer_optional_content.rb +6 -4
  5. data/examples/030-pdfa.rb +12 -11
  6. data/lib/hexapdf/cli/inspect.rb +5 -0
  7. data/lib/hexapdf/composer.rb +23 -1
  8. data/lib/hexapdf/configuration.rb +8 -0
  9. data/lib/hexapdf/content/canvas.rb +3 -3
  10. data/lib/hexapdf/content/canvas_composer.rb +1 -0
  11. data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
  12. data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
  13. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
  14. data/lib/hexapdf/document/layout.rb +63 -30
  15. data/lib/hexapdf/document.rb +24 -2
  16. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  17. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  18. data/lib/hexapdf/importer.rb +15 -5
  19. data/lib/hexapdf/layout/box.rb +48 -36
  20. data/lib/hexapdf/layout/column_box.rb +3 -11
  21. data/lib/hexapdf/layout/container_box.rb +4 -4
  22. data/lib/hexapdf/layout/frame.rb +7 -6
  23. data/lib/hexapdf/layout/inline_box.rb +17 -23
  24. data/lib/hexapdf/layout/list_box.rb +27 -42
  25. data/lib/hexapdf/layout/page_style.rb +23 -16
  26. data/lib/hexapdf/layout/style.rb +5 -5
  27. data/lib/hexapdf/layout/table_box.rb +14 -10
  28. data/lib/hexapdf/layout/text_box.rb +60 -36
  29. data/lib/hexapdf/layout/text_fragment.rb +1 -1
  30. data/lib/hexapdf/layout/text_layouter.rb +7 -8
  31. data/lib/hexapdf/parser.rb +5 -1
  32. data/lib/hexapdf/rectangle.rb +4 -4
  33. data/lib/hexapdf/revisions.rb +1 -1
  34. data/lib/hexapdf/stream.rb +3 -3
  35. data/lib/hexapdf/tokenizer.rb +3 -2
  36. data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
  37. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  38. data/lib/hexapdf/type/acro_form/field.rb +8 -0
  39. data/lib/hexapdf/type/acro_form/form.rb +2 -1
  40. data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
  41. data/lib/hexapdf/type/form.rb +2 -2
  42. data/lib/hexapdf/version.rb +1 -1
  43. data/test/hexapdf/content/test_canvas_composer.rb +13 -8
  44. data/test/hexapdf/digital_signature/common.rb +66 -84
  45. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
  46. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
  47. data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
  48. data/test/hexapdf/digital_signature/test_handler.rb +2 -1
  49. data/test/hexapdf/document/test_layout.rb +44 -5
  50. data/test/hexapdf/layout/test_box.rb +23 -5
  51. data/test/hexapdf/layout/test_frame.rb +21 -2
  52. data/test/hexapdf/layout/test_inline_box.rb +17 -28
  53. data/test/hexapdf/layout/test_list_box.rb +8 -8
  54. data/test/hexapdf/layout/test_page_style.rb +7 -2
  55. data/test/hexapdf/layout/test_table_box.rb +8 -1
  56. data/test/hexapdf/layout/test_text_box.rb +51 -29
  57. data/test/hexapdf/layout/test_text_layouter.rb +0 -3
  58. data/test/hexapdf/test_composer.rb +14 -5
  59. data/test/hexapdf/test_document.rb +27 -0
  60. data/test/hexapdf/test_importer.rb +17 -0
  61. data/test/hexapdf/test_revisions.rb +54 -41
  62. data/test/hexapdf/test_serializer.rb +1 -0
  63. data/test/hexapdf/type/acro_form/test_form.rb +9 -0
  64. metadata +2 -2
@@ -77,9 +77,9 @@ module HexaPDF
77
77
  #
78
78
  # One style property, Layout::Style#font, is handled specially:
79
79
  #
80
- # * If no font is set on a style, the font "Times" is automatically set because otherwise there
81
- # would be problems with text drawing operations (font is the only style property that has no
82
- # valid default value).
80
+ # * If no font is set on a style, the default font specified via the configuration option
81
+ # 'font.default' is automatically set because otherwise there would be problems with text
82
+ # drawing operations (font is the only style property that has no valid default value).
83
83
  #
84
84
  # * Standard style objects only allow font wrapper objects to be set via the Layout::Style#font
85
85
  # method. This class makes usage easier by allowing strings or an array [name, options_hash]
@@ -151,7 +151,9 @@ module HexaPDF
151
151
  # :nodoc:
152
152
  def method_missing(name, *args, **kwargs, &block)
153
153
  if @layout.box_creation_method?(name)
154
- @children << @layout.send(name, *args, **kwargs, &block)
154
+ box = @layout.send(name, *args, **kwargs, &block)
155
+ @children << box
156
+ box
155
157
  else
156
158
  super
157
159
  end
@@ -175,9 +177,6 @@ module HexaPDF
175
177
 
176
178
  end
177
179
 
178
- # The mapping of style name (a Symbol) to Layout::Style instance.
179
- attr_reader :styles
180
-
181
180
  # Creates a new Layout object for the given PDF document.
182
181
  def initialize(document)
183
182
  @document = document
@@ -219,6 +218,21 @@ module HexaPDF
219
218
  style
220
219
  end
221
220
 
221
+ # :call-seq:
222
+ # layout.styles -> styles
223
+ # layout.styles(**mapping) -> styles
224
+ #
225
+ # Returns the mapping of style names to Layout::Style instances. If +mapping+ is provided,
226
+ # also defines the given styles using #style.
227
+ #
228
+ # The argument +mapping+ needs to be a hash mapping a style name (a Symbol) to style
229
+ # properties. The special key +:base+ can be used to define the base style. For details see
230
+ # #style.
231
+ def styles(**mapping)
232
+ mapping.each {|name, properties| style(name, **properties) } unless mapping.empty?
233
+ @styles
234
+ end
235
+
222
236
  # Creates an inline box for use together with text fragments.
223
237
  #
224
238
  # The +valign+ argument ist used to specify the vertical alignment of the box within the text
@@ -342,6 +356,7 @@ module HexaPDF
342
356
  width: width, height: height, properties: properties,
343
357
  style: box_style)
344
358
  end
359
+ alias text text_box
345
360
 
346
361
  # Creates a HexaPDF::Layout::TextBox like #text_box but allows parts of the text to be
347
362
  # formatted differently.
@@ -444,6 +459,7 @@ module HexaPDF
444
459
  box_class_for_name(:text).new(items: data, width: width, height: height,
445
460
  properties: properties, style: box_style)
446
461
  end
462
+ alias formatted_text formatted_text_box
447
463
 
448
464
  # Creates a HexaPDF::Layout::ImageBox for the given image.
449
465
  #
@@ -465,6 +481,7 @@ module HexaPDF
465
481
  box_class_for_name(:image).new(image: image, width: width, height: height,
466
482
  properties: properties, style: style)
467
483
  end
484
+ alias image image_box
468
485
 
469
486
  # This helper class is used by Layout#table_box to allow specifying the keyword arguments used
470
487
  # when converting cell data to box instances.
@@ -483,8 +500,25 @@ module HexaPDF
483
500
  @number_of_columns = number_of_columns
484
501
  end
485
502
 
486
- # Stores the keyword arguments in +args+ for the given 0-based rows and columns which can
487
- # either be a single number or a range of numbers.
503
+ # Stores the hash +args+ containing styling properties for the cells specified via the given
504
+ # 0-based rows and columns.
505
+ #
506
+ # Rows and columns can either be single numbers, ranges of numbers or stepped ranges (i.e.
507
+ # Enumerator::ArithmeticSequence instances).
508
+ #
509
+ # Examples:
510
+ #
511
+ # # Gray background for all cells
512
+ # args[] = {cell: {background_color: "gray"}}
513
+ #
514
+ # # Cell at (2, 3) gets a bigger font size
515
+ # args[2, 3] = {font_size: 50}
516
+ #
517
+ # # First column of every row has bold font
518
+ # args[0..-1, 0] = {font: 'Helvetica bold'}
519
+ #
520
+ # # Every second row has a blue background
521
+ # args[(0..-1).step(2)] = {cell: {background_color: "blue"}}
488
522
  def []=(rows = 0..-1, cols = 0..-1, args)
489
523
  rows = adjust_range(rows.kind_of?(Integer) ? rows..rows : rows, @number_of_rows)
490
524
  cols = adjust_range(cols.kind_of?(Integer) ? cols..cols : cols, @number_of_columns)
@@ -497,7 +531,7 @@ module HexaPDF
497
531
  # is merged.
498
532
  def retrieve_arguments_for(row, col)
499
533
  @argument_infos.each_with_object({}) do |arg_info, result|
500
- next unless arg_info.rows.cover?(row) && arg_info.cols.cover?(col)
534
+ next unless arg_info.rows.include?(row) && arg_info.cols.include?(col)
501
535
  if arg_info.args[:cell]
502
536
  arg_info.args[:cell] = (result[:cell] || {}).merge(arg_info.args[:cell])
503
537
  end
@@ -510,7 +544,8 @@ module HexaPDF
510
544
  # Adjusts the +range+ so that both the begin and the end of the range are zero or positive
511
545
  # integers smaller than +max+.
512
546
  def adjust_range(range, max)
513
- (range.begin % max)..(range.end % max)
547
+ r = (range.begin % max)..(range.end % max)
548
+ range.kind_of?(Range) ? r : r.step(range.step)
514
549
  end
515
550
 
516
551
  end
@@ -528,7 +563,8 @@ module HexaPDF
528
563
  # Additional arguments for the #text_box invocations can be specified using the optional block
529
564
  # that yields a CellArgumentCollector instance. This allows customization of the text boxes.
530
565
  # By specifying the special key +:cell+ it is also possible to assign style properties to the
531
- # cells themselves.
566
+ # cells themselves, irrespective of the type of content of the cells. See
567
+ # CellArgumentCollector#[]= for details.
532
568
  #
533
569
  # See HexaPDF::Layout::TableBox::new for details on +column_widths+, +header+, +footer+, and
534
570
  # +cell_style+.
@@ -574,6 +610,7 @@ module HexaPDF
574
610
  footer: footer, cell_style: cell_style, width: width,
575
611
  height: height, properties: properties, style: style)
576
612
  end
613
+ alias table table_box
577
614
 
578
615
  LOREM_IPSUM = [ # :nodoc:
579
616
  "Lorem ipsum dolor sit amet, con\u{00AD}sectetur adipis\u{00AD}cing elit, sed " \
@@ -593,22 +630,13 @@ module HexaPDF
593
630
  def lorem_ipsum_box(sentences: 4, count: 1, **text_box_properties)
594
631
  text_box(([LOREM_IPSUM[0, sentences].join(" ")] * count).join("\n\n"), **text_box_properties)
595
632
  end
633
+ alias lorem_ipsum lorem_ipsum_box
596
634
 
597
- BOX_METHOD_NAMES = [:text, :formatted_text, :image, :table, :lorem_ipsum] #:nodoc:
598
-
599
- # Allows creating boxes using more convenient method names:
600
- #
601
- # * #text for #text_box
602
- # * #formatted_text for #formatted_text_box
603
- # * #image for #image_box
604
- # * #lorem_ipsum for #lorem_ipsum_box
605
- # * The name of a pre-defined box class like #column will invoke #box appropriately. Same if
606
- # used with a '_box' suffix.
635
+ # Allows creating boxes using more convenient method names: The name of a pre-defined box
636
+ # class like #column will invoke #box appropriately. Same if used with a '_box' suffix.
607
637
  def method_missing(name, *args, **kwargs, &block)
608
638
  name_without_box = name.to_s.sub(/_box$/, '').intern
609
- if BOX_METHOD_NAMES.include?(name)
610
- send("#{name}_box", *args, **kwargs, &block)
611
- elsif @document.config['layout.boxes.map'].key?(name_without_box)
639
+ if @document.config['layout.boxes.map'].key?(name_without_box)
612
640
  box(name_without_box, *args, **kwargs, &block)
613
641
  else
614
642
  super
@@ -620,6 +648,8 @@ module HexaPDF
620
648
  box_creation_method?(name) || super
621
649
  end
622
650
 
651
+ BOX_METHOD_NAMES = [:text, :formatted_text, :image, :table, :lorem_ipsum] #:nodoc:
652
+
623
653
  # :nodoc:
624
654
  def box_creation_method?(name)
625
655
  name = name.to_s.sub(/_box$/, '').intern
@@ -636,8 +666,8 @@ module HexaPDF
636
666
  end
637
667
  end
638
668
 
639
- # Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and +properties+
640
- # arguments.
669
+ # Retrieves the appropriate HexaPDF::Layout::Style object based on the +style+ and
670
+ # +properties+ arguments.
641
671
  #
642
672
  # The +style+ argument specifies the style to retrieve. It can either be a registered style
643
673
  # name (see #style), a hash with style properties or +nil+. In the latter case the registered
@@ -646,15 +676,18 @@ module HexaPDF
646
676
  # If the +properties+ hash is not empty, the retrieved style is duplicated and the properties
647
677
  # hash is applied to it.
648
678
  #
649
- # Finally, a default font (the one from the :base style or otherwise 'Times') is set if
650
- # necessary to ensure that the style object works in all cases.
679
+ # Finally, a default font (the one from the :base style or otherwise the one set using the
680
+ # configuration option 'font.default') is set if necessary to ensure that the style object
681
+ # works in all cases.
651
682
  def retrieve_style(style, properties = nil)
652
683
  if style.kind_of?(Symbol) && !@styles.key?(style)
653
684
  raise HexaPDF::Error, "Style #{style} not defined"
654
685
  end
655
686
  style = HexaPDF::Layout::Style.create(@styles[style] || style || @styles[:base])
656
687
  style = style.dup.update(**properties) unless properties.nil? || properties.empty?
657
- style.font(@styles[:base].font? && @styles[:base].font || 'Times') unless style.font?
688
+ unless style.font?
689
+ style.font(@styles[:base].font? && @styles[:base].font || @document.config['font.default'])
690
+ end
658
691
  unless style.font.respond_to?(:pdf_object)
659
692
  name, options = *style.font
660
693
  style.font(@document.fonts.add(name, **(options || {})))
@@ -278,8 +278,9 @@ module HexaPDF
278
278
  # If the same argument is provided in multiple invocations, the import is done only once and
279
279
  # the previously imported object is returned.
280
280
  #
281
- # Note: If you first create a PDF document from scratch and then want to import objects from it
282
- # into another PDF document, you need to run the following on the source document:
281
+ # Note: If you first create a PDF document from scratch or if you modify an existing document,
282
+ # and then want to import objects from it into another PDF document, you need to run the
283
+ # following on the source document:
283
284
  #
284
285
  # doc.dispatch_message(:complete_objects)
285
286
  # doc.validate
@@ -701,6 +702,27 @@ module HexaPDF
701
702
  result
702
703
  end
703
704
 
705
+ # Returns an in-memory copy of the PDF document.
706
+ #
707
+ # In the context of this method this means that the returned PDF document contains the same PDF
708
+ # object tree as this document, starting at the trailer. A possibly set encryption is not
709
+ # transferred to the returned document.
710
+ #
711
+ # Note: If this PDF document was created from scratch or if it is an existing document that was
712
+ # modified, the following commands need to be run on this document beforehand:
713
+ #
714
+ # doc.dispatch_message(:complete_objects)
715
+ # doc.validate
716
+ #
717
+ # This ensures that all the necessary PDF structures set-up correctly.
718
+ def duplicate
719
+ dest = HexaPDF::Document.new
720
+ dupped_trailer = HexaPDF::Importer.copy(dest, trailer, allow_all: true)
721
+ dest.revisions.current.trailer.value.replace(dupped_trailer.value)
722
+ dest.trailer.delete(:Encrypt)
723
+ dest
724
+ end
725
+
704
726
  # :call-seq:
705
727
  # doc.write(filename, incremental: false, validate: true, update_fields: true, optimize: false)
706
728
  # doc.write(io, incremental: false, validate: true, update_fields: true, optimize: false)
@@ -51,7 +51,7 @@ module HexaPDF
51
51
  attr_accessor :name
52
52
 
53
53
  # Character bounding box as array of four numbers, specifying the x- and y-coordinates of
54
- # the bottom left corner and the x- and y-coordinates of the top right corner.
54
+ # the bottom-left corner and the x- and y-coordinates of the top-right corner.
55
55
  attr_accessor :bbox
56
56
 
57
57
  end
@@ -63,7 +63,7 @@ module HexaPDF
63
63
  attr_accessor :weight
64
64
 
65
65
  # The font bounding box as array of four numbers, specifying the x- and y-coordinates of the
66
- # bottom left corner and the x- and y-coordinates of the top right corner.
66
+ # bottom-left corner and the x- and y-coordinates of the top-right corner.
67
67
  attr_accessor :bounding_box
68
68
 
69
69
  # The y-value of the top of the capital H (or 0 or nil if the font doesn't contain a capital
@@ -71,14 +71,18 @@ module HexaPDF
71
71
  # Imports the given +object+ (belonging to the +source+ document) by completely copying it and
72
72
  # all referenced objects into the +destination+ object.
73
73
  #
74
+ # If the +allow_all+ argument is set to +true+, then the usually omitted catalog and page tree
75
+ # node objects (see the class description for details) are also copied which allows one to make
76
+ # an in-memory duplicate of a HexaPDF::Document object.
77
+ #
74
78
  # Specifying +source+ is optionial if it can be determined through +object+.
75
79
  #
76
80
  # After the operation is finished, all state is discarded. This means that another call to this
77
81
  # method for the same object will yield a new - and different - object. This is in contrast to
78
82
  # using ::for together with #import which remembers and returns already imported objects (which
79
83
  # is generally what one wants).
80
- def self.copy(destination, object, source: nil)
81
- new(NullableWeakRef.new(destination)).import(object, source: source)
84
+ def self.copy(destination, object, allow_all: false, source: nil)
85
+ new(NullableWeakRef.new(destination), allow_all: allow_all).import(object, source: source)
82
86
  end
83
87
 
84
88
  private_class_method :new
@@ -86,9 +90,10 @@ module HexaPDF
86
90
  attr_reader :destination #:nodoc:
87
91
 
88
92
  # Initializes a new importer that can import objects to the +destination+ document.
89
- def initialize(destination)
93
+ def initialize(destination, allow_all: false)
90
94
  @destination = destination
91
95
  @mapper = {}
96
+ @allow_all = allow_all
92
97
  end
93
98
 
94
99
  SourceWrapper = Struct.new(:source) #:nodoc:
@@ -136,7 +141,7 @@ module HexaPDF
136
141
  internal_import(wrapper.source.object(object), wrapper)
137
142
  when HexaPDF::Object
138
143
  wrapper.source ||= object.document
139
- if object.type == :Catalog || object.type == :Pages
144
+ if !@allow_all && (object.type == :Catalog || object.type == :Pages)
140
145
  @mapper[object.data] = nil
141
146
  elsif (mapped_object = @mapper[object.data]&.__getobj__) && !mapped_object.null?
142
147
  mapped_object
@@ -149,7 +154,12 @@ module HexaPDF
149
154
  obj.data.gen = 0
150
155
  @destination.add(obj) if object.indirect?
151
156
 
152
- obj.data.stream = obj.data.stream.dup if obj.data.stream.kind_of?(String)
157
+ stream = obj.data.stream
158
+ if stream.kind_of?(String)
159
+ obj.data.stream = stream.dup
160
+ elsif stream&.source.kind_of?(FiberDoubleForString)
161
+ obj.data.stream = stream.fiber.resume.dup
162
+ end
153
163
  obj.data.value = duplicate(obj.data.value, wrapper)
154
164
  obj.data.value.update(duplicate(object.copy_inherited_values, wrapper)) if object.type == :Page
155
165
  obj
@@ -69,6 +69,10 @@ module HexaPDF
69
69
  # If the subclass supports the value :flow of the 'position' style property, this method
70
70
  # needs to be overridden to return +true+.
71
71
  #
72
+ # Additionally, if a box object uses flow positioning, #fit_result.x should be set to the
73
+ # correct value since Frame#fit can't determine this and uses Frame#left in the absence of a
74
+ # set value.
75
+ #
72
76
  # #empty?::
73
77
  # This method should return +true+ if the subclass won't draw anything when #draw is called.
74
78
  #
@@ -89,28 +93,13 @@ module HexaPDF
89
93
  # This method draws the box specific content and is called from #draw which already handles
90
94
  # things like drawing the border and background. So #draw should usually not be overridden.
91
95
  #
92
- # This base class provides various private helper methods for use in the above methods:
93
- #
94
- # +reserved_width+, +reserved_height+::
95
- # Returns the width respectively the height of the reserved space inside the box that is
96
- # used for the border and padding.
97
- #
98
- # +reserved_width_left+, +reserved_width_right+, +reserved_height_top+,
99
- # +reserved_height_bottom+::
100
- # Returns the reserved space inside the box at the specified edge (left, right, top,
101
- # bottom).
102
- #
103
- # +update_content_width+, +update_content_height+::
104
- # Takes a block that should return the content width respectively height and sets the box's
105
- # width respectively height accordingly.
106
- #
107
- # +create_split_box+::
108
- # Creates a new box based on this one and resets the internal data back to their original
109
- # values.
96
+ # This base class also provides various protected helper methods for use in the above methods:
110
97
  #
111
- # The keyword argument +split_box_value+ (defaults to +true+) is used to set the
112
- # +@split_box+ variable to make the new box aware that it is a split box. This can be set to
113
- # any other truthy value to convey more meaning.
98
+ # * #reserved_width, #reserved_height
99
+ # * #reserved_width_left, #reserved_width_right, #reserved_height_top,
100
+ # #reserved_height_bottom
101
+ # * #update_content_width, #update_content_height
102
+ # * #create_split_box
114
103
  class Box
115
104
 
116
105
  include HexaPDF::Utils
@@ -323,10 +312,12 @@ module HexaPDF
323
312
  # current region of the frame, adjusted for this box. The frame itself is provided as third
324
313
  # argument.
325
314
  #
326
- # The default implementation uses the given available width and height for the box width and
327
- # height if they were initially set to 0. Otherwise the intially specified dimensions are
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.
315
+ # If the box uses flow positioning, the width is set to the frame's width and the height to
316
+ # the remaining height in the frame. Otherwise the given available width and height are used
317
+ # for the width and height if they were initially set to 0. Otherwise the intially specified
318
+ # dimensions are used. The method returns early if the thus configured box already doesn't
319
+ # fit. Otherwise, the #fit_content method is called which allows sub-classes to fit their
320
+ # content.
330
321
  #
331
322
  # The following variables are set that may later be used during splitting or drawing:
332
323
  #
@@ -334,10 +325,25 @@ module HexaPDF
334
325
  # used to adjust the drawing position in #draw_content if necessary.
335
326
  def fit(available_width, available_height, frame)
336
327
  @fit_result.reset(frame)
337
- @width = (@initial_width > 0 ? @initial_width : available_width)
338
- @height = (@initial_height > 0 ? @initial_height : available_height)
339
- return @fit_result if style.position != :flow && (float_compare(@width, available_width) > 0 ||
340
- float_compare(@height, available_height) > 0)
328
+ position_flow = supports_position_flow? && style.position == :flow
329
+ @width = if @initial_width > 0
330
+ @initial_width
331
+ elsif position_flow
332
+ frame.width
333
+ else
334
+ available_width
335
+ end
336
+ @height = if @initial_height > 0
337
+ @initial_height
338
+ elsif position_flow
339
+ frame.y - frame.bottom
340
+ else
341
+ available_height
342
+ end
343
+ return @fit_result if !position_flow && (float_compare(@width, available_width) > 0 ||
344
+ float_compare(@height, available_height) > 0 ||
345
+ @width - reserved_width < 0 ||
346
+ @height - reserved_height < 0)
341
347
 
342
348
  fit_content(available_width, available_height, frame)
343
349
 
@@ -379,7 +385,7 @@ module HexaPDF
379
385
  # system is translated so that the origin is at the bottom left corner of the **content box**.
380
386
  #
381
387
  # 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
388
+ # coordinates passed to it are also modified to represent the bottom-left corner of the
383
389
  # content box but the coordinate system is not translated.
384
390
  def draw(canvas, x, y)
385
391
  if @fit_result.overflow? && @initial_height > 0 && style.overflow == :error
@@ -417,7 +423,7 @@ module HexaPDF
417
423
  (style.overlays? && !style.overlays.none?))
418
424
  end
419
425
 
420
- private
426
+ protected
421
427
 
422
428
  # Returns the width that is reserved by the padding and border style properties.
423
429
  def reserved_width
@@ -465,12 +471,18 @@ module HexaPDF
465
471
  result
466
472
  end
467
473
 
474
+ # :call-seq:
475
+ # update_content_width { block }
476
+ #
468
477
  # Updates the width of the box using the content width returned by the block.
469
478
  def update_content_width
470
479
  return if @initial_width > 0
471
480
  @width = yield + reserved_width
472
481
  end
473
482
 
483
+ # :call-seq:
484
+ # update_content_height { block }
485
+ #
474
486
  # Updates the height of the box using the content height returned by the block.
475
487
  def update_content_height
476
488
  return if @initial_height > 0
@@ -479,13 +491,12 @@ module HexaPDF
479
491
 
480
492
  # Fits the content of the box and returns whether fitting was successful.
481
493
  #
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.
494
+ # This is just a stub implementation that sets the #fit_result status to success. Subclasses
495
+ # should override it to provide the box specific behaviour.
485
496
  #
486
497
  # See #fit for details.
487
498
  def fit_content(_available_width, _available_height, _frame)
488
- fit_result.success! if content_width > 0 && content_height > 0
499
+ fit_result.success!
489
500
  end
490
501
 
491
502
  # Splits the content of the box.
@@ -510,7 +521,8 @@ module HexaPDF
510
521
  end
511
522
  end
512
523
 
513
- # Creates a new box based on this one and resets the data back to their original values.
524
+ # Creates a new box based on this one and resets the internal data back to their original
525
+ # values.
514
526
  #
515
527
  # The variable +@split_box+ is set to +split_box_value+ (defaults to +true+) to make the new
516
528
  # box aware that it is a split box. If needed, subclasses can set the variable to other truthy
@@ -142,19 +142,11 @@ module HexaPDF
142
142
 
143
143
  # Fits the column box into the current region of the frame.
144
144
  #
145
- def fit_content(available_width, available_height, frame)
145
+ def fit_content(_available_width, _available_height, frame)
146
146
  initial_fit_successful = (@equal_height && @columns.size > 1 ? nil : false)
147
147
  tries = 0
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
153
- height = if style.position == :flow
154
- (@initial_height > 0 ? @initial_height : frame.y - frame.bottom) - reserved_height
155
- else
156
- (@initial_height > 0 ? @initial_height : available_height) - reserved_height
157
- end
148
+ width = @width - reserved_width
149
+ height = @height - reserved_height
158
150
 
159
151
  columns = calculate_columns(width)
160
152
  return if columns.empty?
@@ -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
@@ -54,7 +54,7 @@ module HexaPDF
54
54
  #
55
55
  # The method #fit is also called for absolutely positioned boxes but since these boxes are not
56
56
  # subject to the normal constraints, the provided available width and height are the width and
57
- # height inside the frame to the right and top of the bottom left corner of the box.
57
+ # height inside the frame to the right and top of the bottom-left corner of the box.
58
58
  #
59
59
  # * If the box didn't fit, call #find_next_region to determine the next region for placing the
60
60
  # box. If a new region was found, start over with #fit. Otherwise the frame has no more space
@@ -84,10 +84,10 @@ module HexaPDF
84
84
 
85
85
  include HexaPDF::Utils
86
86
 
87
- # The x-coordinate of the bottom left corner.
87
+ # The x-coordinate of the bottom-left corner.
88
88
  attr_reader :left
89
89
 
90
- # The y-coordinate of the bottom left corner.
90
+ # The y-coordinate of the bottom-left corner.
91
91
  attr_reader :bottom
92
92
 
93
93
  # The width of the frame.
@@ -127,7 +127,7 @@ module HexaPDF
127
127
 
128
128
  # An array of box objects representing the parent boxes.
129
129
  #
130
- # The immediate parent is the last array entry, the top most parent the first one. All boxes
130
+ # The immediate parent is the last array entry, the top-most parent the first one. All boxes
131
131
  # that are fitted into this frame have to be child boxes of the immediate parent box.
132
132
  attr_reader :parent_boxes
133
133
 
@@ -216,6 +216,7 @@ module HexaPDF
216
216
  end
217
217
 
218
218
  fit_result = box.fit(aw, ah, self)
219
+ return fit_result if fit_result.failure?
219
220
 
220
221
  width = box.width
221
222
  height = box.height
@@ -251,7 +252,7 @@ module HexaPDF
251
252
  end
252
253
  end
253
254
  when :flow
254
- x = 0
255
+ x = fit_result.x || left
255
256
  y = @y - height
256
257
  else
257
258
  raise HexaPDF::Error, "Invalid value '#{position}' for style property position"
@@ -382,7 +383,7 @@ module HexaPDF
382
383
  # Since not all text may start at the top of the frame, the offset argument can be used to
383
384
  # specify a vertical offset from the top of the frame where layouting should start.
384
385
  #
385
- # To be compatible with TextLayouter, the top left corner of the bounding box of the frame's
386
+ # To be compatible with TextLayouter, the top-left corner of the bounding box of the frame's
386
387
  # shape is the origin of the coordinate system for the width specification, with positive
387
388
  # x-values to the right and positive y-values downwards.
388
389
  #