hexapdf 0.12.1 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +130 -0
  3. data/examples/019-acro_form.rb +41 -4
  4. data/lib/hexapdf/cli/command.rb +4 -2
  5. data/lib/hexapdf/cli/image2pdf.rb +2 -1
  6. data/lib/hexapdf/cli/info.rb +51 -2
  7. data/lib/hexapdf/cli/inspect.rb +30 -8
  8. data/lib/hexapdf/cli/merge.rb +1 -1
  9. data/lib/hexapdf/cli/split.rb +74 -14
  10. data/lib/hexapdf/configuration.rb +15 -0
  11. data/lib/hexapdf/content/graphic_object/arc.rb +3 -3
  12. data/lib/hexapdf/content/parser.rb +1 -1
  13. data/lib/hexapdf/dictionary.rb +9 -6
  14. data/lib/hexapdf/dictionary_fields.rb +1 -9
  15. data/lib/hexapdf/document.rb +41 -16
  16. data/lib/hexapdf/document/files.rb +0 -1
  17. data/lib/hexapdf/encryption/fast_arc4.rb +1 -1
  18. data/lib/hexapdf/encryption/security_handler.rb +1 -0
  19. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -0
  20. data/lib/hexapdf/font/cmap.rb +1 -4
  21. data/lib/hexapdf/font/true_type/subsetter.rb +12 -3
  22. data/lib/hexapdf/font/true_type/table/head.rb +1 -0
  23. data/lib/hexapdf/font/true_type/table/os2.rb +2 -0
  24. data/lib/hexapdf/font/true_type/table/post.rb +15 -10
  25. data/lib/hexapdf/font_loader/from_configuration.rb +2 -2
  26. data/lib/hexapdf/font_loader/from_file.rb +18 -8
  27. data/lib/hexapdf/image_loader/png.rb +3 -2
  28. data/lib/hexapdf/importer.rb +3 -2
  29. data/lib/hexapdf/layout/line.rb +1 -1
  30. data/lib/hexapdf/layout/style.rb +23 -23
  31. data/lib/hexapdf/layout/text_layouter.rb +2 -2
  32. data/lib/hexapdf/layout/text_shaper.rb +3 -2
  33. data/lib/hexapdf/object.rb +52 -25
  34. data/lib/hexapdf/parser.rb +96 -4
  35. data/lib/hexapdf/pdf_array.rb +12 -5
  36. data/lib/hexapdf/revisions.rb +29 -21
  37. data/lib/hexapdf/serializer.rb +34 -8
  38. data/lib/hexapdf/task/optimize.rb +6 -4
  39. data/lib/hexapdf/tokenizer.rb +4 -3
  40. data/lib/hexapdf/type/acro_form/appearance_generator.rb +132 -28
  41. data/lib/hexapdf/type/acro_form/button_field.rb +21 -13
  42. data/lib/hexapdf/type/acro_form/choice_field.rb +68 -14
  43. data/lib/hexapdf/type/acro_form/field.rb +35 -5
  44. data/lib/hexapdf/type/acro_form/form.rb +139 -14
  45. data/lib/hexapdf/type/acro_form/text_field.rb +70 -4
  46. data/lib/hexapdf/type/actions/uri.rb +3 -2
  47. data/lib/hexapdf/type/annotations/widget.rb +3 -4
  48. data/lib/hexapdf/type/catalog.rb +2 -2
  49. data/lib/hexapdf/type/cid_font.rb +1 -1
  50. data/lib/hexapdf/type/file_specification.rb +1 -1
  51. data/lib/hexapdf/type/font.rb +1 -1
  52. data/lib/hexapdf/type/font_simple.rb +4 -2
  53. data/lib/hexapdf/type/font_true_type.rb +6 -2
  54. data/lib/hexapdf/type/font_type0.rb +4 -4
  55. data/lib/hexapdf/type/form.rb +15 -2
  56. data/lib/hexapdf/type/image.rb +2 -2
  57. data/lib/hexapdf/type/page.rb +37 -13
  58. data/lib/hexapdf/type/page_tree_node.rb +29 -5
  59. data/lib/hexapdf/type/resources.rb +1 -0
  60. data/lib/hexapdf/type/trailer.rb +2 -3
  61. data/lib/hexapdf/utils/object_hash.rb +0 -1
  62. data/lib/hexapdf/utils/sorted_tree_node.rb +18 -15
  63. data/lib/hexapdf/version.rb +1 -1
  64. data/test/hexapdf/common_tokenizer_tests.rb +6 -1
  65. data/test/hexapdf/content/graphic_object/test_arc.rb +4 -4
  66. data/test/hexapdf/content/test_canvas.rb +3 -3
  67. data/test/hexapdf/content/test_color_space.rb +1 -1
  68. data/test/hexapdf/encryption/test_aes.rb +4 -4
  69. data/test/hexapdf/encryption/test_standard_security_handler.rb +11 -11
  70. data/test/hexapdf/filter/test_ascii85_decode.rb +1 -1
  71. data/test/hexapdf/filter/test_ascii_hex_decode.rb +1 -1
  72. data/test/hexapdf/font/true_type/table/test_post.rb +1 -1
  73. data/test/hexapdf/font/true_type/test_subsetter.rb +5 -0
  74. data/test/hexapdf/font_loader/test_from_configuration.rb +7 -3
  75. data/test/hexapdf/font_loader/test_from_file.rb +7 -0
  76. data/test/hexapdf/layout/test_style.rb +1 -1
  77. data/test/hexapdf/layout/test_text_layouter.rb +12 -5
  78. data/test/hexapdf/test_configuration.rb +2 -2
  79. data/test/hexapdf/test_dictionary.rb +8 -1
  80. data/test/hexapdf/test_dictionary_fields.rb +2 -2
  81. data/test/hexapdf/test_document.rb +18 -10
  82. data/test/hexapdf/test_object.rb +71 -26
  83. data/test/hexapdf/test_parser.rb +171 -53
  84. data/test/hexapdf/test_pdf_array.rb +8 -1
  85. data/test/hexapdf/test_revisions.rb +35 -0
  86. data/test/hexapdf/test_writer.rb +2 -2
  87. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +296 -38
  88. data/test/hexapdf/type/acro_form/test_button_field.rb +22 -2
  89. data/test/hexapdf/type/acro_form/test_choice_field.rb +92 -9
  90. data/test/hexapdf/type/acro_form/test_field.rb +39 -0
  91. data/test/hexapdf/type/acro_form/test_form.rb +87 -15
  92. data/test/hexapdf/type/acro_form/test_text_field.rb +77 -1
  93. data/test/hexapdf/type/test_font_simple.rb +2 -1
  94. data/test/hexapdf/type/test_font_true_type.rb +6 -0
  95. data/test/hexapdf/type/test_form.rb +26 -1
  96. data/test/hexapdf/type/test_page.rb +45 -7
  97. data/test/hexapdf/type/test_page_tree_node.rb +42 -0
  98. data/test/hexapdf/utils/test_bit_field.rb +2 -0
  99. data/test/hexapdf/utils/test_object_hash.rb +5 -0
  100. data/test/hexapdf/utils/test_sorted_tree_node.rb +10 -9
  101. data/test/test_helper.rb +2 -0
  102. metadata +6 -11
