hexapdf 0.45.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -47
  3. data/examples/019-acro_form.rb +5 -0
  4. data/lib/hexapdf/cli/inspect.rb +5 -0
  5. data/lib/hexapdf/composer.rb +1 -1
  6. data/lib/hexapdf/configuration.rb +8 -0
  7. data/lib/hexapdf/digital_signature/cms_handler.rb +31 -3
  8. data/lib/hexapdf/digital_signature/signing/default_handler.rb +9 -1
  9. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +5 -1
  10. data/lib/hexapdf/document/layout.rb +48 -27
  11. data/lib/hexapdf/document.rb +24 -2
  12. data/lib/hexapdf/importer.rb +15 -5
  13. data/lib/hexapdf/layout/box.rb +25 -28
  14. data/lib/hexapdf/layout/frame.rb +1 -1
  15. data/lib/hexapdf/layout/inline_box.rb +17 -23
  16. data/lib/hexapdf/layout/list_box.rb +24 -29
  17. data/lib/hexapdf/layout/page_style.rb +23 -16
  18. data/lib/hexapdf/layout/style.rb +2 -2
  19. data/lib/hexapdf/layout/text_box.rb +2 -6
  20. data/lib/hexapdf/parser.rb +5 -1
  21. data/lib/hexapdf/revisions.rb +1 -1
  22. data/lib/hexapdf/stream.rb +3 -3
  23. data/lib/hexapdf/tokenizer.rb +3 -2
  24. data/lib/hexapdf/type/acro_form/button_field.rb +2 -0
  25. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  26. data/lib/hexapdf/type/acro_form/field.rb +8 -0
  27. data/lib/hexapdf/type/acro_form/form.rb +2 -1
  28. data/lib/hexapdf/type/acro_form/text_field.rb +2 -0
  29. data/lib/hexapdf/version.rb +1 -1
  30. data/test/hexapdf/digital_signature/common.rb +66 -84
  31. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +7 -0
  32. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +9 -0
  33. data/test/hexapdf/digital_signature/test_cms_handler.rb +41 -1
  34. data/test/hexapdf/digital_signature/test_handler.rb +2 -1
  35. data/test/hexapdf/document/test_layout.rb +28 -5
  36. data/test/hexapdf/layout/test_box.rb +12 -5
  37. data/test/hexapdf/layout/test_frame.rb +12 -2
  38. data/test/hexapdf/layout/test_inline_box.rb +17 -28
  39. data/test/hexapdf/layout/test_list_box.rb +5 -5
  40. data/test/hexapdf/layout/test_page_style.rb +7 -2
  41. data/test/hexapdf/layout/test_text_box.rb +3 -9
  42. data/test/hexapdf/layout/test_text_layouter.rb +0 -3
  43. data/test/hexapdf/test_document.rb +27 -0
  44. data/test/hexapdf/test_importer.rb +17 -0
  45. data/test/hexapdf/test_revisions.rb +54 -41
  46. data/test/hexapdf/type/acro_form/test_form.rb +9 -0
  47. metadata +2 -2
@@ -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)
@@ -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
@@ -352,7 +341,9 @@ module HexaPDF
352
341
  available_height
353
342
  end
354
343
  return @fit_result if !position_flow && (float_compare(@width, available_width) > 0 ||
355
- float_compare(@height, available_height) > 0)
344
+ float_compare(@height, available_height) > 0 ||
345
+ @width - reserved_width < 0 ||
346
+ @height - reserved_height < 0)
356
347
 
357
348
  fit_content(available_width, available_height, frame)
358
349
 
@@ -432,7 +423,7 @@ module HexaPDF
432
423
  (style.overlays? && !style.overlays.none?))
433
424
  end
434
425
 
435
- private
426
+ protected
436
427
 
437
428
  # Returns the width that is reserved by the padding and border style properties.
438
429
  def reserved_width
@@ -480,12 +471,18 @@ module HexaPDF
480
471
  result
481
472
  end
482
473
 
474
+ # :call-seq:
475
+ # update_content_width { block }
476
+ #
483
477
  # Updates the width of the box using the content width returned by the block.
