origami 1.2.7 → 2.0.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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -1,25 +1,20 @@
1
1
  =begin
2
2
 
3
- = File
4
- stream.rb
5
-
6
- = Info
7
- This file is part of Origami, PDF manipulation framework for Ruby
8
- Copyright (C) 2010 Guillaume Delugré <guillaume AT security-labs DOT org>
9
- All right reserved.
10
-
11
- Origami is free software: you can redistribute it and/or modify
12
- it under the terms of the GNU Lesser General Public License as published by
13
- the Free Software Foundation, either version 3 of the License, or
14
- (at your option) any later version.
15
-
16
- Origami is distributed in the hope that it will be useful,
17
- but WITHOUT ANY WARRANTY; without even the implied warranty of
18
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
- GNU Lesser General Public License for more details.
20
-
21
- You should have received a copy of the GNU Lesser General Public License
22
- along with Origami. If not, see <http://www.gnu.org/licenses/>.
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
5
+
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
23
18
 
24
19
  =end
25
20
 
@@ -27,592 +22,714 @@ require 'strscan'
27
22
 
28
23
  module Origami
29
24
 
30
- class InvalidStreamObjectError < InvalidObjectError #:nodoc:
31
- end
32
-
33
- #
34
- # Class representing a PDF Stream Object.
35
- # Streams can be used to hold any kind of data, especially binary data.
36
- #
37
- class Stream
38
-
39
- include Origami::Object
40
- include StandardObject
41
-
42
- TOKENS = [ "stream" + WHITECHARS_NORET + "\\r?\\n", "endstream" ] #:nodoc:
43
-
44
- @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
45
- @@regexp_close = Regexp.new(TOKENS.last)
46
-
47
- @@cast_fingerprints = {}
25
+ class InvalidStreamObjectError < InvalidObjectError #:nodoc:
26
+ end
48
27
 
49
- #
50
- # Actually only 5 first ones are implemented, other ones are mainly about image data processing (JPEG, JPEG2000 ... )
51
- #
52
- @@defined_filters =
53
- [
54
- :ASCIIHexDecode,
55
- :ASCII85Decode,
56
- :LZWDecode,
57
- :FlateDecode,
58
- :RunLengthDecode,
59
- # TODO
60
- :CCITTFaxDecode,
61
- :JBIG2Decode,
62
- :DCTDecode,
63
- :JPXDecode,
64
- # abbrev
65
- :AHx, # ASCIIHexDecode
66
- :A85, # ASCII85Decode
67
- :LZW, # LZWDecode
68
- :Fl, # FlateDecode
69
- :RL, # RunLengthDecode
70
- :CCF, # CCITTFaxDecode
71
- :DCT, # DCTDecode
72
- ]
73
-
74
- attr_accessor :dictionary
75
-
76
- field :Length, :Type => Integer, :Required => true
77
- field :Filter, :Type => [ Name, Array ]
78
- field :DecodeParms, :Type => [ Dictionary, Array ]
79
- field :F, :Type => Dictionary, :Version => "1.2"
80
- field :FFilter, :Type => [ Name, Array ], :Version => "1.2"
81
- field :FDecodeParms, :Type => [ Dictionary, Array ], :Version => "1.2"
82
- field :DL, :Type => Integer, :Version => "1.5"
28
+ # Forward declaration.
29
+ class FileSpec < Dictionary; end
83
30
 
84
31
  #
85
- # Creates a new PDF Stream.
86
- # _data_:: The Stream uncompressed data.
87
- # _dictionary_:: A hash representing the Stream attributes.
32
+ # Class representing a PDF Stream Object.
33
+ # Streams can be used to hold any kind of data, especially binary data.
88
34
  #
89
- def initialize(data = "", dictionary = {})
90
- super()
91
-
92
- set_indirect(true)
93
-
94
- @dictionary, @data = Dictionary.new(dictionary), data
95
- @dictionary.parent = self
96
- end
97
-
98
- def pre_build
99
- encode!
100
-
101
- super
102
- end
35
+ class Stream
36
+ include Origami::Object
37
+ include StandardObject
38
+ using TypeConversion
103
39
 
104
- def post_build
105
- self.Length = @rawdata.length
106
-
107
- super
108
- end
40
+ TOKENS = [ "stream" + WHITECHARS_NORET + "\\r?\\n", "endstream" ] #:nodoc:
109
41
 
