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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -0
- data/README.md +112 -0
- data/bin/config/pdfcop.conf.yml +232 -233
- data/bin/gui/about.rb +27 -37
- data/bin/gui/config.rb +108 -117
- data/bin/gui/file.rb +416 -365
- data/bin/gui/gtkhex.rb +1138 -1153
- data/bin/gui/hexview.rb +55 -57
- data/bin/gui/imgview.rb +48 -51
- data/bin/gui/menu.rb +388 -386
- data/bin/gui/properties.rb +114 -130
- data/bin/gui/signing.rb +571 -617
- data/bin/gui/textview.rb +77 -95
- data/bin/gui/treeview.rb +382 -387
- data/bin/gui/walker.rb +227 -232
- data/bin/gui/xrefs.rb +56 -60
- data/bin/pdf2pdfa +53 -57
- data/bin/pdf2ruby +212 -228
- data/bin/pdfcop +338 -348
- data/bin/pdfdecompress +58 -65
- data/bin/pdfdecrypt +56 -60
- data/bin/pdfencrypt +75 -80
- data/bin/pdfexplode +185 -182
- data/bin/pdfextract +201 -218
- data/bin/pdfmetadata +83 -82
- data/bin/pdfsh +4 -5
- data/bin/pdfwalker +1 -2
- data/bin/shell/.irbrc +45 -82
- data/bin/shell/console.rb +105 -130
- data/bin/shell/hexdump.rb +40 -64
- 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/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
- data/examples/flash/flash.rb +37 -0
- data/{samples → 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.rb +29 -42
- data/lib/origami/3d.rb +350 -225
- data/lib/origami/acroform.rb +262 -288
- data/lib/origami/actions.rb +268 -288
- data/lib/origami/annotations.rb +697 -722
- data/lib/origami/array.rb +258 -184
- data/lib/origami/boolean.rb +74 -84
- data/lib/origami/catalog.rb +397 -434
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/destinations.rb +233 -194
- data/lib/origami/dictionary.rb +253 -232
- data/lib/origami/encryption.rb +1274 -1243
- data/lib/origami/export.rb +232 -268
- data/lib/origami/extensions/fdf.rb +307 -220
- data/lib/origami/extensions/ppklite.rb +368 -435
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters.rb +301 -295
- data/lib/origami/filters/ascii.rb +177 -180
- data/lib/origami/filters/ccitt.rb +528 -535
- data/lib/origami/filters/crypt.rb +26 -35
- data/lib/origami/filters/dct.rb +46 -52
- data/lib/origami/filters/flate.rb +95 -94
- data/lib/origami/filters/jbig2.rb +49 -55
- data/lib/origami/filters/jpx.rb +38 -44
- data/lib/origami/filters/lzw.rb +189 -183
- data/lib/origami/filters/predictors.rb +221 -235
- data/lib/origami/filters/runlength.rb +103 -104
- data/lib/origami/font.rb +173 -186
- data/lib/origami/functions.rb +67 -81
- data/lib/origami/graphics.rb +25 -21
- data/lib/origami/graphics/colors.rb +178 -187
- data/lib/origami/graphics/instruction.rb +79 -85
- data/lib/origami/graphics/path.rb +142 -148
- data/lib/origami/graphics/patterns.rb +160 -167
- data/lib/origami/graphics/render.rb +43 -50
- data/lib/origami/graphics/state.rb +138 -153
- data/lib/origami/graphics/text.rb +188 -205
- data/lib/origami/graphics/xobject.rb +819 -815
- data/lib/origami/header.rb +63 -78
- data/lib/origami/javascript.rb +596 -597
- data/lib/origami/linearization.rb +285 -290
- data/lib/origami/metadata.rb +139 -148
- data/lib/origami/name.rb +112 -148
- data/lib/origami/null.rb +53 -62
- data/lib/origami/numeric.rb +162 -175
- data/lib/origami/obfuscation.rb +186 -174
- data/lib/origami/object.rb +593 -573
- data/lib/origami/outline.rb +42 -47
- data/lib/origami/outputintents.rb +73 -82
- data/lib/origami/page.rb +703 -592
- data/lib/origami/parser.rb +238 -290
- data/lib/origami/parsers/fdf.rb +41 -33
- data/lib/origami/parsers/pdf.rb +75 -95
- data/lib/origami/parsers/pdf/lazy.rb +137 -0
- data/lib/origami/parsers/pdf/linear.rb +64 -66
- data/lib/origami/parsers/ppklite.rb +34 -70
- data/lib/origami/pdf.rb +1030 -1005
- data/lib/origami/reference.rb +102 -102
- data/lib/origami/signature.rb +591 -609
- data/lib/origami/stream.rb +668 -551
- data/lib/origami/string.rb +397 -373
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +144 -158
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +88 -79
- data/lib/origami/xfa.rb +2863 -2882
- data/lib/origami/xreftable.rb +472 -384
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +82 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +90 -0
- data/test/test_pages.rb +31 -0
- data/test/test_pdf.rb +16 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +95 -0
- data/test/test_pdf_parse.rb +96 -0
- data/test/test_pdf_sign.rb +58 -0
- data/test/test_streams.rb +182 -0
- data/test/test_xrefs.rb +67 -0
- metadata +88 -58
- data/README +0 -67
- data/bin/pdf2graph +0 -121
- data/bin/pdfcocoon +0 -104
- data/lib/origami/file.rb +0 -233
- data/samples/README.txt +0 -45
- data/samples/actions/launch/calc.rb +0 -87
- data/samples/actions/launch/winparams.rb +0 -22
- data/samples/actions/loop/loopgoto.rb +0 -24
- data/samples/actions/loop/loopnamed.rb +0 -21
- data/samples/actions/named/named.rb +0 -31
- data/samples/actions/samba/smbrelay.rb +0 -26
- data/samples/actions/webbug/submitform.js +0 -26
- data/samples/actions/webbug/webbug-browser.rb +0 -68
- data/samples/actions/webbug/webbug-js.rb +0 -67
- data/samples/actions/webbug/webbug-reader.rb +0 -90
- data/samples/attachments/attach.rb +0 -40
- data/samples/attachments/attached.txt +0 -1
- data/samples/crypto/crypto.rb +0 -28
- data/samples/digsig/signed.rb +0 -46
- data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
- data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
- data/samples/exploits/exploit_customdictopen.rb +0 -55
- data/samples/exploits/getannots.rb +0 -69
- data/samples/flash/flash.rb +0 -31
- data/samples/javascript/attached.txt +0 -1
- data/samples/javascript/js.rb +0 -52
- data/templates/patterns.rb +0 -66
- data/templates/widgets.rb +0 -173
- data/templates/xdp.rb +0 -92
- data/test/ts_pdf.rb +0 -50
data/lib/origami/stream.rb
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
=begin
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
86
|
-
#
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
105
|
-
self.Length = @rawdata.length
|
|
106
|
-
|
|
107
|
-
super
|
|
108
|
-
end
|
|
40
|
+
TOKENS = [ "stream" + WHITECHARS_NORET + "\\r?\\n", "endstream" ] #:nodoc:
|
|
109
41
|
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
179
|
-
best_type = Stream
|
|
45
|
+
@@cast_fingerprints = {}
|
|
180
46
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
|
|
58
|
+
CCITTFaxDecode
|
|
59
|
+
JBIG2Decode
|
|
60
|
+
DCTDecode
|
|
61
|
+
JPXDecode
|
|
189
62
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
63
|
+
AHx
|
|
64
|
+
A85
|
|
65
|
+
LZW
|
|
66
|
+
Fl
|
|
67
|
+
RL
|
|
68
|
+
CCF
|
|
69
|
+
DCT
|
|
70
|
+
]
|
|
194
71
|
|
|
195
|
-
|
|
196
|
-
raise InvalidStreamObjectError, 'Predictor functions can only be used with Flate or LZW filters'
|
|
197
|
-
end
|
|
72
|
+
attr_reader :dictionary
|
|
198
73
|
|
|
199
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
self
|
|
210
|
-
end
|
|
90
|
+
set_indirect(true)
|
|
211
91
|
|
|
212
|
-
|
|
213
|
-
|
|
92
|
+
@encoded_data = nil
|
|
93
|
+
@dictionary, @data = Dictionary.new(dictionary), data
|
|
94
|
+
@dictionary.parent = self
|
|
95
|
+
end
|
|
214
96
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
223
|
-
|
|
101
|
+
@dictionary
|
|
102
|
+
end
|
|
224
103
|
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
rescue Filter::
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
self
|
|
302
341
|
end
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
370
|
+
# Skip the Crypt filter.
|
|
371
|
+
next
|
|
372
|
+
end
|
|
371
373
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
end
|
|
374
|
+
@encoded_data = encode_data(@encoded_data, filter, params)
|
|
375
|
+
end
|
|
375
376
|
|
|
376
|
-
|
|
377
|
-
not @rawdata.nil?
|
|
378
|
-
end
|
|
377
|
+
self.Length = @encoded_data.length
|
|
379
378
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
unless dparms.is_a? ::Array
|
|
383
|
-
@dictionary[:DecodeParms] = dparms = []
|
|
384
|
-
end
|
|
379
|
+
self
|
|
380
|
+
end
|
|
385
381
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
end
|
|
382
|
+
def to_s(indent: 1, tab: "\t") #:nodoc:
|
|
383
|
+
content = ""
|
|
389
384
|
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
394
|
-
|
|
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
|
-
|
|
402
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
class ExternalStream < Stream
|
|
397
|
+
def []=(key,val) #:nodoc:
|
|
398
|
+
@dictionary[key] = val
|
|
399
|
+
end
|
|
424
400
|
|
|
425
|
-
|
|
401
|
+
def each_key(&b) #:nodoc:
|
|
402
|
+
@dictionary.each_key(&b)
|
|
403
|
+
end
|
|
426
404
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
405
|
+
def key?(name)
|
|
406
|
+
@dictionary.key?(name)
|
|
407
|
+
end
|
|
408
|
+
alias has_key? key?
|
|
430
409
|
|
|
431
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
523
|
-
|
|
423
|
+
def decoded? #:nodoc:
|
|
424
|
+
not @data.nil?
|
|
425
|
+
end
|
|
524
426
|
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
534
|
-
|
|
431
|
+
def each_decode_params
|
|
432
|
+
params = self.DecodeParms
|
|
535
433
|
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
561
|
-
# _no_:: The Object number.
|
|
498
|
+
# Class representing an external Stream.
|
|
562
499
|
#
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
#
|
|
512
|
+
# Class representing a Stream containing other Objects.
|
|
580
513
|
#
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
734
|
+
|
|
618
735
|
end
|