484
478
  def update_content_width
485
479
  return if @initial_width > 0
486
480
  @width = yield + reserved_width
487
481
  end
488
482
 
483
+ # :call-seq:
484
+ # update_content_height { block }
485
+ #
489
486
  # Updates the height of the box using the content height returned by the block.
490
487
  def update_content_height
491
488
  return if @initial_height > 0
@@ -494,13 +491,12 @@ module HexaPDF
494
491
 
495
492
  # Fits the content of the box and returns whether fitting was successful.
496
493
  #
497
- # This is just a stub implementation that sets the #fit_result status to success if the
498
- # content rectangle is not degenerate. Subclasses should override it to provide the box
499
- # specific behaviour.
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.
500
496
  #
501
497
  # See #fit for details.
502
498
  def fit_content(_available_width, _available_height, _frame)
503
- fit_result.success! if content_width > 0 && content_height > 0
499
+ fit_result.success!
504
500
  end
505
501
 
506
502
  # Splits the content of the box.
@@ -525,7 +521,8 @@ module HexaPDF
525
521
  end
526
522
  end
527
523
 
528
- # 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.
529
526
  #
530
527
  # The variable +@split_box+ is set to +split_box_value+ (defaults to +true+) to make the new
531
528
  # box aware that it is a split box. If needed, subclasses can set the variable to other truthy
@@ -252,7 +252,7 @@ module HexaPDF
252
252
  end
253
253
  end
254
254
  when :flow
255
- x = 0
255
+ x = fit_result.x || left
256
256
  y = @y - height
257
257
  else
258
258
  raise HexaPDF::Error, "Invalid value '#{position}' for style property position"
@@ -43,14 +43,12 @@ module HexaPDF
43
43
  # An InlineBox wraps a regular Box so that it can be used as an item for a Line. This enables
44
44
  # inline graphics.
45
45
  #
46
- # Complete box auto-sizing is not possible since the available space cannot be determined
47
- # beforehand! This means the box *must* have at least its width set. The height may either also
48
- # be set or determined during fitting.
46
+ # When an inline box gets placed on a line, the method #fit_wrapped_box is called to fit the
47
+ # wrapped box. This allows the wrapped box to correctly set its width and height which are
48
+ # needed by the TextLayouter algorithm.
49
49
  #
50
- # Fitting of the wrapped box via #fit_wrapped_box needs to be done before accessing any other
51
- # method that uses the wrapped box. For fitting, a frame is used that has the width of the
52
- # wrapped box and its height, or if not set, a practically infinite height. In the latter case
53
- # the height *must* be set during fitting.
50
+ # Note: It is *mandatory* that the wrapped box sets its width and height without relying on the
51
+ # dimensions of the frame's current region.
54
52
  class InlineBox
55
53
 
56
54
  # Creates an InlineBox that wraps a basic Box. All arguments (except +valign+) and the block
@@ -74,7 +72,6 @@ module HexaPDF
74
72
  # The +valign+ argument can be used to specify the vertical alignment of the box relative to
75
73
  # other items in the Line.
76
74
  def initialize(box, valign: :baseline)
77
- raise HexaPDF::Error, "Width of box not set" if box.width == 0
78
75
  @box = box
79
76
  @valign = valign
80
77
  end
@@ -102,8 +99,7 @@ module HexaPDF
102
99
  # Draws the wrapped box. If the box has margins specified, the x and y offsets are correctly
103
100
  # adjusted.
104
101
  def draw(canvas, x, y)
105
- canvas.translate(x - @fit_result.x + box.style.margin.left,
106
- y - @fit_result.y + box.style.margin.bottom) { @fit_result.draw(canvas) }
102
+ @fit_result.draw(canvas, dx: x, dy: y)
107
103
  end
108
104
 
109
105
  # The minimum x-coordinate which is always 0.
@@ -129,19 +125,17 @@ module HexaPDF
129
125
  # Fits the wrapped box.
130
126
  #
131
127
  # If the +frame+ argument is +nil+, a custom frame is created. Otherwise the given +frame+ is