110
- def method_missing(field, *args) #:nodoc:
111
- if field.to_s[-1,1] == '='
112
- self[field.to_s[0..-2].to_sym] = args.first
113
- else
114
- obj = self[field];
115
- obj.is_a?(Reference) ? obj.solve : obj
116
- end
117
- end
118
-
119
- def self.parse(stream, parser = nil) #:nodoc:
120
-
121
- dictionary = Dictionary.parse(stream, parser)
122
- return dictionary if not stream.skip(@@regexp_open)
123
-
124
- length = dictionary[:Length]
125
- if not length.is_a?(Integer)
126
- rawdata = stream.scan_until(@@regexp_close)
127
- if rawdata.nil?
128
- raise InvalidStreamObjectError,
129
- "Stream shall end with a 'endstream' statement"
130
- end
131
- else
132
- length = length.value
133
- rawdata = stream.peek(length)
134
- stream.pos += length
135
-
136
- if not ( unmatched = stream.scan_until(@@regexp_close) )
137
- raise InvalidStreamObjectError,
138
- "Stream shall end with a 'endstream' statement"
139
- end
140
-
141
- rawdata << unmatched
142
- end
143
-
144
- stm =
145
- if Origami::OPTIONS[:enable_type_guessing]
146
- self.guess_type(dictionary).new('', dictionary.to_h)
147
- else
148
- Stream.new('', dictionary.to_h)
149
- end
150
-
151
- rawdata.chomp!(TOKENS.last)
152
-
153
- if rawdata[-1,1] == "\n"
154
- if rawdata[-2,1] == "\r"
155
- rawdata = rawdata[0, rawdata.size - 2]
156
- else
157
- rawdata = rawdata[0, rawdata.size - 1]
158
- end
159
- end
160
- #rawdata.chomp! if length.is_a?(Integer) and length < rawdata.length
161
-
162
- stm.rawdata = rawdata
163
- stm.file_offset = dictionary.file_offset
164
-
165
- stm
166
- end
167
-
168
- def self.add_type_info(typeclass, key, value) #:nodoc:
169
- if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Stream and
170
- @@cast_fingerprints.has_key?(typeclass.superclass)
171
- @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
172
- end
173
-
174
- @@cast_fingerprints[typeclass] ||= {}
175
- @@cast_fingerprints[typeclass][key.to_o] = value.to_o
176
- end
42
+ @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
43
+ @@regexp_close = Regexp.new(TOKENS.last)
177
44
 
178
- def self.guess_type(hash) #:nodoc:
179
- best_type = Stream
45
+ @@cast_fingerprints = {}
180
46
 
181
- @@cast_fingerprints.each_pair do |typeclass, keys|
182
- best_type = typeclass if keys.all? { |k,v|
183
- hash.has_key?(k) and hash[k] == v
184
- } and typeclass < best_type
185
- end
47
+ #
48
+ # Actually only 5 first ones are implemented,
49
+ # other ones are mainly about image data processing (JPEG, JPEG2000 ...)
50
+ #
51
+ @@defined_filters = %i[
52
+ ASCIIHexDecode
53
+ ASCII85Decode
54
+ LZWDecode
55
+ FlateDecode
56
+ RunLengthDecode
186
57
 
187
- best_type
188
- end
58
+ CCITTFaxDecode
59
+ JBIG2Decode
60
+ DCTDecode
61
+ JPXDecode
189
62
 
190
- def set_predictor(predictor, colors = 1, bitspercomponent = 8, columns = 1)
191
-
192
- filters = self.Filter
193
- filters = [ filters ] unless filters.is_a?(::Array)
63
+ AHx
64
+ A85
65
+ LZW
66
+ Fl
67
+ RL
68
+ CCF
69
+ DCT
70
+ ]
194
71
 
195
- if not filters.include?(:FlateDecode) and not filters.include?(:LZWDecode)
196
- raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
197
- end
72
+ attr_reader :dictionary
198
73
 
199
- layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
74
+ field :Length, :Type => Integer, :Required => true
75
+ field :Filter, :Type => [ Name, Array.of(Name) ]
76
+ field :DecodeParms, :Type => [ Dictionary, Array.of(Dictionary) ]
77
+ field :F, :Type => FileSpec, :Version => "1.2"
78
+ field :FFilter, :Type => [ Name, Array.of(Name) ], :Version => "1.2"
79
+ field :FDecodeParms, :Type => [ Dictionary, Array.of(Dictionary) ], :Version => "1.2"
80
+ field :DL, :Type => Integer, :Version => "1.5"
200
81
 
201
- params = Filter::LZW::DecodeParms.new
202
- params[:Predictor] = predictor
203
- params[:Colors] = colors if colors != 1
204
- params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
205
- params[:Columns] = columns if columns != 1
82
+ #
83
+ # Creates a new PDF Stream.
84
+ # _data_:: The Stream uncompressed data.
85
+ # _dictionary_:: A hash representing the Stream attributes.
86
+ #
87
+ def initialize(data = "", dictionary = {})
88
+ super()
206
89
 
207
- set_decode_params(layer, params)
208
-
209
- self
210
- end
90
+ set_indirect(true)
211
91
 
