hexapdf 0.45.0 → 0.46.0

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