132
- # used for the fitting operation.
133
- def fit_wrapped_box(frame)
134
- frame = if frame
135
- frame.child_frame(0, 0, box.width, box.height == 0 ? 100_000 : box.height)
136
- else
137
- Frame.new(0, 0, box.width, box.height == 0 ? 100_000 : box.height)
138
- end
139
- @fit_result = frame.fit(box)
140
- if !@fit_result.success?
141
- raise HexaPDF::Error, "Box for inline use could not be fit"
142
- elsif box.height > 99_000
143
- raise HexaPDF::Error, "Box for inline use has no valid height set after fitting"
144
- end
128
+ # used for creating an appropriate child frame for the fitting operation.
129
+ #
130
+ # After this operation the caller is responsible for checking the actual width and height of
131
+ # the inline box and whether it really fits.
132
+ def fit_wrapped_box(frame = nil)
133
+ @fit_result = box.fit(100_000, 100_000, frame || Frame.new(0, 0, 100_000, 100_000))
134
+ margin = box.style.margin if box.style.margin?
135
+ @fit_result.x = margin&.left.to_i
136
+ @fit_result.y = margin&.bottom.to_i
137
+ @fit_result.mask = Geom2D::Rectangle(0, 0, @fit_result.x + box.width + margin&.right.to_i,
138
+ @fit_result.y + box.height + margin&.top.to_i)
145
139
  end
146
140
 
147
141
  end
@@ -243,7 +243,7 @@ module HexaPDF
243
243
  break if !box_fitter.success? || height <= 0
244
244
  end
245
245
 
246
- @height = @results.sum(&:height) + (@results.count - 1) * item_spacing + reserved_height
246
+ update_content_height { @results.sum(&:height) + (@results.count - 1) * item_spacing }
247
247
 
248
248
  if @results.size == @children.size && @results.all? {|r| r.box_fitter.success? }
249
249
  fit_result.success!
@@ -316,37 +316,32 @@ module HexaPDF
316
316
  def item_marker_box(document, index)
317
317
  return @marker_type.call(document, self, index) if @marker_type.kind_of?(Proc)
318
318
 
319
- unless (fragment = @item_marker_fragment)
319
+ unless (items = @item_marker_items)
320
320
  marker_style = {
321
- font: style.font? ? style.font : document.fonts.add("Times"),
322
- font_size: style.font_size || 10, fill_color: style.fill_color
321
+ font_size: style.font_size || 10,
322
+ fill_color: style.fill_color,
323
323
  }
324
- fragment = case @marker_type
325
- when :disc
326
- TextFragment.create("•", marker_style)
327
- when :circle
328
- unless marker_style[:font].decode_codepoint("❍".ord).valid?
329
- marker_style[:font] = document.fonts.add("ZapfDingbats")
330
- end
331
- TextFragment.create("❍", **marker_style,
332
- font_size: style.font_size / 2.0,
333
- text_rise: -style.font_size / 1.8)
334
- when :square
335
- unless marker_style[:font].decode_codepoint("■".ord).valid?
336
- marker_style[:font] = document.fonts.add("ZapfDingbats")
337
- end
338
- TextFragment.create("■", **marker_style,
339
- font_size: style.font_size / 2.0,
340
- text_rise: -style.font_size / 1.8)
341
- when :decimal
342
- text = (@start_number + index).to_s << "."
343
- TextFragment.create(text, marker_style)
344
- else
345
- raise HexaPDF::Error, "Unknown list marker type #{@marker_type.inspect}"
346
- end
347
- @item_marker_fragment = fragment unless @marker_type == :decimal
324
+ marker_style[:font] = style.font if style.font?
325
+ items = case @marker_type
326
+ when :disc
327
+ document.layout.text_fragments("•", style: marker_style)
328
+ when :circle
329
+ document.layout.text_fragments("", style: marker_style,
330
+ font_size: style.font_size / 2.0,
331
+ text_rise: -style.font_size / 1.8)
332
+ when :square
333
+ document.layout.text_fragments("■", style: marker_style,
334
+ font_size: style.font_size / 2.0,
335
+ text_rise: -style.font_size / 1.8)
336
+ when :decimal
337
+ text = (@start_number + index).to_s << "."
338
+ document.layout.text_fragments(text, style: marker_style)
339
+ else
340
+ raise HexaPDF::Error, "Unknown list marker type #{@marker_type.inspect}"
341
+ end
342
+ @item_marker_items = items unless @marker_type == :decimal
348
343
  end