@@ -53,9 +53,10 @@ module HexaPDF
53
53
 
54
54
  def perform_validation #:nodoc:
55
55
  super
56
- unless self[:URI].ascii_only?
56
+ uri = self[:URI]
57
+ if uri && !uri.ascii_only?
57
58
  yield("URIs have to contain ASCII characters only", true)
58
- uri = self[:URI].dup.force_encoding(Encoding::BINARY)
59
+ uri = uri.dup.force_encoding(Encoding::BINARY)
59
60
  uri.encode!(Encoding::US_ASCII, fallback: lambda {|c| "%#{c.ord.to_s(16).upcase}" })
60
61
  self[:URI] = uri
61
62
  end
@@ -307,10 +307,9 @@ module HexaPDF
307
307
  color = [0]
308
308
  if (da = self[:DA] || field[:DA])
309
309
  HexaPDF::Content::Parser.parse(da) do |obj, params|
310
- if obj == :rg || obj == :g || obj == :k
311
- color = params.dup
312
- elsif obj == :Tf
313
- size = params[1]
310
+ case obj
311
+ when :rg, :g, :k then color = params.dup
312
+ when :Tf then size = params[1]
314
313
  end
315
314
  end
316
315
  end
@@ -120,12 +120,12 @@ module HexaPDF
120
120
  private