212
- def cast_to(type)
213
- super(type)
92
+ @encoded_data = nil
93
+ @dictionary, @data = Dictionary.new(dictionary), data
94
+ @dictionary.parent = self
95
+ end
214
96
 
215
- cast = type.new("", self.dictionary.to_h)
216
- cast.rawdata = @rawdata.dup
217
- cast.no, cast.generation = self.no, self.generation
218
- cast.set_indirect(true)
219
- cast.set_pdf(self.pdf)
220
- cast.file_offset = self.file_offset
97
+ def dictionary=(dict)
98
+ @dictionary = dict
99
+ @dictionary.parent = self
221
100
 
222
- cast
223
- end
101
+ @dictionary
102
+ end
224
103
 
225
- def value #:nodoc:
226
- self
227
- end
228
-
229
- #
230
- # Returns the uncompressed stream content.
231
- #
232
- def data
233
- self.decode! if @data.nil?
104
+ def pre_build
105
+ encode!
234
106
 
235
- @data
236
- end
237
-
238
- #
239
- # Sets the uncompressed stream content.
240
- # _str_:: The new uncompressed data.
241
- #
242
- def data=(str)
243
- @rawdata = nil
244
- @data = str
245
- end
246
-
247
- #
248
- # Returns the raw compressed stream content.
249
- #
250
- def rawdata
251
- self.encode! if @rawdata.nil?
107
+ super
108
+ end
252
109
 
