origamindee 3.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +89 -0
- data/COPYING.LESSER +165 -0
- data/README.md +131 -0
- data/bin/config/pdfcop.conf.yml +236 -0
- data/bin/pdf2pdfa +87 -0
- data/bin/pdf2ruby +333 -0
- data/bin/pdfcop +476 -0
- data/bin/pdfdecompress +97 -0
- data/bin/pdfdecrypt +91 -0
- data/bin/pdfencrypt +113 -0
- data/bin/pdfexplode +223 -0
- data/bin/pdfextract +277 -0
- data/bin/pdfmetadata +143 -0
- data/bin/pdfsh +12 -0
- data/bin/shell/console.rb +128 -0
- data/bin/shell/hexdump.rb +59 -0
- data/bin/shell/irbrc +69 -0
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/examples/events/events.rb +72 -0
- data/examples/flash/flash.rb +37 -0
- data/examples/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami/3d.rb +364 -0
- data/lib/origami/acroform.rb +321 -0
- data/lib/origami/actions.rb +318 -0
- data/lib/origami/annotations.rb +711 -0
- data/lib/origami/array.rb +242 -0
- data/lib/origami/boolean.rb +90 -0
- data/lib/origami/catalog.rb +418 -0
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/compound.rb +161 -0
- data/lib/origami/destinations.rb +252 -0
- data/lib/origami/dictionary.rb +192 -0
- data/lib/origami/encryption.rb +1084 -0
- data/lib/origami/extensions/fdf.rb +347 -0
- data/lib/origami/extensions/ppklite.rb +422 -0
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters/ascii.rb +211 -0
- data/lib/origami/filters/ccitt/tables.rb +267 -0
- data/lib/origami/filters/ccitt.rb +357 -0
- data/lib/origami/filters/crypt.rb +38 -0
- data/lib/origami/filters/dct.rb +54 -0
- data/lib/origami/filters/flate.rb +69 -0
- data/lib/origami/filters/jbig2.rb +57 -0
- data/lib/origami/filters/jpx.rb +47 -0
- data/lib/origami/filters/lzw.rb +170 -0
- data/lib/origami/filters/predictors.rb +292 -0
- data/lib/origami/filters/runlength.rb +129 -0
- data/lib/origami/filters.rb +364 -0
- data/lib/origami/font.rb +196 -0
- data/lib/origami/functions.rb +79 -0
- data/lib/origami/graphics/colors.rb +230 -0
- data/lib/origami/graphics/instruction.rb +98 -0
- data/lib/origami/graphics/path.rb +182 -0
- data/lib/origami/graphics/patterns.rb +174 -0
- data/lib/origami/graphics/render.rb +62 -0
- data/lib/origami/graphics/state.rb +149 -0
- data/lib/origami/graphics/text.rb +225 -0
- data/lib/origami/graphics/xobject.rb +918 -0
- data/lib/origami/graphics.rb +38 -0
- data/lib/origami/header.rb +75 -0
- data/lib/origami/javascript.rb +713 -0
- data/lib/origami/linearization.rb +330 -0
- data/lib/origami/metadata.rb +172 -0
- data/lib/origami/name.rb +135 -0
- data/lib/origami/null.rb +65 -0
- data/lib/origami/numeric.rb +181 -0
- data/lib/origami/obfuscation.rb +245 -0
- data/lib/origami/object.rb +760 -0
- data/lib/origami/optionalcontent.rb +183 -0
- data/lib/origami/outline.rb +54 -0
- data/lib/origami/outputintents.rb +85 -0
- data/lib/origami/page.rb +722 -0
- data/lib/origami/parser.rb +269 -0
- data/lib/origami/parsers/fdf.rb +56 -0
- data/lib/origami/parsers/pdf/lazy.rb +176 -0
- data/lib/origami/parsers/pdf/linear.rb +122 -0
- data/lib/origami/parsers/pdf.rb +118 -0
- data/lib/origami/parsers/ppklite.rb +57 -0
- data/lib/origami/pdf.rb +1108 -0
- data/lib/origami/reference.rb +134 -0
- data/lib/origami/signature.rb +702 -0
- data/lib/origami/stream.rb +705 -0
- data/lib/origami/string.rb +444 -0
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +190 -0
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +100 -0
- data/lib/origami/xfa/config.rb +453 -0
- data/lib/origami/xfa/connectionset.rb +146 -0
- data/lib/origami/xfa/datasets.rb +49 -0
- data/lib/origami/xfa/localeset.rb +42 -0
- data/lib/origami/xfa/package.rb +59 -0
- data/lib/origami/xfa/pdf.rb +73 -0
- data/lib/origami/xfa/signature.rb +42 -0
- data/lib/origami/xfa/sourceset.rb +43 -0
- data/lib/origami/xfa/stylesheet.rb +44 -0
- data/lib/origami/xfa/template.rb +1691 -0
- data/lib/origami/xfa/xdc.rb +42 -0
- data/lib/origami/xfa/xfa.rb +146 -0
- data/lib/origami/xfa/xfdf.rb +43 -0
- data/lib/origami/xfa/xmpmeta.rb +43 -0
- data/lib/origami/xfa.rb +62 -0
- data/lib/origami/xreftable.rb +557 -0
- data/lib/origami.rb +47 -0
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +36 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +68 -0
- data/test/test_forms.rb +30 -0
- data/test/test_native_types.rb +83 -0
- data/test/test_object_tree.rb +33 -0
- data/test/test_pages.rb +60 -0
- data/test/test_pdf.rb +20 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +102 -0
- data/test/test_pdf_parse.rb +134 -0
- data/test/test_pdf_parse_lazy.rb +69 -0
- data/test/test_pdf_sign.rb +97 -0
- data/test/test_streams.rb +184 -0
- data/test/test_xrefs.rb +67 -0
- metadata +280 -0
@@ -0,0 +1,705 @@
|
|
1
|
+
=begin
|
2
|
+
|
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/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'strscan'
|
22
|
+
|
23
|
+
module Origami
|
24
|
+
|
25
|
+
class InvalidStreamObjectError < InvalidObjectError #:nodoc:
|
26
|
+
end
|
27
|
+
|
28
|
+
# Forward declaration.
|
29
|
+
class FileSpec < Dictionary; end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Class representing a PDF Stream Object.
|
33
|
+
# Streams can be used to hold any kind of data, especially binary data.
|
34
|
+
#
|
35
|
+
class Stream
|
36
|
+
include Origami::Object
|
37
|
+
include StandardObject
|
38
|
+
include FieldAccessor
|
39
|
+
include Enumerable
|
40
|
+
extend TypeGuessing
|
41
|
+
|
42
|
+
TOKENS = [ "stream" + WHITECHARS_NORET + "(\\r\\n|\\r|\\n)" , "endstream" ] #:nodoc:
|
43
|
+
|
44
|
+
@@regexp_open = Regexp.new(WHITESPACES + TOKENS.first)
|
45
|
+
@@regexp_close = Regexp.new(TOKENS.last)
|
46
|
+
|
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
|
57
|
+
|
58
|
+
CCITTFaxDecode
|
59
|
+
JBIG2Decode
|
60
|
+
DCTDecode
|
61
|
+
JPXDecode
|
62
|
+
|
63
|
+
AHx
|
64
|
+
A85
|
65
|
+
LZW
|
66
|
+
Fl
|
67
|
+
RL
|
68
|
+
CCF
|
69
|
+
DCT
|
70
|
+
]
|
71
|
+
|
72
|
+
attr_reader :dictionary
|
73
|
+
|
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"
|
81
|
+
|
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()
|
89
|
+
|
90
|
+
set_indirect(true)
|
91
|
+
|
92
|
+
@encoded_data = nil
|
93
|
+
@dictionary, @data = Dictionary.new(dictionary), data
|
94
|
+
@dictionary.parent = self
|
95
|
+
end
|
96
|
+
|
97
|
+
def dictionary=(dict)
|
98
|
+
@dictionary = dict
|
99
|
+
@dictionary.parent = self
|
100
|
+
end
|
101
|
+
|
102
|
+
def pre_build
|
103
|
+
encode!
|
104
|
+
|
105
|
+
super
|
106
|
+
end
|
107
|
+
|
108
|
+
def post_build
|
109
|
+
self.Length = @encoded_data.length
|
110
|
+
|
111
|
+
super
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.parse(stream, parser = nil) #:nodoc:
|
115
|
+
scanner = Parser.init_scanner(stream)
|
116
|
+
dictionary = Dictionary.parse(scanner, parser)
|
117
|
+
return dictionary if not scanner.skip(@@regexp_open)
|
118
|
+
|
119
|
+
length = dictionary[:Length]
|
120
|
+
if not length.is_a?(Integer)
|
121
|
+
raw_data = scanner.scan_until(@@regexp_close)
|
122
|
+
if raw_data.nil?
|
123
|
+
raise InvalidStreamObjectError,
|
124
|
+
"Stream shall end with a 'endstream' statement"
|
125
|
+
end
|
126
|
+
else
|
127
|
+
length = length.value
|
128
|
+
raw_data = scanner.peek(length)
|
129
|
+
scanner.pos += length
|
130
|
+
|
131
|
+
if not ( unmatched = scanner.scan_until(@@regexp_close) )
|
132
|
+
raise InvalidStreamObjectError,
|
133
|
+
"Stream shall end with a 'endstream' statement"
|
134
|
+
end
|
135
|
+
|
136
|
+
raw_data << unmatched
|
137
|
+
end
|
138
|
+
|
139
|
+
stm =
|
140
|
+
if Origami::OPTIONS[:enable_type_guessing]
|
141
|
+
self.guess_type(dictionary).new('', dictionary)
|
142
|
+
else
|
143
|
+
Stream.new('', dictionary)
|
144
|
+
end
|
145
|
+
|
146
|
+
raw_data.chomp!(TOKENS.last)
|
147
|
+
|
148
|
+
if raw_data[-1,1] == "\n"
|
149
|
+
if raw_data[-2,1] == "\r"
|
150
|
+
raw_data = raw_data[0, raw_data.size - 2]
|
151
|
+
else
|
152
|
+
raw_data = raw_data[0, raw_data.size - 1]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
#raw_data.chomp! if length.is_a?(Integer) and length < raw_data.length
|
156
|
+
|
157
|
+
stm.encoded_data = raw_data
|
158
|
+
stm.file_offset = dictionary.file_offset
|
159
|
+
|
160
|
+
stm
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Iterates over each Filter in the Stream.
|
165
|
+
#
|
166
|
+
def each_filter
|
167
|
+
filters = self.Filter
|
168
|
+
|
169
|
+
return enum_for(__method__) do
|
170
|
+
case filters
|
171
|
+
when NilClass then 0
|
172
|
+
when Array then filters.length
|
173
|
+
else
|
174
|
+
1
|
175
|
+
end
|
176
|
+
end unless block_given?
|
177
|
+
|
178
|
+
return if filters.nil?
|
179
|
+
|
180
|
+
if filters.is_a?(Array)
|
181
|
+
filters.each do |filter| yield(filter) end
|
182
|
+
else
|
183
|
+
yield(filters)
|
184
|
+
end
|
185
|
+
|
186
|
+
self
|
187
|
+
end
|
188
|
+
|
189
|
+
#
|
190
|
+
# Returns an Array of Filters for this Stream.
|
191
|
+
#
|
192
|
+
def filters
|
193
|
+
self.each_filter.to_a
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# Set predictor type for the current Stream.
|
198
|
+
# Applies only for LZW and FlateDecode filters.
|
199
|
+
#
|
200
|
+
def set_predictor(predictor, colors: 1, bitspercomponent: 8, columns: 1)
|
201
|
+
filters = self.filters
|
202
|
+
|
203
|
+
layer = filters.index(:FlateDecode) or filters.index(:LZWDecode)
|
204
|
+
if layer.nil?
|
205
|
+
raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
|
206
|
+
end
|
207
|
+
|
208
|
+
params = Filter::LZW::DecodeParms.new
|
209
|
+
params[:Predictor] = predictor
|
210
|
+
params[:Colors] = colors if colors != 1
|
211
|
+
params[:BitsPerComponent] = bitspercomponent if bitspercomponent != 8
|
212
|
+
params[:Columns] = columns if columns != 1
|
213
|
+
|
214
|
+
set_decode_params(layer, params)
|
215
|
+
|
216
|
+
self
|
217
|
+
end
|
218
|
+
|
219
|
+
def cast_to(type, _parser = nil)
|
220
|
+
assert_cast_type(type)
|
221
|
+
|
222
|
+
cast = type.new("", self.dictionary.copy)
|
223
|
+
cast.encoded_data = self.encoded_data.dup
|
224
|
+
cast.file_offset = self.file_offset
|
225
|
+
|
226
|
+
transfer_attributes(cast)
|
227
|
+
end
|
228
|
+
|
229
|
+
def value #:nodoc:
|
230
|
+
self
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
# Returns the uncompressed stream content.
|
235
|
+
#
|
236
|
+
def data
|
237
|
+
self.decode! unless decoded?
|
238
|
+
|
239
|
+
@data
|
240
|
+
end
|
241
|
+
alias decoded_data data
|
242
|
+
|
243
|
+
#
|
244
|
+
# Sets the uncompressed stream content.
|
245
|
+
# _str_:: The new uncompressed data.
|
246
|
+
#
|
247
|
+
def data=(str)
|
248
|
+
@encoded_data = nil
|
249
|
+
@data = str
|
250
|
+
end
|
251
|
+
alias decoded_data= data=
|
252
|
+
|
253
|
+
#
|
254
|
+
# Returns the raw compressed stream content.
|
255
|
+
#
|
256
|
+
def encoded_data
|
257
|
+
self.encode! unless encoded?
|
258
|
+
|
259
|
+
@encoded_data
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Sets the raw compressed stream content.
|
264
|
+
# _str_:: the new raw data.
|
265
|
+
#
|
266
|
+
def encoded_data=(str)
|
267
|
+
@encoded_data = str
|
268
|
+
@data = nil
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# Uncompress the stream data.
|
273
|
+
#
|
274
|
+
def decode!
|
275
|
+
self.decrypt! if self.is_a?(Encryption::EncryptedStream)
|
276
|
+
return if decoded?
|
277
|
+
|
278
|
+
filters = self.filters
|
279
|
+
dparams = decode_params
|
280
|
+
|
281
|
+
@data = @encoded_data.dup
|
282
|
+
@data.freeze
|
283
|
+
|
284
|
+
filters.each_with_index do |filter, layer|
|
285
|
+
params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
|
286
|
+
|
287
|
+
# Handle Crypt filters.
|
288
|
+
if filter == :Crypt
|
289
|
+
raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
|
290
|
+
|
291
|
+
# Skip the Crypt filter.
|
292
|
+
next
|
293
|
+
end
|
294
|
+
|
295
|
+
begin
|
296
|
+
@data = decode_data(@data, filter, params)
|
297
|
+
rescue Filter::Error => error
|
298
|
+
@data = error.decoded_data
|
299
|
+
raise
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
self
|
304
|
+
end
|
305
|
+
|
306
|
+
#
|
307
|
+
# Compress the stream data.
|
308
|
+
#
|
309
|
+
def encode!
|
310
|
+
return if encoded?
|
311
|
+
|
312
|
+
filters = self.filters
|
313
|
+
dparams = decode_params
|
314
|
+
|
315
|
+
@encoded_data = @data.dup
|
316
|
+
(filters.length - 1).downto(0) do |layer|
|
317
|
+
params = dparams[layer].is_a?(Dictionary) ? dparams[layer] : {}
|
318
|
+
filter = filters[layer]
|
319
|
+
|
320
|
+
# Handle Crypt filters.
|
321
|
+
if filter == :Crypt
|
322
|
+
raise Filter::Error, "Crypt filter must be the first filter" unless layer.zero?
|
323
|
+
|
324
|
+
# Skip the Crypt filter.
|
325
|
+
next
|
326
|
+
end
|
327
|
+
|
328
|
+
@encoded_data = encode_data(@encoded_data, filter, params)
|
329
|
+
end
|
330
|
+
|
331
|
+
self.Length = @encoded_data.length
|
332
|
+
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
def to_s(indent: 1, tab: "\t", eol: $/) #:nodoc:
|
337
|
+
content = ""
|
338
|
+
|
339
|
+
content << @dictionary.to_s(indent: indent, tab: tab)
|
340
|
+
content << "stream" + eol
|
341
|
+
content << self.encoded_data
|
342
|
+
content << eol << TOKENS.last
|
343
|
+
|
344
|
+
super(content, eol: eol)
|
345
|
+
end
|
346
|
+
|
347
|
+
def [](key) #:nodoc:
|
348
|
+
@dictionary[key]
|
349
|
+
end
|
350
|
+
|
351
|
+
def []=(key, val) #:nodoc:
|
352
|
+
@dictionary[key] = val
|
353
|
+
end
|
354
|
+
|
355
|
+
def each_key(&b) #:nodoc:
|
356
|
+
@dictionary.each_key(&b)
|
357
|
+
end
|
358
|
+
|
359
|
+
def each_pair(&b) #:nodoc
|
360
|
+
@dictionary.each_pair(&b)
|
361
|
+
end
|
362
|
+
alias each each_pair
|
363
|
+
|
364
|
+
def key?(name)
|
365
|
+
@dictionary.key?(name)
|
366
|
+
end
|
367
|
+
alias has_key? key?
|
368
|
+
|
369
|
+
def keys
|
370
|
+
@dictionary.keys
|
371
|
+
end
|
372
|
+
|
373
|
+
private
|
374
|
+
|
375
|
+
def decoded? #:nodoc:
|
376
|
+
not @data.nil?
|
377
|
+
end
|
378
|
+
|
379
|
+
def encoded? #:nodoc:
|
380
|
+
not @encoded_data.nil?
|
381
|
+
end
|
382
|
+
|
383
|
+
def each_decode_params
|
384
|
+
params = self.DecodeParms
|
385
|
+
|
386
|
+
return enum_for(__method__) do
|
387
|
+
case params
|
388
|
+
when NilClass then 0
|
389
|
+
when Array then params.length
|
390
|
+
else
|
391
|
+
1
|
392
|
+
end
|
393
|
+
end unless block_given?
|
394
|
+
|
395
|
+
return if params.nil?
|
396
|
+
|
397
|
+
if params.is_a?(Array)
|
398
|
+
params.each do |param| yield(param) end
|
399
|
+
else
|
400
|
+
yield(params)
|
401
|
+
end
|
402
|
+
|
403
|
+
self
|
404
|
+
end
|
405
|
+
|
406
|
+
def decode_params
|
407
|
+
each_decode_params.to_a
|
408
|
+
end
|
409
|
+
|
410
|
+
def set_decode_params(layer, params) #:nodoc:
|
411
|
+
dparms = self.DecodeParms
|
412
|
+
unless dparms.is_a? ::Array
|
413
|
+
@dictionary[:DecodeParms] = dparms = []
|
414
|
+
end
|
415
|
+
|
416
|
+
if layer > dparms.length - 1
|
417
|
+
dparms.concat(::Array.new(layer - dparms.length + 1, Null.new))
|
418
|
+
end
|
419
|
+
|
420
|
+
dparms[layer] = params
|
421
|
+
@dictionary[:DecodeParms] = dparms.first if dparms.length == 1
|
422
|
+
|
423
|
+
self
|
424
|
+
end
|
425
|
+
|
426
|
+
def decode_data(data, filter, params) #:nodoc:
|
427
|
+
filter_module(filter).decode(data, params)
|
428
|
+
end
|
429
|
+
|
430
|
+
def encode_data(data, filter, params) #:nodoc:
|
431
|
+
mod = filter_module(filter)
|
432
|
+
|
433
|
+
encoded = mod.encode(data, params)
|
434
|
+
|
435
|
+
if %i[ASCIIHexDecode ASCII85Decode AHx A85].include?(filter.value)
|
436
|
+
encoded << mod::EOD
|
437
|
+
end
|
438
|
+
|
439
|
+
encoded
|
440
|
+
end
|
441
|
+
|
442
|
+
def filter_module(name)
|
443
|
+
unless name.is_a?(Name)
|
444
|
+
raise InvalidObjectStreamObjectError, "Filter has invalid type #{name.type}"
|
445
|
+
end
|
446
|
+
|
447
|
+
unless DEFINED_FILTERS.include?(name.value)
|
448
|
+
raise InvalidStreamObjectError, "Invalid filter : #{name}"
|
449
|
+
end
|
450
|
+
|
451
|
+
Filter.const_get(name.value.to_s.sub(/Decode$/, ""))
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
#
|
456
|
+
# Class representing an external Stream.
|
457
|
+
#
|
458
|
+
class ExternalStream < Stream
|
459
|
+
|
460
|
+
def initialize(filespec, hash = {})
|
461
|
+
hash[:F] = filespec
|
462
|
+
super('', hash)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
class InvalidObjectStreamObjectError < InvalidStreamObjectError #:nodoc:
|
467
|
+
end
|
468
|
+
|
469
|
+
#
|
470
|
+
# Class representing a Stream containing other Objects.
|
471
|
+
#
|
472
|
+
class ObjectStream < Stream
|
473
|
+
include Enumerable
|
474
|
+
|
475
|
+
NUM = 0 #:nodoc:
|
476
|
+
OBJ = 1 #:nodoc:
|
477
|
+
|
478
|
+
field :Type, :Type => Name, :Default => :ObjStm, :Required => true, :Version => "1.5"
|
479
|
+
field :N, :Type => Integer, :Required => true
|
480
|
+
field :First, :Type => Integer, :Required => true
|
481
|
+
field :Extends, :Type => ObjectStream
|
482
|
+
|
483
|
+
#
|
484
|
+
# Creates a new Object Stream.
|
485
|
+
# _dictionary_:: A hash of attributes to set to the Stream.
|
486
|
+
# _raw_data_:: The Stream data.
|
487
|
+
#
|
488
|
+
def initialize(raw_data = "", dictionary = {})
|
489
|
+
super
|
490
|
+
|
491
|
+
@objects = nil
|
492
|
+
end
|
493
|
+
|
494
|
+
def pre_build #:nodoc:
|
495
|
+
load!
|
496
|
+
|
497
|
+
prolog = ""
|
498
|
+
data = ""
|
499
|
+
objoff = 0
|
500
|
+
@objects.to_a.sort.each do |num,obj|
|
501
|
+
|
502
|
+
obj.set_indirect(false)
|
503
|
+
obj.objstm_offset = objoff
|
504
|
+
|
505
|
+
prolog << "#{num} #{objoff} "
|
506
|
+
objdata = "#{obj} "
|
507
|
+
|
508
|
+
objoff += objdata.size
|
509
|
+
data << objdata
|
510
|
+
obj.set_indirect(true)
|
511
|
+
obj.no = num
|
512
|
+
end
|
513
|
+
|
514
|
+
self.data = prolog + data
|
515
|
+
|
516
|
+
@dictionary[:N] = @objects.size
|
517
|
+
@dictionary[:First] = prolog.size
|
518
|
+
|
519
|
+
super
|
520
|
+
end
|
521
|
+
|
522
|
+
#
|
523
|
+
# Adds a new Object to this Stream.
|
524
|
+
# _object_:: The Object to append.
|
525
|
+
#
|
526
|
+
def <<(object)
|
527
|
+
unless object.generation == 0
|
528
|
+
raise InvalidObjectError, "Cannot store an object with generation > 0 in an ObjectStream"
|
529
|
+
end
|
530
|
+
|
531
|
+
if object.is_a?(Stream)
|
532
|
+
raise InvalidObjectError, "Cannot store a Stream in an ObjectStream"
|
533
|
+
end
|
534
|
+
|
535
|
+
# We must have an associated document to generate new object numbers.
|
536
|
+
if @document.nil?
|
537
|
+
raise InvalidObjectError, "The ObjectStream must be added to a document before inserting objects"
|
538
|
+
end
|
539
|
+
|
540
|
+
# The object already belongs to a document.
|
541
|
+
unless object.document.nil?
|
542
|
+
object = import_object_from_document(object)
|
543
|
+
end
|
544
|
+
|
545
|
+
load!
|
546
|
+
|
547
|
+
object.no, object.generation = @document.allocate_new_object_number if object.no == 0
|
548
|
+
store_object(object)
|
549
|
+
|
550
|
+
Reference.new(object.no, 0)
|
551
|
+
end
|
552
|
+
alias insert <<
|
553
|
+
|
554
|
+
#
|
555
|
+
# Deletes Object _no_.
|
556
|
+
#
|
557
|
+
def delete(no)
|
558
|
+
load!
|
559
|
+
|
560
|
+
@objects.delete(no)
|
561
|
+
end
|
562
|
+
|
563
|
+
#
|
564
|
+
# Returns the index of Object _no_.
|
565
|
+
#
|
566
|
+
def index(no)
|
567
|
+
@objects.to_a.sort.index { |num, _| num == no }
|
568
|
+
end
|
569
|
+
|
570
|
+
#
|
571
|
+
# Returns a given decompressed object contained in the Stream.
|
572
|
+
# _no_:: The Object number.
|
573
|
+
#
|
574
|
+
def extract(no)
|
575
|
+
load!
|
576
|
+
|
577
|
+
@objects[no]
|
578
|
+
end
|
579
|
+
|
580
|
+
#
|
581
|
+
# Returns a given decompressed object by index.
|
582
|
+
# _index_:: The Object index in the ObjectStream.
|
583
|
+
#
|
584
|
+
def extract_by_index(index)
|
585
|
+
load!
|
586
|
+
|
587
|
+
raise TypeError, "index must be an integer" unless index.is_a?(::Integer)
|
588
|
+
raise IndexError, "index #{index} out of range" if index < 0 or index >= @objects.size
|
589
|
+
|
590
|
+
@objects.to_a.sort[index][1]
|
591
|
+
end
|
592
|
+
|
593
|
+
#
|
594
|
+
# Returns whether a specific object is contained in this stream.
|
595
|
+
# _no_:: The Object number.
|
596
|
+
#
|
597
|
+
def include?(no)
|
598
|
+
load!
|
599
|
+
|
600
|
+
@objects.include?(no)
|
601
|
+
end
|
602
|
+
|
603
|
+
#
|
604
|
+
# Iterates over each object in the stream.
|
605
|
+
#
|
606
|
+
def each(&b)
|
607
|
+
load!
|
608
|
+
|
609
|
+
@objects.values.each(&b)
|
610
|
+
end
|
611
|
+
alias each_object each
|
612
|
+
|
613
|
+
#
|
614
|
+
# Returns the number of objects contained in the stream.
|
615
|
+
#
|
616
|
+
def length
|
617
|
+
raise InvalidObjectStreamObjectError, "Invalid number of objects" unless self.N.is_a?(Integer)
|
618
|
+
|
619
|
+
self.N.to_i
|
620
|
+
end
|
621
|
+
|
622
|
+
#
|
623
|
+
# Returns the array of inner objects.
|
624
|
+
#
|
625
|
+
def objects
|
626
|
+
load!
|
627
|
+
|
628
|
+
@objects.values
|
629
|
+
end
|
630
|
+
|
631
|
+
private
|
632
|
+
|
633
|
+
#
|
634
|
+
# Preprocess the object in case it already belongs to a document.
|
635
|
+
# If the document is the same as the current object stream, remove the duplicate object from our document.
|
636
|
+
# If the object comes from another document, use the export method to create a version without references.
|
637
|
+
#
|
638
|
+
def import_object_from_document(object)
|
639
|
+
obj_doc = object.document
|
640
|
+
|
641
|
+
# Remove the previous instance if the object is indirect to avoid duplicates.
|
642
|
+
if obj_doc.equal?(@document)
|
643
|
+
@document.delete_object(object.reference) if object.indirect?
|
644
|
+
|
645
|
+
# Otherwise, create a exported version of the object.
|
646
|
+
else
|
647
|
+
object = object.export
|
648
|
+
end
|
649
|
+
|
650
|
+
object
|
651
|
+
end
|
652
|
+
|
653
|
+
def store_object(object) #:nodoc:
|
654
|
+
object.set_indirect(true) # all stored objects are indirect.
|
655
|
+
object.parent = self # set this stream as the parent.
|
656
|
+
object.set_document(@document) # inherit document information.
|
657
|
+
|
658
|
+
@objects[object.no] = object
|
659
|
+
end
|
660
|
+
|
661
|
+
def load! #:nodoc:
|
662
|
+
return unless @objects.nil?
|
663
|
+
|
664
|
+
decode!
|
665
|
+
|
666
|
+
@objects = {}
|
667
|
+
return if @data.empty?
|
668
|
+
|
669
|
+
data = StringScanner.new(@data)
|
670
|
+
nums = []
|
671
|
+
offsets = []
|
672
|
+
first_offset = first_object_offset
|
673
|
+
|
674
|
+
self.length.times do
|
675
|
+
nums << Integer.parse(data).to_i
|
676
|
+
offsets << Integer.parse(data).to_i
|
677
|
+
end
|
678
|
+
|
679
|
+
self.length.times do |i|
|
680
|
+
unless (0...@data.size).cover?(first_object_offset + offsets[i]) and offsets[i] >= 0
|
681
|
+
raise InvalidObjectStreamObjectError, "Invalid offset '#{offsets[i]} for object #{nums[i]}"
|
682
|
+
end
|
683
|
+
|
684
|
+
data.pos = first_offset + offsets[i]
|
685
|
+
type = Object.typeof(data)
|
686
|
+
raise InvalidObjectStreamObjectError,
|
687
|
+
"Bad embedded object format in object stream" if type.nil?
|
688
|
+
|
689
|
+
embeddedobj = type.parse(data)
|
690
|
+
embeddedobj.no = nums[i] # object number
|
691
|
+
embeddedobj.objstm_offset = offsets[i]
|
692
|
+
|
693
|
+
store_object(embeddedobj)
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
def first_object_offset #:nodoc:
|
698
|
+
raise InvalidObjectStreamObjectError, "Invalid First offset" unless self.First.is_a?(Integer)
|
699
|
+
raise InvalidObjectStreamObjectError, "Negative object offset" if self.First < 0
|
700
|
+
|
701
|
+
return self.First.to_i
|
702
|
+
end
|
703
|
+
end
|
704
|
+
|
705
|
+
end
|