121
121
 
122
122
  # Ensures that there is a valid page tree.
123
- def perform_validation
123
+ def perform_validation(&block)
124
124
  super
125
125
  unless key?(:Pages)
126
126
  yield("A PDF document needs a page tree", true)
127
127
  value[:Pages] = document.add({Type: :Pages})
128
- value[:Pages].validate {|msg, correctable| yield(msg, correctable) }
128
+ value[:Pages].validate(&block)
129
129
  end
130
130
  end
131
131
 
@@ -95,7 +95,7 @@ module HexaPDF
95
95
  #
96
96
  # See: PDF1.7 s9.7.4.3
97
97
  def widths
98
- document.cache(@data, :widths) do
98
+ cache(:widths) do
99
99
  result = {}
100
100
  index = 0
101
101
  array = self[:W] || []
@@ -220,7 +220,7 @@ module HexaPDF
220
220
 
221
221
  if document.catalog.key?(:Names) && document.catalog[:Names].key?(:EmbeddedFiles)
222
222
  tree = document.catalog[:Names][:EmbeddedFiles]
223
- tree.each_entry.find_all {|_, spec| document.deref(spec) == self }.each do |(name, _)|
223
+ tree.each_entry.find_all {|_, spec| spec == self }.each do |(name, _)|
224
224
  tree.delete_entry(name)
225
225
  end
226
226
  end
@@ -102,7 +102,7 @@ module HexaPDF
102
102
 
103
103
  # Parses and caches the ToUnicode CMap.
104
104
  def to_unicode_cmap
105
- document.cache(@data, :to_unicode_cmap) do
105
+ cache(:to_unicode_cmap) do
106
106
  if key?(:ToUnicode)
107
107
  HexaPDF::Font::CMap.parse(self[:ToUnicode].stream)
108
108
  else
@@ -57,7 +57,7 @@ module HexaPDF
57
57
  #
58
58
  # Note that the encoding is cached internally when accessed the first time.
59
59
  def encoding
60
- document.cache(@data, :encoding) do
60
+ cache(:encoding) do
61
61
  case (val = self[:Encoding])
62
62
  when Symbol
63
63
  encoding = HexaPDF::Font::Encoding.for_name(val)
@@ -170,7 +170,9 @@ module HexaPDF
170
170
  [:FirstChar, :LastChar, :Widths].each do |field|
171
171
  yield("Required field #{field} is not set", false) if self[field].nil?
172
172
  end
173
- if self[:Widths].length != (self[:LastChar] - self[:FirstChar] + 1)
173
+
174
+ if key?(:Widths) && key?(:LastChar) && key?(:FirstChar) &&
175
+ self[:Widths].length != (self[:LastChar] - self[:FirstChar] + 1)
174
176
  yield("Invalid number of entries in field Widths", false)
175
177
  end
176
178
  end
@@ -48,8 +48,12 @@ module HexaPDF
48
48
  private
49
49
 
50
50
  def perform_validation