349
- TextBox.new(items: [fragment], style: {text_align: :right, padding: [0, 5, 0, 0]})
344
+ TextBox.new(items: items, style: {text_align: :right, padding: [0, 5, 0, 0]})
350
345
  end
351
346
 
352
347
  # Draws the list items onto the canvas at position [x, y].
@@ -41,7 +41,10 @@ require 'hexapdf/layout/frame'
41
41
  module HexaPDF
42
42
  module Layout
43
43
 
44
- # A PageStyle defines the initial look of a page and the placement of one or more frames.
44
+ # A PageStyle defines the dimensions of a page, its initial look, the Frame for object placement
45
+ # and which page style should be used next.
46
+ #
47
+ # This class is used by HexaPDF::Composer to style the individual pages.
45
48
  class PageStyle
46
49
 
47
50
  # The page size.
@@ -65,14 +68,15 @@ module HexaPDF
65
68
  # The callable object is given a canvas and the page style as arguments. It needs to draw the
66
69
  # initial content of the page. Note that the graphics state of the canvas is *not* saved
67
70
  # before executing the template code and restored afterwards. If this is needed, the object
68
- # needs to do it itself.
71
+ # needs to do it itself. The #next_style attribute can optionally be set.
69
72
  #
70
- # Furthermore it should set the #frame and #next_style attributes appropriately, if not done
71
- # beforehand. The #create_frame method can be used for easily creating a rectangular frame.
73
+ # Furthermore, the callable object should set the #frame that defines the area on the page
74
+ # where content should be placed. The #create_frame method can be used for easily creating a
75
+ # rectangular frame.
72
76
  #
73
77
  # Example:
74
78
  #
75
- # page_style.template = lambda do |canvas, style
79
+ # page_style.template = lambda do |canvas, style|
76
80
  # box = canvas.context.box
77
81
  # canvas.fill_color("fd0") do
78
82
  # canvas.rectangle(0, 0, box.width, box.height).fill
@@ -81,22 +85,24 @@ module HexaPDF
81
85
  # end
82
86
  attr_accessor :template
83
87
 
84
- # The HexaPDF::Layout::Frame object that defines the area on the page where content should be
85
- # placed.
88
+ # The Frame object that defines the area for the last page created with #create_page where
89
+ # content should be placed.
86
90
  #
87
- # This can either be set beforehand or during execution of the #template.
88
- #
89
- # If no frame has been set, a frame covering the page except for a default margin on all sides
90
- # is set during #create_page.
91
+ # This value is usually updated during execution of the #template. If the value is not
92
+ # updated, a frame covering the page except for a default margin on all sides is set during
93
+ # #create_page.
91
94
  attr_accessor :frame
92
95
 
93
96
  # Defines the name of the page style that should be used for the next page.
94
97
  #
98
+ # Note that this value can be different each time a new page is created via #create_page.
99
+ #
95
100
  # If this attribute is +nil+ (the default), it means that this style should be used again.
96
101
  attr_accessor :next_style
97
102
 
98
103
  # Creates a new page style instance for the given page size, orientation and next style
99
- # values. If a block is given, it is used as template for defining the initial content.
104
+ # values. If a block is given, it is used as #template for defining the initial content of a
105
+ # page.
100
106
  #
101
107
  # Example:
102
108
  #
@@ -113,14 +119,15 @@ module HexaPDF
113
119
  @next_style = next_style
114
120
  end
115
121
 
116
- # Creates a new page in the given document with this page style and returns it.
122
+ # Creates a new page in the given document using this page style and returns it.
117
123
  #
118
- # If #frame has not been set beforehand or during execution of the #template, a default frame
119
- # covering the whole page except a margin of 36 is created.
124
+ # If the #frame has not changed during execution of the #template, a default frame covering
125
+ # the whole page except a margin of 36 is assigned.
120
126
  def create_page(document)