253
- @rawdata
254
- end
255
-
256
- #
257
- # Sets the raw compressed stream content.
258
- # _str_:: the new raw data.
259
- #
260
- def rawdata=(str)
261
- @rawdata = str
262
- @data = nil
263
- end
264
-
265
- #
266
- # Uncompress the stream data.
267
- #
268
- def decode!
269
- self.decrypt! if self.is_a?(Encryption::EncryptedStream)
270
-
271
- unless is_decoded?
272
- filters = self.Filter
273
-
274
- if filters.nil?
275
- @data = @rawdata.dup
276
- else
277
- case filters
278
- when Array, Name then
279
- dparams = self.DecodeParms || []
280
-
281
- dparams = [ dparams ] unless dparams.is_a?(::Array)
282
- filters = [ filters ] unless filters.is_a?(::Array)
283
-
284
- @data = @rawdata.dup
285
- @data.freeze
286
-
287
- filters.length.times do |layer|
110
+ def post_build
111
+ self.Length = @encoded_data.length
112
+
113
+ super
114
+ end
115
+
116
+ def self.parse(stream, parser = nil) #:nodoc:
117
+ dictionary = Dictionary.parse(stream, parser)
118
+ return dictionary if not stream.skip(@@regexp_open)
119
+
120
+ length = dictionary[:Length]
121
+ if not length.is_a?(Integer)
122
+ raw_data = stream.scan_until(@@regexp_close)
123
+ if raw_data.nil?
124
+ raise InvalidStreamObjectError,
125
+ "Stream shall end with a 'endstream' statement"
126
+ end
127
+ else
128
+ length = length.value
129
+ raw_data = stream.peek(length)
130
+ stream.pos += length
131
+
132
+ if not ( unmatched = stream.scan_until(@@regexp_close) )
133
+ raise InvalidStreamObjectError,
134
+ "Stream shall end with a 'endstream' statement"
135
+ end
136
+
137
+ raw_data << unmatched
138
+ end
139
+
140
+ stm =
141
+ if Origami::OPTIONS[:enable_type_guessing]
142
+ self.guess_type(dictionary).new('', dictionary.to_h)
143
+ else
144
+ Stream.new('', dictionary.to_h)
145
+ end
146
+
147
+ raw_data.chomp!(TOKENS.last)
148
+
149
+ if raw_data[-1,1] == "\n"
150
+ if raw_data[-2,1] == "\r"
151
+ raw_data = raw_data[0, raw_data.size - 2]
152
+ else
153
+ raw_data = raw_data[0, raw_data.size - 1]
154
+ end
155
+ end
156
+ #raw_data.chomp! if length.is_a?(Integer) and length < raw_data.length
157
+
158
+ stm.encoded_data = raw_data
159
+ stm.file_offset = dictionary.file_offset
160
+
161
+ stm
162
+ end
163
+
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
168
+ end
169
+
170
+ @@cast_fingerprints[typeclass] ||= {}
171
+ @@cast_fingerprints[typeclass][key.to_o] = value.to_o
172
+ end
173
+
174
+ def self.guess_type(hash) #:nodoc:
175
+ best_type = self
176
+
177
+ @@cast_fingerprints.each_pair do |klass, keys|
178
+ next unless klass < best_type
179
+
180
+ best_type = klass if keys.all? { |k,v| hash[k] == v }
181
+ end
182
+
183
+ best_type
184
+ end
185
+
186
+ #
187
+ # Iterates over each Filter in the Stream.
188
+ #
189
+ def each_filter
190
+ filters = self.Filter
191
+
192
+ return enum_for(__method__) do
193
+ case filters
194
+ when NilClass then 0
195
+ when Array then filters.length
196
+ else
197
+ 1
198
+ end
199
+ end unless block_given?
200
+
201
+ return if filters.nil?
202
+
203
+ if filters.is_a?(Array)
204
+ filters.each do |filter| yield(filter) end
205
+ else
206
+ yield(filters)
207
+ end
208
+
209
+ self
210
+ end
211
+
212
+ #
213
+ # Returns an Array of Filters for this Stream.
214
+ #
215
+ def filters
216
+ self.each_filter.to_a
217
+ end
218
+
219
+ #
220
+ # Set predictor type for the current Stream.
221
+ # Applies only for LZW and FlateDecode filters.
222
+ #
223
+ def set_predictor(predictor, colors: 1, bitspercomponent: 8, columns: 1)
224
+ filters = self.filters
225
+
226
+ unless filters.include?(:FlateDecode) or filters.include?(:LZWDecode)
227
+ raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
228
+ end
229
+
230
+ layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
231
+
232
+ params = Filter::LZW::DecodeParms.new
233
+ params[:Predictor] = predictor
234
+ params[:Colors] = colors if colors != 1
235
+ params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
236
+ params[:Columns] = columns if columns != 1
237
+
238
+ set_decode_params(layer, params)
239
+
240
+ self
241
+ end
242
+
243
+ def cast_to(type, _parser = nil)
244
+ super(type)
245
+
246
+ cast = type.new("", self.dictionary.to_h)
247
+ cast.encoded_data = self.encoded_data.dup
248
+ cast.no, cast.generation = self.no, self.generation
249
+ cast.set_indirect(true)
250
+ cast.set_document(self.document)
251
+ cast.file_offset = self.file_offset
252
+
253
+ cast
254
+ end
255
+
256
+ def value #:nodoc:
257
+ self
258
+ end
259
+
260
+ #
261
+ # Returns the uncompressed stream content.
262
+ #
263
+ def data
264
+ self.decode! unless self.decoded?
265
+
266
+ @data
267
+ end
268
+ alias decoded_data data
269
+
270
+ #
271
+ # Sets the uncompressed stream content.
272
+ # _str_:: The new uncompressed data.
273
+ #
274
+ def data=(str)
275
+ @encoded_data = nil
276
+ @data = str
277
+ end
278
+ alias decoded_data= data=
279
+
280
+ #
281
+ # Returns the raw compressed stream content.
282
+ #
283
+ def encoded_data
284
+ self.encode! unless self.encoded?
285
+
286
+ @encoded_data
287
+ end
288
+
289
+ #
290
+ # Sets the raw compressed stream content.
291
+ # _str_:: the new raw data.
292
+ #
293
+ def encoded_data=(str)
294
+ @encoded_data = str
295
+ @data = nil
296
+ end
297
+
298
+ #
299
+ # Uncompress the stream data.
300
+ #
301
+ def decode!
302
+ self.decrypt! if self.is_a?(Encryption::EncryptedStream)
303
+ return if decoded?
304
+
305
+ filters = self.filters
306
+
307
+ if filters.empty?
308
+ @data = @encoded_data.dup
309
+ return
310
+ end
311
+
312
+ unless filters.all?{|filter| filter.is_a?(Name)}
313
+ raise InvalidStreamObjectError, "Invalid Filter type parameter"
314
+ end
315
+
316
+ dparams = decode_params
317
+
318
+ @data = @encoded_data.dup
319
+ @data.freeze
320
+
321
+ filters.each_with_index do |filter, layer|
288
322
  params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
289
- filter = filters[layer]
323
+
324
+ # Handle Crypt filters.
325
+ if filter == :Crypt
326
+ raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
327
+
328
+ # Skip the Crypt filter.
329
+ next
330
+ end
290
331
 
291
332
  begin
292
- @data = decode_data(@data, filter, params)
293
- rescue Filter::InvalidFilterDataError => e
294
- @data = e.decoded_data if e.decoded_data
295
- raise InvalidStreamObjectError,
296
- "Error while decoding stream #{self.reference}\n\t-> [#{e.class}] #{e.message}"
333
+ @data = decode_data(@data, filter, params)
334
+ rescue Filter::Error => error
335
+ @data = error.decoded_data if error.decoded_data
336
+ raise
297
337
  end
298
- end
299
- else
300
- raise InvalidStreamObjectError, "Invalid Filter type parameter"
301
- end
338
+ end
339
+
340
+ self
302
341
  end