51
- super
52
- yield("Required field FontDescriptor is not set", false) if self[:FontDescriptor].nil?
51
+ std_font = FontType1::StandardFonts.standard_font?(self[:BaseFont])
52
+ super(ignore_missing_font_fields: std_font)
53
+
54
+ if self[:FontDescriptor].nil? && !std_font
55
+ yield("Required field FontDescriptor is not set", false)
56
+ end
53
57
  end
54
58
 
55
59
  end
@@ -58,8 +58,8 @@ module HexaPDF
58
58
 
59
59
  # Returns the CID font of this type 0 font.
60
60
  def descendant_font
61
- document.cache(@data, :descendant_font) do
62
- document.wrap(document.deref(self[:DescendantFonts][0]))
61
+ cache(:descendant_font) do
62
+ document.wrap(self[:DescendantFonts][0])
63
63
  end
64
64
  end
65
65
 
@@ -116,7 +116,7 @@ module HexaPDF
116
116
  #
117
117
  # Note that the CMap is cached internally when accessed the first time.
118
118
  def cmap
119
- document.cache(@data, :cmap) do
119
+ cache(:cmap) do
120
120
  val = self[:Encoding]
121
121
  if val.kind_of?(Symbol)
122
122
  HexaPDF::Font::CMap.for_name(val.to_s)
@@ -135,7 +135,7 @@ module HexaPDF
135
135
  #
136
136
  # See: PDF1.7 s9.10.2
137
137
  def ucs2_cmap
138
- document.cache(@data, :ucs2_cmap) do
138
+ cache(:ucs2_cmap) do
139
139
  encoding = self[:Encoding]
140
140
  system_info = descendant_font[:CIDSystemInfo]
141
141
  registry = system_info[:Registry]
@@ -94,14 +94,18 @@ module HexaPDF
94
94
 
95
95
  # Replaces the contents of the form XObject with the given string.
96
96
  #
97
+ # This also clears the cache to avoid returning invalid objects.
98
+ #
97
99
  # Note: This is the same as #stream= but here for interface compatibility with Page.
98
100
  def contents=(data)
99
101
  self.stream = data
102
+ clear_cache
100
103
  end
101
104
 
102
105
  # Returns the resource dictionary which is automatically created if it doesn't exist.
103
106
  def resources
104
- self[:Resources] ||= document.wrap({}, type: :XXResources)
107
+ self[:Resources] ||= document.wrap({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]},
108
+ type: :XXResources)
105
109
  end
106
110
 
107
111
  # Processes the content stream of the form XObject with the given processor object.
@@ -126,13 +130,22 @@ module HexaPDF
126
130
  # The canvas object is cached once it is created so that its graphics state is correctly
127
131
  # retained without the need for parsing its contents.
128
132
  #
133
+ # If the bounding box of the form XObject doesn't have its origin at (0, 0), the canvas origin
134
+ # is translated into the bottom left corner so that this detail doesn't matter when using the
135
+ # canvas. This means that the canvas' origin is always at the bottom left corner of the
136
+ # bounding box.
137
+ #
129
138
  # *Note* that a canvas can only be retrieved for initially empty form XObjects!
130
139
  def canvas
131
- document.cache(@data, :canvas) do
140
+ cache(:canvas) do
132
141
  unless stream.empty?
133
142
  raise HexaPDF::Error, "Cannot create a canvas for a form XObjects with contents"
134
143
  end
144
+
135
145
  canvas = Content::Canvas.new(self)
146
+ if box.left != 0 || box.bottom != 0
147
+ canvas.save_graphics_state.translate(box.left, box.bottom)
148
+ end
136
149
  self.stream = canvas.stream_data
137
150
  set_filter(:FlateDecode)
138
151
  canvas
@@ -150,7 +150,7 @@ module HexaPDF
150
150
  color_space, = *self[:ColorSpace]
151
151
  if color_space == :Indexed
