origami 2.0.0 → 2.0.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/bin/gui/config.rb +2 -1
  4. data/bin/gui/file.rb +118 -240
  5. data/bin/gui/gtkhex.rb +5 -5
  6. data/bin/gui/hexview.rb +20 -16
  7. data/bin/gui/imgview.rb +1 -1
  8. data/bin/gui/menu.rb +138 -158
  9. data/bin/gui/properties.rb +46 -48
  10. data/bin/gui/signing.rb +183 -214
  11. data/bin/gui/textview.rb +1 -1
  12. data/bin/gui/treeview.rb +13 -7
  13. data/bin/gui/walker.rb +102 -71
  14. data/bin/gui/xrefs.rb +1 -1
  15. data/bin/pdf2ruby +3 -3
  16. data/bin/pdfcop +18 -11
  17. data/bin/pdfextract +14 -5
  18. data/bin/pdfmetadata +3 -3
  19. data/bin/shell/console.rb +8 -8
  20. data/bin/shell/hexdump.rb +4 -4
  21. data/examples/attachments/nested_document.rb +1 -1
  22. data/examples/javascript/hello_world.rb +3 -3
  23. data/lib/origami.rb +0 -1
  24. data/lib/origami/acroform.rb +3 -3
  25. data/lib/origami/array.rb +1 -3
  26. data/lib/origami/boolean.rb +1 -3
  27. data/lib/origami/catalog.rb +3 -9
  28. data/lib/origami/destinations.rb +2 -2
  29. data/lib/origami/dictionary.rb +15 -29
  30. data/lib/origami/encryption.rb +334 -692
  31. data/lib/origami/extensions/fdf.rb +3 -2
  32. data/lib/origami/extensions/ppklite.rb +5 -9
  33. data/lib/origami/filespec.rb +2 -2
  34. data/lib/origami/filters.rb +54 -36
  35. data/lib/origami/filters/ascii.rb +67 -49
  36. data/lib/origami/filters/ccitt.rb +4 -236
  37. data/lib/origami/filters/ccitt/tables.rb +267 -0
  38. data/lib/origami/filters/crypt.rb +1 -1
  39. data/lib/origami/filters/dct.rb +0 -1
  40. data/lib/origami/filters/flate.rb +3 -43
  41. data/lib/origami/filters/lzw.rb +62 -99
  42. data/lib/origami/filters/predictors.rb +135 -105
  43. data/lib/origami/filters/runlength.rb +34 -22
  44. data/lib/origami/graphics.rb +2 -2
  45. data/lib/origami/graphics/colors.rb +89 -63
  46. data/lib/origami/graphics/path.rb +14 -14
  47. data/lib/origami/graphics/patterns.rb +31 -33
  48. data/lib/origami/graphics/render.rb +0 -1
  49. data/lib/origami/graphics/state.rb +9 -9
  50. data/lib/origami/graphics/text.rb +17 -17
  51. data/lib/origami/graphics/xobject.rb +102 -92
  52. data/lib/origami/javascript.rb +91 -68
  53. data/lib/origami/linearization.rb +22 -20
  54. data/lib/origami/metadata.rb +1 -1
  55. data/lib/origami/name.rb +1 -3
  56. data/lib/origami/null.rb +1 -3
  57. data/lib/origami/numeric.rb +3 -13
  58. data/lib/origami/object.rb +100 -72
  59. data/lib/origami/page.rb +24 -28
  60. data/lib/origami/parser.rb +34 -51
  61. data/lib/origami/parsers/fdf.rb +2 -2
  62. data/lib/origami/parsers/pdf.rb +41 -18
  63. data/lib/origami/parsers/pdf/lazy.rb +83 -46
  64. data/lib/origami/parsers/pdf/linear.rb +19 -10
  65. data/lib/origami/parsers/ppklite.rb +1 -1
  66. data/lib/origami/pdf.rb +150 -206
  67. data/lib/origami/reference.rb +4 -6
  68. data/lib/origami/signature.rb +76 -48
  69. data/lib/origami/stream.rb +69 -63
  70. data/lib/origami/string.rb +2 -19
  71. data/lib/origami/trailer.rb +25 -22
  72. data/lib/origami/version.rb +1 -1
  73. data/lib/origami/xfa.rb +6 -4
  74. data/lib/origami/xreftable.rb +29 -29
  75. data/test/test_annotations.rb +16 -38
  76. data/test/test_pdf_attachment.rb +1 -1
  77. data/test/test_pdf_parse.rb +1 -1
  78. data/test/test_xrefs.rb +2 -2
  79. metadata +4 -4
  80. data/lib/origami/export.rb +0 -247