127
+ frame_before = @frame
121
128
  page = document.pages.create(media_box: page_size, orientation: orientation)
122
129
  template&.call(page.canvas, self)
123
- self.frame ||= create_frame(page, 36)
130
+ self.frame = create_frame(page, 36) if @frame.equal?(frame_before)
124
131
  page
125
132
  end
126
133
 
@@ -1369,8 +1369,8 @@ module HexaPDF
1369
1369
  # composer.text('Text underneath')
1370
1370
  #
1371
1371
  # :fill_frame_horizontal::
1372
- # The mask covers the box including the margin around the box and the space to the left
1373
- # and right in the frame.
1372
+ # The mask covers the box including the margin around the box, the space to the left and
1373
+ # right in the frame and the space to the top of the current region.
1374
1374
  #
1375
1375
  # Examples:
1376
1376
  #
@@ -106,11 +106,6 @@ module HexaPDF
106
106
  true
107
107
  end
108
108
 
109
- # :nodoc:
110
- def draw(canvas, x, y)
111
- super(canvas, x + @x_offset, y)
112
- end
113
-
114
109
  # :nodoc:
115
110
  def empty?
116
111
  super && (!@result || @result.lines.empty?)
@@ -137,7 +132,8 @@ module HexaPDF
137
132
  min_x = [min_x, line.x_offset].min
138
133
  max_x = [max_x, line.x_offset + line.width].max
139
134
  end
140
- @width = (min_x.finite? ? (@x_offset = min_x; max_x - min_x) : 0) + reserved_width
135
+ @width = (min_x.finite? ? max_x - min_x : 0) + reserved_width
136
+ fit_result.x = @x_offset = min_x
141
137
  @height = @initial_height > 0 ? @initial_height : @result.height + reserved_height
142
138
  else