152
152
  result.indexed = true
153
- color_space, = *document.deref(self[:ColorSpace][1])
153
+ color_space, = *self[:ColorSpace][1]
154
154
  end
155
155
  case color_space
156
156
  when :DeviceRGB, :CalRGB
@@ -240,7 +240,7 @@ module HexaPDF
240
240
  end
241
241
 
242
242
  if color_type == ImageLoader::PNG::INDEXED
243
- palette_data = document.deref(self[:ColorSpace][3])
243
+ palette_data = self[:ColorSpace][3]
244
244
  palette_data = palette_data.stream unless palette_data.kind_of?(String)
245
245
  palette = ''.b
246
246
  if info.color_space == :rgb
@@ -304,7 +304,7 @@ module HexaPDF
304
304
  def contents
305
305
  Array(self[:Contents]).each_with_object("".b) do |content_stream, content|
306
306
  content << " " unless content.empty?
307
- content << document.deref(content_stream).stream
307
+ content << content_stream.stream
308
308
  end
309
309
  end
310
310
 
@@ -323,10 +323,11 @@ module HexaPDF
323
323
  end
324
324
  end
325
325
 
326
- # Returns the possibly inherited resource dictionary which is automatically created if it
326
+ # Returns the, possibly inherited, resource dictionary which is automatically created if it
327
327
  # doesn't exist.
328
328
  def resources
329
- self[:Resources] ||= document.wrap({}, type: :XXResources)
329
+ self[:Resources] ||= document.wrap({ProcSet: [:PDF, :Text, :ImageB, :ImageC, :ImageI]},
330
+ type: :XXResources)
330
331
  end
331
332
 
332
333
  # Processes the content streams associated with the page with the given processor object.
@@ -344,8 +345,7 @@ module HexaPDF
344
345
  node = self
345
346
  while (parent_node = node[:Parent])
346
347
  parent_node[:Kids].each do |kid|
347
- kid = document.deref(kid)
348
- break if kid.data == node.data
348
+ break if kid == node
349
349
  idx += (kid.type == :Page ? 1 : kid[:Count])
350
350
  end
351
351
  node = parent_node
@@ -353,11 +353,26 @@ module HexaPDF
353
353
  idx
354
354
  end
355
355
 
356
+ # Returns all parent nodes of the page up to the root of the page tree.
357
+ #
358
+ # The direct parent is the first node in the array and the root node the last.
359
+ def ancestor_nodes
360
+ parent = self[:Parent]
361
+ result = [parent]
362
+ result << parent while (parent = parent[:Parent])
363
+ result
364
+ end
365
+
356
366
  # Returns the requested type of canvas for the page.
357
367
  #
358
368
  # The canvas object is cached once it is created so that its graphics state is correctly
359
369
  # retained without the need for parsing its contents.
360
370
  #
371
+ # If the media box of the page doesn't have its origin at (0, 0), the canvas origin is
372
+ # translated into the bottom left corner so that this detail doesn't matter when using the
373
+ # canvas. This means that the canvas' origin is always at the bottom left corner of the media
374
+ # box.
375
+ #
361
376
  # type::
362
377
  # Can either be
363
378
  # * :page for getting the canvas for the page itself (only valid for initially empty pages)
@@ -368,22 +383,31 @@ module HexaPDF
368
383
  raise ArgumentError, "Invalid value for 'type', expected: :page, :underlay or :overlay"
369
384
  end
370
385
  cache_key = "#{type}_canvas".intern
371
- return document.cache(@data, cache_key) if document.cached?(@data, cache_key)
386
+ return cache(cache_key) if cached?(cache_key)
372
387
 
373
388
  if type == :page && key?(:Contents)
374
389
  raise HexaPDF::Error, "Cannot get the canvas for a page with contents"
375
390
  end
376
391
 