303
- end
304
-
305
- self
306
- end
307
-
308
- #
309
- # Compress the stream data.
310
- #
311
- def encode!
312
-
313
- unless is_encoded?
314
- filters = self.Filter
315
-
316
- if filters.nil?
317
- @rawdata = @data.dup
318
- else
319
- case filters
320
- when Array, Name then
321
- dparams = self.DecodeParms || []
322
-
323
- dparams = [ dparams ] unless dparams.is_a?(::Array)
324
- filters = [ filters ] unless filters.is_a?(::Array)
325
-
326
- @rawdata = @data.dup
327
- (filters.length - 1).downto(0) do |layer|
342
+
343
+ #
344
+ # Compress the stream data.
345
+ #
346
+ def encode!
347
+ return if encoded?
348
+ filters = self.filters
349
+
350
+ if filters.empty?
351
+ @encoded_data = @data.dup
352
+ return
353
+ end
354
+
355
+ unless filters.all?{|filter| filter.is_a?(Name)}
356
+ raise InvalidStreamObjectError, "Invalid Filter type parameter"
357
+ end
358
+
359
+ dparams = decode_params
360
+
361
+ @encoded_data = @data.dup
362
+ (filters.length - 1).downto(0) do |layer|
328
363
  params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
329
364
  filter = filters[layer]
330
365
 
331
- @rawdata = encode_data(@rawdata, filter, params)
332
- end
333
- else
334
- raise InvalidStreamObjectError, "Invalid filter type parameter"
335
- end
336
- end
337
-
338
- self.Length = @rawdata.length
339
- end
340
-
341
- self
342
- end
343
-
344
- def to_s(indent = 1) #:nodoc:
345
-
346
- content = ""
347
-
348
- content << @dictionary.to_s(indent)
349
- content << "stream" + EOL
350
- content << self.rawdata
351
- content << EOL << TOKENS.last
352
-
353
- super(content)
354
- end
355
-
356
- def [](key) #:nodoc:
357
- @dictionary[key]
358
- end
359
-
360
- def []=(key,val) #:nodoc:
361
- @dictionary[key] = val
362
- end
363
-
364
- def each_key(&b) #:nodoc:
365
- @dictionary.each_key(&b)
366
- end
367
-
368
- def self.native_type ; Stream end
366
+ # Handle Crypt filters.
367
+ if filter == :Crypt
368
+ raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
369
369
 
370
- private
370
+ # Skip the Crypt filter.
371
+ next
372
+ end
371
373
 
372
- def is_decoded? #:nodoc:
373
- not @data.nil?
374
- end
374
+ @encoded_data = encode_data(@encoded_data, filter, params)
375
+ end
375
376
 
376
- def is_encoded? #:nodoc:
377
- not @rawdata.nil?
378
- end
377
+ self.Length = @encoded_data.length
379
378
 
380
- def set_decode_params(layer, params) #:nodoc:
381
- dparms = self.DecodeParms
382
- unless dparms.is_a? ::Array
383
- @dictionary[:DecodeParms] = dparms = []
384
- end
379
+ self
380
+ end
385
381
 
386
- if layer > dparms.length - 1
387
- dparms.concat(::Array.new(layer - dparms.length + 1, Null.new))
388
- end
382
+ def to_s(indent: 1, tab: "\t") #:nodoc:
383
+ content = ""
389
384
 
390
- dparms[layer] = params
391
- @dictionary[:DecodeParms] = dparms.first if dparms.length == 1
385
+ content << @dictionary.to_s(indent: indent, tab: tab)
386
+ content << "stream" + EOL
387
+ content << self.encoded_data
388
+ content << EOL << TOKENS.last
392
389
 
393
- self
394
- end
395
-
396
- def decode_data(data, filter, params) #:nodoc:
397
- unless @@defined_filters.include?(filter.value)
398
- raise InvalidStreamObjectError, "Unknown filter : #{filter}"
399
- end
390
+ super(content)
391
+ end
400
392
 
401
- Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,"")).decode(data, params)
402
- end
403
-
404
- def encode_data(data, filter, params) #:nodoc:
405
- unless @@defined_filters.include?(filter.value)
406
- raise InvalidStreamObjectError, "Unknown filter : #{filter}"
407
- end
408
-
409
- encoded = Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,"")).encode(data, params)
410
-
411
- if filter.value == :ASCIIHexDecode or filter.value == :ASCII85Decode
412
- encoded << Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,""))::EOD
413
- end
414
-
415
- encoded
416
- end
417
-
418
- end
393
+ def [](key) #:nodoc:
394
+ @dictionary[key]
395
+ end
419
396
 
420
- #
421
- # Class representing an external Stream.
422
- #
423
- class ExternalStream < Stream
397
+ def []=(key,val) #:nodoc:
398
+ @dictionary[key] = val
399
+ end
424
400
 