@@ -42,7 +42,7 @@ module Origami
42
42
  @refno, @refgen = refno, refgen
43
43
  end
44
44
 
45
- def self.parse(stream, parser = nil) #:nodoc:
45
+ def self.parse(stream, _parser = nil) #:nodoc:
46
46
  offset = stream.pos
47
47
 
48
48
  if stream.scan(@@regexp).nil?
@@ -68,7 +68,7 @@ module Origami
68
68
  target = doc.get_object(self)
69
69
 
70
70
  if target.nil? and not Origami::OPTIONS[:ignore_bad_references]
71
- raise InvalidReferenceError, "Cannot resolve reference : #{self.to_s}"
71
+ raise InvalidReferenceError, "Cannot resolve reference : #{self}"
72
72
  end
73
73
 
74
74
  target or Null.new
@@ -104,13 +104,11 @@ module Origami
104
104
  end
105
105
 
106
106
  #
107
- # Returns self.
107
+ # Returns the referenced object value.
108
108
  #
109
109
  def value
110
- self
110
+ self.solve.value
111
111
  end
112
-
113
- def self.native_type ; Reference end
114
112
  end
115
113
 
116
114
  end
@@ -18,12 +18,7 @@
18
18
 
19
19
  =end
20
20
 
21
- begin
22
- require 'openssl' if Origami::OPTIONS[:use_openssl]
23
- rescue LoadError
24
- Origami::OPTIONS[:use_openssl] = false
25
- end
26
-
21
+ require 'openssl'
27
22
  require 'digest/sha1'
28
23
 
29
24
  module Origami
@@ -38,48 +33,29 @@ module Origami
38
33
  # If no argument is passed, embedded certificates are treated as trusted.
39
34
  #
40
35
  def verify(trusted_certs: [])
41
- unless Origami::OPTIONS[:use_openssl]
42
- fail "OpenSSL is not present or has been disabled."
43
- end
44
-
45
36
  digsig = self.signature
37
+ digsig = digsig.cast_to(Signature::DigitalSignature) unless digsig.is_a?(Signature::DigitalSignature)
46
38
 
47
39
  unless digsig[:Contents].is_a?(String)
48
40
  raise SignatureError, "Invalid digital signature contents"
49
41
  end
50
42
 
51
43
  store = OpenSSL::X509::Store.new
52
- trusted_certs.each do |ca| store.add_cert(ca) end
44
+ trusted_certs.each { |ca| store.add_cert(ca) }
53
45
  flags = 0
54
46
  flags |= OpenSSL::PKCS7::NOVERIFY if trusted_certs.empty?
55
47
 
56
- stream = StringScanner.new(self.original_data)
57
- stream.pos = digsig[:Contents].file_offset
58
- Object.typeof(stream).parse(stream)
59
- endofsig_offset = stream.pos
60
- stream.terminate
61
-
62
- s1,l1,s2,l2 = digsig.ByteRange
63
- if s1.value != 0 or
64
- (s2.value + l2.value) != self.original_data.size or
65
- (s1.value + l1.value) != digsig[:Contents].file_offset or
66
- s2.value != endofsig_offset
67
-
68
- raise SignatureError, "Invalid signature byte range"
69
- end
48
+ data = extract_signed_data(digsig)
49
+ signature = digsig[:Contents]
50
+ subfilter = digsig.SubFilter.value
70
51
 