392
+ create_canvas = lambda do
393
+ Content::Canvas.new(self).tap do |canvas|
394
+ media_box = box(:media)
395
+ if media_box.left != 0 || media_box.bottom != 0
396
+ canvas.translate(media_box.left, media_box.bottom)
397
+ end
398
+ end
399
+ end
400
+
377
401
  contents = self[:Contents]
378
402
  if contents.nil?
379
- page_canvas = document.cache(@data, :page_canvas, Content::Canvas.new(self))
403
+ page_canvas = cache(:page_canvas, create_canvas.call)
380
404
  self[:Contents] = document.add({Filter: :FlateDecode},
381
405
  stream: page_canvas.stream_data)
382
406
  end
383
407
 
384
408
  if type == :overlay || type == :underlay
385
- underlay_canvas = document.cache(@data, :underlay_canvas, Content::Canvas.new(self))
386
- overlay_canvas = document.cache(@data, :overlay_canvas, Content::Canvas.new(self))
409
+ underlay_canvas = cache(:underlay_canvas, create_canvas.call)
410
+ overlay_canvas = cache(:overlay_canvas, create_canvas.call)
387
411
 
388
412
  stream = HexaPDF::StreamData.new do
389
413
  Fiber.yield(" q ")
@@ -396,18 +420,19 @@ module HexaPDF
396
420
  underlay = document.add({Filter: :FlateDecode}, stream: stream)
397
421
 
398
422
  stream = HexaPDF::StreamData.new do
399
- Fiber.yield(" Q ")
423
+ Fiber.yield(" Q q ")
400
424
  fiber = overlay_canvas.stream_data.fiber
401
425
  while fiber.alive? && (data = fiber.resume)
402
426
  Fiber.yield(data)
403
427
  end
428
+ " Q "
404
429
  end
405
430
  overlay = document.add({Filter: :FlateDecode}, stream: stream)
406
431
 
407
432
  self[:Contents] = [underlay, *self[:Contents], overlay]
408
433
  end
409
434
 
410
- document.cache(@data, cache_key)
435
+ cache(cache_key)
411
436
  end
412
437
 
413
438
  # Creates a Form XObject from the page's dictionary and contents for the given PDF document.
@@ -448,8 +473,7 @@ module HexaPDF
448
473
  REQUIRED_INHERITABLE_FIELDS.each do |name|
449
474
  next if self[name]
450
475
  yield("Inheritable page field #{name} not set", name == :Resources)
451
- self[:Resources] = {}
452
- self[:Resources].validate(&block)
476
+ resources.validate(&block) if name == :Ressources
453
477
  end
454
478
  end
455
479
 
@@ -172,11 +172,10 @@ module HexaPDF
172
172
  return unless page && !page.null? && page[:Parent]
173
173
 
174
174
  parent = page[:Parent]
175
- index = parent[:Kids].index {|kid| kid.data == page.data }
175
+ index = parent[:Kids].index(page)
176
176
 
177
177
  if index
178
- ancestors = [parent]
179
- ancestors << parent while (parent = parent[:Parent])
178
+ ancestors = page.ancestor_nodes
180
179
  return nil unless ancestors.include?(self)
181
180
 
182
181
  page[:Parent][:Kids].delete_at(index)
@@ -188,6 +187,31 @@ module HexaPDF
188
187
  end
189
188
  end
190
189
 
190
+ # :call-seq:
191
+ # pages.move_page(page, to_index)
192
+ # pages.move_page(index, to_index)
193
+ #
194
+ # Moves the given page or the page at the position specified by the zero-based index to the
195
+ # +to_index+ position.
196
+ #
197
+ # If the page that should be moved, doesn't exist or is invalid, an error is raised.
198
+ #
199
+ # Negative indices count backwards from the end, i.e. -1 is the last page. When using a
200
+ # negative index, the page will be moved after that element. So using an index of -1 will
201
+ # move the page after the last page.
202
+ def move_page(page, to_index)
203
+ page = self.page(page) if page.kind_of?(Integer)
204
+ if page.nil? || page.null? || !page[:Parent] ||
205
+ !(ancestors = page.ancestor_nodes).include?(self)
206
+ raise HexaPDF::Error, "The page to be moved doesn't exist in this page tree"
207
+ end
208
+
209
+ parent = page[:Parent]
210
+ insert_page(to_index, page)
211
+ ancestors.each {|node| node[:Count] -= 1 }
212
+ parent[:Kids].delete(page)
213
+ end
214
+
191
215
  # :call-seq:
192
216
  # pages.each_page {|page| block } -> pages
193
217
  # pages.each_page -> Enumerator
@@ -222,7 +246,7 @@ module HexaPDF
222
246
  # Ensures that the /Count and /Parent fields of the whole page tree are set up correctly and
223
247
  # that there is at least one page node. This is therefore only done for the root node of the
224
248
  # page tree!
225
- def perform_validation
249
+ def perform_validation(&block)
226
250
  super
227
251
  return if key?(:Parent)
228
252
 
@@ -255,7 +279,7 @@ module HexaPDF
255
279
 
256
280
  if self[:Count] == 0
257
281
  yield("A PDF document needs at least one page", true)
258
- add_page.validate {|msg, correctable| yield(msg, correctable) }
282
+ add_page.validate(&block)
259
283
  end
260
284
  end
261
285
 
@@ -219,6 +219,7 @@ module HexaPDF
219
219
  super
220
220
  val = self[:ProcSet]
221
221
  if !val
222
+ yield("No procedure set specified", true)
222
223
  self[:ProcSet] = [:PDF, :Text, :ImageB, :ImageC, :ImageI]
223
224
  else
224
225
  val.reject! do |name|
@@ -97,7 +97,7 @@ module HexaPDF
97
97
  private
98
98
 
99
99
  # Validates the trailer.
100
- def perform_validation
100
+ def perform_validation(&block)
101
101
  super
102
102
  unless value[:ID]
103
103
  msg = if value[:Encrypt]
@@ -111,8 +111,7 @@ module HexaPDF
111
111
 
112
112
  unless value[:Root]
113
113
  yield("A PDF document must have a Catalog dictionary", true)
114
- value[:Root] = document.add({Type: :Catalog})
115
- value[:Root].validate {|message, correctable| yield(message, correctable) }
114
+ catalog.validate(&block)
116
115
  end
117
116
 