425
- def initialize(filespec, hash = {})
401
+ def each_key(&b) #:nodoc:
402
+ @dictionary.each_key(&b)
403
+ end
426
404
 
427
- hash[:F] = filespec
428
- super('', hash)
429
- end
405
+ def key?(name)
406
+ @dictionary.key?(name)
407
+ end
408
+ alias has_key? key?
430
409
 
431
- end
432
-
433
- class InvalidObjectStreamObjectError < InvalidStreamObjectError #:nodoc:
434
- end
435
-
436
- #
437
- # Class representing a Stream containing other Objects.
438
- #
439
- class ObjectStream < Stream
440
-
441
- include Enumerable
442
-
443
- NUM = 0 #:nodoc:
444
- OBJ = 1 #:nodoc:
445
-
446
- field :Type, :Type => Name, :Default => :ObjStm, :Required => true, :Version => "1.5"
447
- field :N, :Type => Integer, :Required => true
448
- field :First, :Type => Integer, :Required => true
449
- field :Extends, :Type => Stream
410
+ def self.native_type ; Stream end
450
411
 
451
- #
452
- # Creates a new Object Stream.
453
- # _dictionary_:: A hash of attributes to set to the Stream.
454
- # _rawdata_:: The Stream data.
455
- #
456
- def initialize(rawdata = "", dictionary = {})
457
- @objects = nil
458
-
459
- super(rawdata, dictionary)
460
- end
461
-
462
- def pre_build #:nodoc:
463
- load! if @objects.nil?
464
-
465
- prolog = ""
466
- data = ""
467
- objoff = 0
468
- @objects.to_a.sort.each do |num,obj|
469
-
470
- obj.set_indirect(false)
471
- obj.objstm_offset = objoff
472
-
473
- prolog << "#{num} #{objoff} "
474
- objdata = "#{obj.to_s} "
475
-
476
- objoff += objdata.size
477
- data << objdata
478
- obj.set_indirect(true)
479
- obj.no = num
480
- end
481
-
482
- self.data = prolog + data
483
-
484
- @dictionary[:N] = @objects.size
485
- @dictionary[:First] = prolog.size
486
-
487
- super
488
- end
489
-
490
- #
491
- # Adds a new Object to this Stream.
492
- # _object_:: The Object to append.
493
- #
494
- def <<(object)
495
- unless object.generation == 0
496
- raise InvalidObjectError, "Cannot store an object with generation > 0 in an ObjectStream"
497
- end
498
-
499
- if object.is_a?(Stream)
500
- raise InvalidObjectError, "Cannot store a Stream in an ObjectStream"
501
- end
502
-
503
- load! if @objects.nil?
504
-
505
- object.no, object.generation = @pdf.alloc_new_object_number if object.no == 0
506
-
507
- object.set_indirect(true) # object is indirect
508
- object.parent = self # set this stream as the parent
509
- object.set_pdf(@pdf) # indirect objects need pdf information
510
- @objects[object.no] = object
511
-
512
- Reference.new(object.no, 0)
513
- end
514
- alias :insert :<<
412
+ private
515
413
 
516
- #
517
- # Deletes Object _no_.
518
- #
519
- def delete(no)
520
- load! if @objects.nil?
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
521
422
 
522
- @objects.delete(no)
523
- end
423
+ def decoded? #:nodoc:
424
+ not @data.nil?
425
+ end
524
426
 
525
- #
526
- # Returns the index of Object _no_.
527
- #
528
- def index(no)
529
- ind = 0
530
- @objects.to_a.sort.each { |num, obj|
531
- return ind if num == no
427
+ def encoded? #:nodoc:
428
+ not @encoded_data.nil?
429
+ end
532
430
 
533
- ind = ind + 1
534
- }
431
+ def each_decode_params
432
+ params = self.DecodeParms
535
433
 
536
- nil
537
- end
434
+ return enum_for(__method__) do
435
+ case params
436
+ when NilClass then 0
437
+ when Array then params.length
438
+ else
439
+ 1
440
+ end
441
+ end unless block_given?
538
442
 
539
- #
540
- # Returns a given decompressed object contained in the Stream.
541
- # _no_:: The Object number.
542
- #
543
- def extract(no)
544
- load! if @objects.nil?
545
-
546
- @objects[no]
547
- end
443
+ return if params.nil?
548
444
 
