hexapdf 0.44.0 → 0.46.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 (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
  #