118
117
  if value[:Encrypt] && (!document.security_handler ||
@@ -125,7 +125,6 @@ module HexaPDF
125
125
  def oids
126
126
  @oids.keys
127
127
  end
128
- private :oids
129
128
 
130
129
  end
131
130
 
@@ -143,12 +143,12 @@ module HexaPDF
143
143
  stack.reverse_each.inject do |nested_node, node|
144
144
  if (!nested_node[container_name] || nested_node[container_name].empty?) &&
145
145
  (!nested_node[:Kids] || nested_node[:Kids].empty?)
146
- node[:Kids].delete_at(node[:Kids].index {|n| document.deref(n) == nested_node })
146
+ node[:Kids].delete(nested_node)
147
147
  document.delete(nested_node)
148
148
  end
149
149
  if !node[:Kids].empty? && node[:Limits]
150
- node[:Limits][0] = document.deref(node[:Kids][0])[:Limits][0]
151
- node[:Limits][1] = document.deref(node[:Kids][-1])[:Limits][1]
150
+ node[:Limits][0] = node[:Kids][0][:Limits][0]
151
+ node[:Limits][1] = node[:Kids][-1][:Limits][1]
152
152
  end
153
153
  node
154
154
  end
@@ -167,11 +167,11 @@ module HexaPDF
167
167
  if node.key?(container_name)
168
168
  index = find_in_leaf_node(node[container_name], key)
169
169
  if node[container_name][index] == key
170
- result = document.deref(node[container_name][index + 1])
170
+ result = node[container_name][index + 1]
171
171
  end
172
172
  elsif node.key?(:Kids)
173
173
  index = find_in_intermediate_node(node[:Kids], key)
174
- node = document.deref(node[:Kids][index])
174
+ node = node[:Kids][index]
175
175
  break unless key >= node[:Limits][0] && key <= node[:Limits][1]
176
176
  else
177
177
  break
@@ -192,12 +192,12 @@ module HexaPDF
192
192
  container_name = leaf_node_container_name
193
193
  stack = [self]
194
194
  until stack.empty?
195
- node = document.deref(stack.pop)
195
+ node = stack.pop
196
196
  if node.key?(container_name)
197
197
  data = node[container_name]
198
198
  index = 0
199
199
  while index < data.length
200
- yield(data[index], document.deref(data[index + 1]))
200
+ yield(data[index], data[index + 1])
201
201
  index += 2
202
202
  end
203
203
  elsif node.key?(:Kids)
@@ -215,7 +215,7 @@ module HexaPDF
215
215
  def path_to_key(node, key, stack)
216
216
  return unless node.key?(:Kids)
217
217
  index = find_in_intermediate_node(node[:Kids], key)
218
- stack << document.deref(node[:Kids][index])
218
+ stack << node[:Kids][index]
219
219
  path_to_key(stack.last, key, stack)
220
220
  end
221
221
 
@@ -226,7 +226,7 @@ module HexaPDF
226
226
  right = array.length - 1
227
227
  while left < right
228
228
  mid = (left + right) / 2
229
- limits = document.deref(array[mid])[:Limits]
229
+ limits = array[mid][:Limits]
230
230
  if limits[1] < key
231
231
  left = mid + 1
232
232
  elsif limits[0] > key
@@ -295,7 +295,7 @@ module HexaPDF
295
295
  node[container_name] = leaf_node[container_name].slice!(split_point..-1)
296
296
  node[:Limits] = node[container_name].values_at(0, -2)
297
297
  leaf_node[:Limits][1] = leaf_node[container_name][-2]
298
- index = 1 + parent[:Kids].index {|o| document.deref(o) == leaf_node }
298
+ index = 1 + parent[:Kids].index {|o| o == leaf_node }
299
299
  parent[:Kids].insert(index, node)
300
300
  end
301
301
  end
@@ -307,10 +307,10 @@ module HexaPDF
307
307
 
308
308
  # All kids entries must be indirect objects
309
309
  if key?(:Kids)
310
- self[:Kids].each do |kid|
311
- unless (kid.kind_of?(HexaPDF::Object) && kid.indirect?) ||
312
- kid.kind_of?(HexaPDF::Reference)
313
- yield("Child entries of sorted tree nodes must be indirect objects", false)
310
+ self[:Kids].each_with_index do |kid, index|
311
+ unless kid.kind_of?(HexaPDF::Object) && kid.indirect?
312
+ yield("Child entries of sorted tree nodes must be indirect objects", true)
313
+ value[:Kids][index] = document.add(kid)
314
314
  end
315
315
  end
316
316
  end
@@ -321,15 +321,18 @@ module HexaPDF
321
321
  container = self[container_name]
322
322
  if container.length.odd?
323
323
  yield("Sorted tree leaf node contains odd number of entries", false)
324
+ return
324
325
  end
325
326
  index = 0
326
327
  old = nil
327
328
  while index < container.length
328
- key = document.unwrap(container[index])
329
+ key = container[index]
329
330
  if !key.kind_of?(key_type)
330
331
  yield("A key must be a #{key_type} object, not a #{key.class}", false)
332
+ return
331
333
  elsif old && old > key
332
334
  yield("Sorted tree leaf node entries are not correctly sorted", false)
335
+ return
333
336
  end
334
337
  old = key
335
338
  index += 2