143
139
  @result = @tl.fit(@items, @width - reserved_width, @height - reserved_height,
@@ -71,6 +71,10 @@ module HexaPDF
71
71
  end
72
72
 
73
73
  # Returns +true+ if the PDF file is a linearized file.
74
+ #
75
+ # Note: The method uses heuristics to determine whether a PDF file is linearized. In case of
76
+ # slightly invalid or damaged PDFs that HexaPDF can recover from it is possible that this method
77
+ # returns +true+ even though the PDF isn't actually linearized.
74
78
  def linearized?
75
79
  @linearized ||=
76
80
  begin
@@ -293,7 +297,7 @@ module HexaPDF
293
297
  next
294
298
  elsif type == 'n'
295
299
  if pos == 0 || gen > 65535
296
- maybe_raise("Invalid in use cross-reference entry",
300
+ maybe_raise("Invalid in use cross-reference entry for object number #{oid}",
297
301
  pos: @tokenizer.pos)
298
302
  xref.add_free_entry(oid, gen)
299
303
  else
@@ -97,7 +97,7 @@ module HexaPDF
97
97
  merge_revision = offset
98
98
  end
99
99
 
100
- if merge_revision == offset
100
+ if merge_revision == offset && !revisions.empty?
101
101
  xref_section.merge!(revisions.first.xref_section)
102
102
  offset = trailer[:Prev] # Get possible next offset before overwriting trailer
103
103
  trailer = revisions.first.trailer
@@ -51,6 +51,9 @@ module HexaPDF
51
51
  # normalized to arrays on assignment to ease further processing.
52
52
  class StreamData
53
53
 
54
+ # The source.
55
+ attr_reader :source
56
+
54
57
  # The filter(s) that need to be applied for getting the decoded stream data.
55
58
  attr_reader :filter
56
59
 
@@ -110,9 +113,6 @@ module HexaPDF
110
113
 
111
114
  protected
112
115
 
113
- # The source.
114
- attr_reader :source
115
-
116
116
  # The optional offset into the bytes provided by source.
117
117
  attr_reader :offset
118
118
 
@@ -82,6 +82,7 @@ module HexaPDF
82
82
  # correctable situations are only raised if the return value of calling the object is +true+.
83
83
  def initialize(io, on_correctable_error: nil)
84
84
  @io = io
85
+ @io_chunk = String.new(''.b)
85
86
  @ss = StringScanner.new(''.b)
86
87
  @original_pos = -1
87
88
  @on_correctable_error = on_correctable_error || proc { false }
@@ -439,9 +440,9 @@ module HexaPDF
439
440
  @io.seek(@next_read_pos)
440
441
  return false if @io.eof?
441
442
 
442
- @ss << @io.read(8192)
443
+ @ss << @io.read(8192, @io_chunk)
443
444
  if @ss.pos > 8192 && @ss.string.length > 16384
444
- @ss.string.slice!(0, 8192)
445
+ @ss.string.replace(@ss.string.byteslice(8192..-1))
445
446
  @ss.pos -= 8192
446
447
  @original_pos += 8192
447
448
  end
@@ -65,6 +65,8 @@ module HexaPDF
65
65
  #
66
66
  # == Type Specific Field Flags
67
67
  #
68
+ # See the class description for Field for the general field flags.
69
+ #
68
70
  # :no_toggle_to_off:: Only used with radio buttons fields. If this flag is set, one button
69
71
  # needs to be selected at all times. Otherwise, clicking on the selected
70
72
  # button deselects it.
@@ -51,6 +51,8 @@ module HexaPDF
51
51
  #
52
52
  # == Type Specific Field Flags
53
53
  #
54
+ # See the class description for Field for the general field flags.
55
+ #
54
56
  # :combo:: If set, the field represents a combo box.
55
57
  #
56
58
  # :edit:: If set, the combo box includes an editable text box for entering arbitrary values.
@@ -76,6 +76,8 @@ module HexaPDF
76
76
  #
77
77
  # :no_export:: The field should *not* be exported by a submit-form action.
78
78
  #
79
+ # Also see the class description of the subclasses for additional, type specific field flags.
80
+ #
79
81
  # == Field Type Implementation Notes
80
82
  #
81
83
  # If an AcroForm field type adds additional inheritable dictionary fields, it has to set the
@@ -124,6 +126,8 @@ module HexaPDF
124
126
  #
125
127
  # Returns an array of flag names representing the set bit flags.
126
128
  #
129
+ # See the class description for a list of available flags.
130
+ #
127
131
 
128
132
  ##
129
133
  # :method: flagged?
@@ -133,6 +137,8 @@ module HexaPDF
133
137
  # Returns +true+ if the given flag is set. The argument can either be the flag name or the
134
138
  # bit index.
135
139
  #
140
+ # See the class description for a list of available flags.
141
+ #
136
142
 
137
143
  ##
138
144
  # :method: flag
@@ -142,6 +148,8 @@ module HexaPDF
142
148
  # Sets the given flags, given as flag names or bit indices. If +clear_existing+ is +true+,
143
149
  # all prior flags will be cleared.
144
150
  #
151
+ # See the class description for a list of available flags.
152
+ #
145
153
  bit_field(:flags, {read_only: 0, required: 1, no_export: 2},
146
154
  lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
147
155
  value_getter: "self[:Ff]", value_setter: "self[:Ff]")
@@ -388,13 +388,14 @@ module HexaPDF
388
388
  page_annots = page[:Annots].to_a - to_delete
389
389
  page[:Annots].value.replace(page_annots)
390
390
  end
391
- to_delete.each {|widget| document.delete(widget) }
392
391
 
393
392
  if field[:Parent]
394
393
  field[:Parent][:Kids].delete(field)
395
394
  else
396
395
  self[:Fields].delete(field)
397
396
  end
397
+
398
+ to_delete.each {|widget| document.delete(widget) }
398
399
  document.delete(field)
399
400
  end
400
401
 
@@ -50,6 +50,8 @@ module HexaPDF
50
50
  #
51
51
  # == Type Specific Field Flags
52
52
  #
53
+ # See the class description for Field for the general field flags.
54
+ #
53
55
  # :multiline:: If set, the text field may contain multiple lines.
54
56
  #
55
57
  # :password:: The field is a password field. This changes the behaviour of the PDF reader
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.45.0'
40
+ VERSION = '0.46.0'
41
41
 
42
42
  end