71
- data = self.original_data[s1,l1] + self.original_data[s2,l2]
52
+ case subfilter
53
+ when Signature::DigitalSignature::PKCS7_DETACHED
54
+ Signature.verify_pkcs7_detached_signature(data, signature, store, flags)
72
55
 
73
- case digsig.SubFilter.value.to_s
74
- when 'adbe.pkcs7.detached'
75
- flags |= OpenSSL::PKCS7::DETACHED
76
- p7 = OpenSSL::PKCS7.new(digsig[:Contents].value)
77
- raise SignatureError, "Not a PKCS7 detached signature" unless p7.detached?
78
- p7.verify([], store, data, flags)
79
56
 
80
- when 'adbe.pkcs7.sha1'
81
- p7 = OpenSSL::PKCS7.new(digsig[:Contents].value)
82
- p7.verify([], store, nil, flags) and p7.data == Digest::SHA1.digest(data)
57
+ when Signature::DigitalSignature::PKCS7_SHA1
58
+ Signature.verify_pkcs7_sha1_signature(data, signature, store, flags)
83
59
 
84
60
  else
85
61
  raise NotImplementedError, "Unsupported method #{digsig.SubFilter}"
@@ -107,10 +83,6 @@ module Origami
107
83
  contact: nil,
108
84
  reason: nil)
109
85
 
110
- unless Origami::OPTIONS[:use_openssl]
111
- fail "OpenSSL is not present or has been disabled."
112
- end
113
-
114
86
  unless certificate.is_a?(OpenSSL::X509::Certificate)
115
87
  raise TypeError, "A OpenSSL::X509::Certificate object must be passed."
116
88
  end
@@ -140,7 +112,7 @@ module Origami
140
112
  end
141
113
 
142
114
  when 'adbe.pkcs7.sha1'