549
- #
550
- # Returns a given decompressed object by index.
551
- # _index_:: The Object index in the ObjectStream.
552
- #
553
- def extract_by_index(index)
554
- load! if @objects.nil?
445
+ if params.is_a?(Array)
446
+ params.each do |param| yield(param) end
447
+ else
448
+ yield(params)
449
+ end
450
+
451
+ self
452
+ end
453
+
454
+ def decode_params
455
+ each_decode_params.to_a
456
+ end
457
+
458
+ def set_decode_params(layer, params) #:nodoc:
459
+ dparms = self.DecodeParms
460
+ unless dparms.is_a? ::Array
461
+ @dictionary[:DecodeParms] = dparms = []
462
+ end
463
+
464
+ if layer > dparms.length - 1
465
+ dparms.concat(::Array.new(layer - dparms.length + 1, Null.new))
466
+ end
467
+
468
+ dparms[layer] = params
469
+ @dictionary[:DecodeParms] = dparms.first if dparms.length == 1
470
+
471
+ self
472
+ end
473
+
474
+ def decode_data(data, filter, params) #:nodoc:
475
+ unless @@defined_filters.include?(filter.value)
476
+ raise InvalidStreamObjectError, "Unknown filter : #{filter}"
477
+ end
478
+
479
+ Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,"")).decode(data, params)
480
+ end
555
481
 
556
- @objects.to_a.sort[index]
482
+ def encode_data(data, filter, params) #:nodoc:
483
+ unless @@defined_filters.include?(filter.value)
484
+ raise InvalidStreamObjectError, "Unknown filter : #{filter}"
485
+ end
486
+
487
+ encoded = Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,"")).encode(data, params)
488
+
489
+ if filter.value == :ASCIIHexDecode or filter.value == :ASCII85Decode
490
+ encoded << Origami::Filter.const_get(filter.value.to_s.sub(/Decode$/,""))::EOD
491
+ end
492
+
493
+ encoded
494
+ end
557
495
  end
558
-
496
+
559
497
  #
560
- # Returns whether a specific object is contained in this stream.
561
- # _no_:: The Object number.
498
+ # Class representing an external Stream.
562
499
  #
563
- def include?(no)
564
- load! if @objects.nil?
565
-
566
- @objects.include?(no)
500
+ class ExternalStream < Stream
501
+
502
+ def initialize(filespec, hash = {})
503
+ hash[:F] = filespec
504
+ super('', hash)
505
+ end
567
506
  end
568
-
569
- #
570
- # Iterates over each object in the stream.
571
- #
572
- def each(&b)
573
- load! if @objects.nil?
574
-
575
- @objects.values.each(&b)
507
+
508
+ class InvalidObjectStreamObjectError < InvalidStreamObjectError #:nodoc:
576
509
  end
577
-
510
+
578
511
  #
579
- # Returns the array of inner objects.
512
+ # Class representing a Stream containing other Objects.
580
513
  #