143
- signfield_size = -> (crt, pkey, certs) do
115
+ signfield_size = -> (crt, pkey, certs) do
144
116
  OpenSSL::PKCS7.sign(
145
117
  crt,
146
118
  pkey,
@@ -151,7 +123,7 @@ module Origami
151
123
  end
152
124
 
153
125
  when 'adbe.x509.rsa_sha1'
154
- signfield_size = -> (crt, pkey, certs) do
126
+ signfield_size = -> (_crt, pkey, _certs) do
155
127
  pkey.private_encrypt(
156
128
  Digest::SHA1.digest('')
157
129
  ).size
@@ -172,7 +144,7 @@ module Origami
172
144
  annotation.V = digsig
173
145
  add_fields(annotation)
174
146
  self.Catalog.AcroForm.SigFlags =
175
- InteractiveForm::SigFlags::SIGNATURESEXIST | InteractiveForm::SigFlags::APPENDONLY
147
+ InteractiveForm::SigFlags::SIGNATURES_EXIST | InteractiveForm::SigFlags::APPEND_ONLY
176
148
 
177
149
  digsig.Type = :Sig #:nodoc:
178
150
  digsig.Contents = HexaString.new("\x00" * signfield_size[certificate, key, ca]) #:nodoc:
@@ -263,8 +235,8 @@ module Origami
263
235
  def signed?
264
236
  begin
265
237
  self.Catalog.AcroForm.is_a?(Dictionary) and
266
- self.Catalog.AcroForm.has_key?(:SigFlags) and
267
- (self.Catalog.AcroForm.SigFlags & InteractiveForm::SigFlags::SIGNATURESEXIST != 0)
238
+ self.Catalog.AcroForm.SigFlags.is_a?(Integer) and
239
+ (self.Catalog.AcroForm.SigFlags & InteractiveForm::SigFlags::SIGNATURES_EXIST != 0)
268
240
  rescue InvalidReferenceError
269
241
  false
270
242
  end
@@ -275,9 +247,6 @@ module Origami
275
247
  # _rights_:: list of rights defined in UsageRights::Rights
276
248
  #
277
249
  def enable_usage_rights(cert, pkey, *rights)
278
- unless Origami::OPTIONS[:use_openssl]
279
- fail "OpenSSL is not present or has been disabled."
280
- end
281
250
 
282
251
  signfield_size = -> (crt, key, ca) do
283
252
  OpenSSL::PKCS7.sign(
@@ -301,7 +270,7 @@ module Origami
301
270
  digsig = Signature::DigitalSignature.new.set_indirect(true)
302
271
 
303
272
  self.Catalog.AcroForm ||= InteractiveForm.new
304
- #self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::APPENDONLY
273
+ #self.Catalog.AcroForm.SigFlags = InteractiveForm::SigFlags::APPEND_ONLY
305
274
 
306
275
  digsig.Type = :Sig #:nodoc:
307
276
  digsig.Contents = HexaString.new("\x00" * signfield_size[certificate, key, []]) #:nodoc:
@@ -390,6 +359,34 @@ module Origami
390
359
 
391
360
  raise SignatureError, "Cannot find digital signature"
392
361
  end
362
+
363
+ private
364
+
365
+ #
366
+ # Verifies the ByteRange field of a digital signature and returned the signed data.
367
+ #
368
+ def extract_signed_data(digsig)
369
+ # Computes the boundaries of the Contents field.
370
+ start_sig = digsig[:Contents].file_offset
371
+
372
+ stream = StringScanner.new(self.original_data)
373
+ stream.pos = digsig[:Contents].file_offset
374
+ Object.typeof(stream).parse(stream)
375
+ end_sig = stream.pos
376
+ stream.terminate
377
+
378
+ r1, r2 = digsig.ranges
379
+ if r1.begin != 0 or
380
+ r2.end != self.original_data.size or
381
+ r1.end != start_sig or
382
+ r2.begin != end_sig
383
+
384
+ raise SignatureError, "Invalid signature byte range"
385
+ end
386
+
387
+ self.original_data[r1] + self.original_data[r2]
388
+ end
389
+
393
390
  end
394
391
 
395
392
  class Perms < Dictionary
@@ -402,6 +399,21 @@ module Origami
402
399
 
403
400
  module Signature
404
401
 
402
+ # Verifies a PKCS7 detached signature.
403
+ def self.verify_pkcs7_detached_signature(data, signature, store, flags)
404
+ pkcs7 = OpenSSL::PKCS7.new(signature)
405
+ raise SignatureError, "Not a PKCS7 detached signature" unless pkcs7.detached?
406
+
407
+ flags |= OpenSSL::PKCS7::DETACHED
408
+ pkcs7.verify([], store, data, flags)
409
+ end
410
+
411
+ # Verifies a PKCS7-SHA1 signature.
412
+ def self.verify_pkcs7_sha1_signature(data, signature, store, flags)
413
+ pkcs7 = OpenSSL::PKCS7.new(signature)
414
+ pkcs7.verify([], store, nil, flags) and pkcs7.data == Digest::SHA1.digest(data)
415
+ end
416
+
405
417
  #
406
418
  # Class representing a signature which can be embedded in DigitalSignature dictionary.
407
419
  # It must be a direct object.
@@ -494,6 +506,10 @@ module Origami
494
506
  class DigitalSignature < Dictionary
495
507
  include StandardObject
496
508
 
509
+ PKCS1_RSA_SHA1 = :"adbe.x509.rsa_sha1"
510
+ PKCS7_SHA1 = :"adbe.pkcs7.sha1"
511
+ PKCS7_DETACHED = :"adbe.pkcs7.detached"
512
+
497
513
  field :Type, :Type => Name, :Default => :Sig
498
514
  field :Filter, :Type => Name, :Default => :"Adobe.PPKLite", :Required => true
499
515
  field :SubFilter, :Type => Name
@@ -537,6 +553,18 @@ module Origami
537
553
  output(content)
538
554
  end
539
555
 
556
+ def ranges
557
+ byte_range = self.ByteRange
558
+
559
+ unless byte_range.is_a?(Array) and byte_range.length == 4 and byte_range.all? {|i| i.is_a?(Integer) }
560
+ raise SignatureError, "Invalid ByteRange field value"
561
+ end
562
+
563
+ byte_range.map(&:to_i).each_slice(2).map do |start, length|
564
+ (start...start + length)
565
+ end
566
+ end
567
+
540
568
  def signature_offset #:nodoc:
541
569
  indent, tab = 1, "\t"
542
570
  content = "#{no} #{generation} obj" + EOL + TOKENS.first + EOL
@@ -35,14 +35,16 @@ module Origami
35
35
  class Stream
36
36
  include Origami::Object
37
37
  include StandardObject
38
+ include FieldAccessor
38
39
  using TypeConversion
39
40
 
40
- TOKENS = [ "stream" + WHITECHARS_NORET + "\\r?\\n", "endstream" ] #:nodoc:
41
+ TOKENS = [ "stream" + WHITECHARS_NORET + "(\\r\\n|\\r|\\n)" , "endstream" ] #:nodoc:
41
42
 
42
43
  @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
43
44
  @@regexp_close = Regexp.new(TOKENS.last)
44
45
 
45
- @@cast_fingerprints = {}
46
+ @@type_signatures = {}
47
+ @@type_keys = []
46
48
 
47
49
  #
48
50
  # Actually only 5 first ones are implemented,
@@ -139,9 +141,9 @@ module Origami
139
141
 
140
142
  stm =
141
143
  if Origami::OPTIONS[:enable_type_guessing]
142
- self.guess_type(dictionary).new('', dictionary.to_h)
144
+ self.guess_type(dictionary).new('', dictionary)
143
145
  else
144
- Stream.new('', dictionary.to_h)
146
+ Stream.new('', dictionary)
145
147
  end
146
148
 
147
149
  raw_data.chomp!(TOKENS.last)
@@ -161,20 +163,24 @@ module Origami
161
163
  stm
162
164
  end
163
165
 
164
- def self.add_type_info(typeclass, key, value) #:nodoc:
165
- if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Stream and
166
- @@cast_fingerprints.has_key?(typeclass.superclass)
167
- @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
166
+ def self.add_type_signature(key, value) #:nodoc:
167
+ key, value = key.to_o, value.to_o
168
+
169
+ # Inherit the superclass type information.
170
+ if not @@type_signatures.key?(self) and @@type_signatures.key?(self.superclass)
171
+ @@type_signatures[self] = @@type_signatures[self.superclass].dup
168
172
  end
169
173
 
170
- @@cast_fingerprints[typeclass] ||= {}
171
- @@cast_fingerprints[typeclass][key.to_o] = value.to_o
174
+ @@type_signatures[self] ||= {}
175
+ @@type_signatures[self][key] = value
176
+
177
+ @@type_keys.push(key) unless @@type_keys.include?(key)
172
178
  end
173
179
 
174
180
  def self.guess_type(hash) #:nodoc:
175
181
  best_type = self
176
182
 
177
- @@cast_fingerprints.each_pair do |klass, keys|
183
+ @@type_signatures.each_pair do |klass, keys|
178
184
  next unless klass < best_type
179
185
 
180
186
  best_type = klass if keys.all? { |k,v| hash[k] == v }
@@ -223,12 +229,11 @@ module Origami
223
229
  def set_predictor(predictor, colors: 1, bitspercomponent: 8, columns: 1)
224
230
  filters = self.filters
225
231
 
226
- unless filters.include?(:FlateDecode) or filters.include?(:LZWDecode)
232
+ layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
233
+ if layer.nil?
227
234
  raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
228
235
  end
229
236
 
230
- layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
231
-
232
237
  params = Filter::LZW::DecodeParms.new
233
238
  params[:Predictor] = predictor
234
239
  params[:Colors] = colors if colors != 1
@@ -243,7 +248,7 @@ module Origami
243
248
  def cast_to(type, _parser = nil)
244
249
  super(type)
245
250
 
246
- cast = type.new("", self.dictionary.to_h)
251
+ cast = type.new("", self.dictionary.copy)
247
252
  cast.encoded_data = self.encoded_data.dup
248
253
  cast.no, cast.generation = self.no, self.generation
249
254
  cast.set_indirect(true)
@@ -261,7 +266,7 @@ module Origami
261
266
  # Returns the uncompressed stream content.
262
267
  #
263
268
  def data
264
- self.decode! unless self.decoded?
269
+ self.decode! unless decoded?
265
270
 
266
271
  @data
267
272
  end
@@ -281,7 +286,7 @@ module Origami
281
286
  # Returns the raw compressed stream content.
282
287
  #
283
288
  def encoded_data
284
- self.encode! unless self.encoded?
289
+ self.encode! unless encoded?
285
290
 
286
291
  @encoded_data
287
292
  end
@@ -407,19 +412,8 @@ module Origami
407
412
  end
408
413
  alias has_key? key?
409
414
 
410
- def self.native_type ; Stream end
411
-
412
415
  private
413
416
 
414
- def method_missing(field, *args) #:nodoc:
415
- if field.to_s[-1,1] == '='
416
- self[field.to_s[0..-2].to_sym] = args.first
417
- else
418
- obj = self[field];
419
- obj.is_a?(Reference) ? obj.solve : obj
420
- end
421
- end
422
-
423
417
  def decoded? #:nodoc:
424
418
  not @data.nil?
425
419
  end
@@ -534,7 +528,7 @@ module Origami
534
528
  end
535
529
 
536
530
  def pre_build #:nodoc:
537
- load! if @objects.nil?
531
+ load!
538
532
 
539
533
  prolog = ""
540
534
  data = ""
@@ -545,7 +539,7 @@ module Origami
545
539
  obj.objstm_offset = objoff
546
540
 
547
541
  prolog << "#{num} #{objoff} "
548
- objdata = "#{obj.to_s} "
542
+ objdata = "#{obj} "
549
543
 
550
544
  objoff += objdata.size
551
545
  data << objdata
@@ -580,23 +574,14 @@ module Origami
580
574
  end
581
575
 
582
576
  # The object already belongs to a document.
583
- unless (obj_doc = object.document).nil?
584
- # Remove the previous instance if the object is indirect to avoid duplicates.
585
- if obj_doc.equal?(@document)
586
- @document.delete_object(object.reference) if object.indirect?
587
- else
588
- object = object.export
589
- end
577
+ unless object.document.nil?
578
+ object = import_object_from_document(object)
590
579
  end
591
580
 
592
- load! if @objects.nil?
581
+ load!
593
582
 
594
583
  object.no, object.generation = @document.allocate_new_object_number if object.no == 0
595
-
596
- object.set_indirect(true) # object is indirect
597
- object.parent = self # set this stream as the parent
598
- object.set_document(@document) # indirect objects need pdf information
599
- @objects[object.no] = object
584
+ store_object(object)
600
585
 
601
586
  Reference.new(object.no, 0)
602
587
  end
@@ -606,7 +591,7 @@ module Origami
606
591
  # Deletes Object _no_.
607
592
  #
608
593
  def delete(no)
609
- load! if @objects.nil?
594
+ load!
610
595
 
611
596
  @objects.delete(no)
612
597
  end
@@ -615,14 +600,7 @@ module Origami
615
600
  # Returns the index of Object _no_.
616
601
  #
617
602
  def index(no)
618
- ind = 0
619
- @objects.to_a.sort.each do |num, obj|
620
- return ind if num == no
621
-
622
- ind = ind + 1
623
- end
624
-
625
- nil
603
+ @objects.to_a.sort.index { |num, _| num == no }
626
604
  end
627
605
 
628
606
  #
@@ -630,7 +608,7 @@ module Origami
630
608
  # _no_:: The Object number.
631
609
  #
632
610
  def extract(no)
633
- load! if @objects.nil?
611
+ load!
634
612
 
635
613
  @objects[no]
636
614
  end
@@ -640,7 +618,7 @@ module Origami
640
618
  # _index_:: The Object index in the ObjectStream.
641
619
  #
642
620
  def extract_by_index(index)
643
- load! if @objects.nil?
621
+ load!
644
622
 
645
623
  raise TypeError, "index must be an integer" unless index.is_a?(::Integer)
646
624
  raise IndexError, "index #{index} out of range" if index < 0 or index >= @objects.size
@@ -653,7 +631,7 @@ module Origami
653
631
  # _no_:: The Object number.
654
632
  #
655
633
  def include?(no)
656
- load! if @objects.nil?
634
+ load!
657
635
 
658
636
  @objects.include?(no)
659
637
  end
@@ -662,7 +640,7 @@ module Origami
662
640
  # Iterates over each object in the stream.
663
641
  #
664
642
  def each(&b)
665
- load! if @objects.nil?
643
+ load!
666
644
 
667
645
  @objects.values.each(&b)
668
646
  end
@@ -681,14 +659,44 @@ module Origami
681
659
  # Returns the array of inner objects.
682
660
  #
683
661
  def objects
684
- load! if @objects.nil?
662
+ load!
685
663
 
686
664
  @objects.values
687
665
  end
688
666
 
689
667
  private
690
668
 
669
+ #
670
+ # Preprocess the object in case it already belongs to a document.
671
+ # If the document is the same as the current object stream, remove the duplicate object from our document.
672
+ # If the object comes from another document, use the export method to create a version without references.
673
+ #
674
+ def import_object_from_document(object)
675
+ obj_doc = object.document
676
+
677
+ # Remove the previous instance if the object is indirect to avoid duplicates.
678
+ if obj_doc.equal?(@document)
679
+ @document.delete_object(object.reference) if object.indirect?
680
+
681
+ # Otherwise, create a exported version of the object.
682
+ else
683
+ object = object.export
684
+ end
685
+
686
+ object
687
+ end
688
+
689
+ def store_object(object) #:nodoc:
690
+ object.set_indirect(true) # all stored objects are indirect.
691
+ object.parent = self # set this stream as the parent.
692
+ object.set_document(@document) # inherit document information.
693
+
694
+ @objects[object.no] = object
695
+ end
696
+
691
697
  def load! #:nodoc:
698
+ return unless @objects.nil?
699
+
692
700
  decode!
693
701
 
694
702
  @objects = {}
@@ -705,7 +713,7 @@ module Origami
705
713
  end
706
714
 
707
715
  self.length.times do |i|
708
- unless (0...@data.size).cover? (first_object_offset + offsets[i]) and offsets[i] >= 0
716
+ unless (0...@data.size).cover?(first_object_offset + offsets[i]) and offsets[i] >= 0
709
717
  raise InvalidObjectStreamObjectError, "Invalid offset '#{offsets[i]} for object #{nums[i]}"
710
718
  end
711
719
 
@@ -715,12 +723,10 @@ module Origami
715
723
  "Bad embedded object format in object stream" if type.nil?
716
724
 
717
725
  embeddedobj = type.parse(data)
718
- embeddedobj.set_indirect(true) # object is indirect
719
- embeddedobj.no = nums[i] # object number
720
- embeddedobj.parent = self # set this stream as the parent
721
- embeddedobj.set_document(@document) # indirect objects need pdf information
726
+ embeddedobj.no = nums[i] # object number
722
727
  embeddedobj.objstm_offset = offsets[i]
723
- @objects[nums[i]] = embeddedobj
728
+
729
+ store_object(embeddedobj)
724
730
  end
725
731
  end
726
732