581
- def objects
582
- load! if @objects.nil?
583
-
584
- @objects.values
585
- end
586
-
587
- private
588
-
589
- def load! #:nodoc:
590
- decode!
591
-
592
- data = StringScanner.new(@data)
593
- nums = []
594
- offsets = []
595
-
596
- @dictionary[:N].to_i.times do
597
- nums << Integer.parse(data).to_i
598
- offsets << Integer.parse(data)
599
- end
600
-
601
- @objects = {}
602
- nums.size.times do |i|
603
- type = Object.typeof(data)
604
- raise InvalidObjectStreamObjectError,
605
- "Bad embedded object format in object stream" if type.nil?
606
-
607
- embeddedobj = type.parse(data)
608
- embeddedobj.set_indirect(true) # object is indirect
609
- embeddedobj.no = nums[i] # object number
610
- embeddedobj.parent = self # set this stream as the parent
611
- embeddedobj.set_pdf(@pdf) # indirect objects need pdf information
612
- embeddedobj.objstm_offset = offsets[i]
613
- @objects[nums[i]] = embeddedobj
614
- end
615
-
514
+ class ObjectStream < Stream
515
+ include Enumerable
516
+
517
+ NUM = 0 #:nodoc:
518
+ OBJ = 1 #:nodoc:
519
+
520
+ field :Type, :Type => Name, :Default => :ObjStm, :Required => true, :Version => "1.5"
521
+ field :N, :Type => Integer, :Required => true
522
+ field :First, :Type => Integer, :Required => true
523
+ field :Extends, :Type => ObjectStream
524
+
525
+ #
526
+ # Creates a new Object Stream.
527
+ # _dictionary_:: A hash of attributes to set to the Stream.
528
+ # _raw_data_:: The Stream data.
529
+ #
530
+ def initialize(raw_data = "", dictionary = {})
531
+ super
532
+
533
+ @objects = nil
534
+ end
535
+
536
+ def pre_build #:nodoc:
537
+ load! if @objects.nil?
538
+
539
+ prolog = ""
540
+ data = ""
541
+ objoff = 0
542
+ @objects.to_a.sort.each do |num,obj|
543
+
544
+ obj.set_indirect(false)
545
+ obj.objstm_offset = objoff
546
+
547
+ prolog << "#{num} #{objoff} "
548
+ objdata = "#{obj.to_s} "
549
+
550
+ objoff += objdata.size
551
+ data << objdata
552
+ obj.set_indirect(true)
553
+ obj.no = num
554
+ end
555
+
556
+ self.data = prolog + data
557
+
558
+ @dictionary[:N] = @objects.size
559
+ @dictionary[:First] = prolog.size
560
+
561
+ super
562
+ end
563
+
564
+ #
565
+ # Adds a new Object to this Stream.
566
+ # _object_:: The Object to append.
567
+ #
568
+ def <<(object)
569
+ unless object.generation == 0
570
+ raise InvalidObjectError, "Cannot store an object with generation > 0 in an ObjectStream"
571
+ end
572
+
573
+ if object.is_a?(Stream)
574
+ raise InvalidObjectError, "Cannot store a Stream in an ObjectStream"
575
+ end
576
+
577
+ # We must have an associated document to generate new object numbers.
578
+ if @document.nil?
579
+ raise InvalidObjectError, "The ObjectStream must be added to a document before inserting objects"
580
+ end
581
+
582
+ # 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
590
+ end
591
+
592
+ load! if @objects.nil?
593
+
594
+ 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
600
+
601
+ Reference.new(object.no, 0)
602
+ end
603
+ alias insert <<
604
+
605
+ #
606
+ # Deletes Object _no_.
607
+ #
608
+ def delete(no)
609
+ load! if @objects.nil?
610
+
611
+ @objects.delete(no)
612
+ end
613
+
614
+ #
615
+ # Returns the index of Object _no_.
616
+ #
617
+ 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
626
+ end
627
+
628
+ #
629
+ # Returns a given decompressed object contained in the Stream.
630
+ # _no_:: The Object number.
631
+ #
632
+ def extract(no)
633
+ load! if @objects.nil?
634
+
635
+ @objects[no]
636
+ end
637
+
638
+ #
639
+ # Returns a given decompressed object by index.
640
+ # _index_:: The Object index in the ObjectStream.
641
+ #
642
+ def extract_by_index(index)
643
+ load! if @objects.nil?
644
+
645
+ raise TypeError, "index must be an integer" unless index.is_a?(::Integer)
646
+ raise IndexError, "index #{index} out of range" if index < 0 or index >= @objects.size
647
+
648
+ @objects.to_a.sort[index][1]
649
+ end
650
+
651
+ #
652
+ # Returns whether a specific object is contained in this stream.
653
+ # _no_:: The Object number.
654
+ #
655
+ def include?(no)
656
+ load! if @objects.nil?
657
+
658
+ @objects.include?(no)
659
+ end
660
+
661
+ #
662
+ # Iterates over each object in the stream.
663
+ #
664
+ def each(&b)
665
+ load! if @objects.nil?
666
+
667
+ @objects.values.each(&b)
668
+ end
669
+ alias each_object each
670
+
671
+ #
672
+ # Returns the number of objects contained in the stream.
673
+ #
674
+ def length
675
+ raise InvalidObjectStreamObjectError, "Invalid number of objects" unless self.N.is_a?(Integer)
676
+
677
+ self.N.to_i
678
+ end
679
+
680
+ #
681
+ # Returns the array of inner objects.
682
+ #
683
+ def objects
684
+ load! if @objects.nil?
685
+
686
+ @objects.values
687
+ end
688
+
689
+ private
690
+
691
+ def load! #:nodoc:
692
+ decode!
693
+
694
+ @objects = {}
695
+ return if @data.empty?
696
+
697
+ data = StringScanner.new(@data)
698
+ nums = []
699
+ offsets = []
700
+ first_offset = first_object_offset
701
+
702
+ self.length.times do
703
+ nums << Integer.parse(data).to_i
704
+ offsets << Integer.parse(data).to_i
705
+ end
706
+
707
+ self.length.times do |i|
708
+ unless (0...@data.size).cover? (first_object_offset + offsets[i]) and offsets[i] >= 0
709
+ raise InvalidObjectStreamObjectError, "Invalid offset '#{offsets[i]} for object #{nums[i]}"
710
+ end
711
+
712
+ data.pos = first_offset + offsets[i]
713
+ type = Object.typeof(data)
714
+ raise InvalidObjectStreamObjectError,
715
+ "Bad embedded object format in object stream" if type.nil?
716
+
717
+ 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
722
+ embeddedobj.objstm_offset = offsets[i]
723
+ @objects[nums[i]] = embeddedobj
724
+ end
725
+ end
726
+
727
+ def first_object_offset #:nodoc:
728
+ raise InvalidObjectStreamObjectError, "Invalid First offset" unless self.First.is_a?(Integer)
729
+ raise InvalidObjectStreamObjectError, "Negative object offset" if self.First < 0
730
+
731
+ return self.First.to_i
732
+ end
616
733
  end
617
- end
734
+
618
735
  end