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/obfuscation.rb
CHANGED
|
@@ -1,233 +1,245 @@
|
|
|
1
|
-
|
|
1
|
+
=begin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
FILTERS = [ :FlateDecode, :RunLengthDecode, :LZWDecode, :ASCIIHexDecode, :ASCII85Decode ]
|
|
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
10
|
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
13
15
|
|
|
14
|
-
|
|
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/>.
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
length = rand(max_size) + 1
|
|
19
|
+
=end
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
byte = rand(256).chr until (not byte.nil? and byte != "\n" and byte != "\r"); byte
|
|
22
|
-
}.join
|
|
23
|
-
|
|
24
|
-
"%#{junk_comment}#{EOL}"
|
|
25
|
-
end
|
|
21
|
+
module Origami
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
module Obfuscator
|
|
24
|
+
using TypeConversion
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
WHITECHARS = [ " ", "\t", "\r", "\n", "\0" ]
|
|
27
|
+
OBJECTS = [ Array, Boolean, Dictionary, Integer, Name, Null, Stream, String, Real, Reference ]
|
|
28
|
+
MAX_INT = 0xFFFFFFFF
|
|
29
|
+
PRINTABLE = ("!".."9").to_a + (':'..'Z').to_a + ('['..'z').to_a + ('{'..'~').to_a
|
|
30
|
+
FILTERS = [ :FlateDecode, :RunLengthDecode, :LZWDecode, :ASCIIHexDecode, :ASCII85Decode ]
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
32
|
+
def self.junk_spaces(max_size = 3)
|
|
33
|
+
length = rand(max_size) + 1
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
::Array.new(length) { WHITECHARS[rand(WHITECHARS.size)] }.join
|
|
36
|
+
end
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
def self.junk_comment(max_size = 15)
|
|
39
|
+
length = rand(max_size) + 1
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
41
|
+
junk_comment = ::Array.new(length) {
|
|
42
|
+
byte = rand(256).chr until (not byte.nil? and byte != "\n" and byte != "\r"); byte
|
|
43
|
+
}.join
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
45
|
+
"%#{junk_comment}#{EOL}"
|
|
46
|
+
end
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
def self.junk_object(type = nil)
|
|
49
|
+
if type.nil?
|
|
50
|
+
type = OBJECTS[rand(OBJECTS.size)]
|
|
51
|
+
end
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
hash[Obfuscator.junk_name] = obj unless obj.is_a?(Stream)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
hash.to_o
|
|
62
|
-
end
|
|
53
|
+
unless type.include?(Origami::Object)
|
|
54
|
+
raise TypeError, "Not a valid object type"
|
|
55
|
+
end
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
end
|
|
57
|
+
Obfuscator.send("junk_#{type.to_s.split('::').last.downcase}")
|
|
58
|
+
end
|
|
67
59
|
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
def self.junk_array(max_size = 5)
|
|
61
|
+
length = rand(max_size) + 1
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
::Array.new(length) {
|
|
64
|
+
obj = Obfuscator.junk_object until (not obj.nil? and not obj.is_a?(Stream)) ; obj
|
|
65
|
+
}.to_o
|
|
66
|
+
end
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
def self.junk_boolean
|
|
69
|
+
Boolean.new(rand(2).zero?)
|
|
70
|
+
end
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
chainlen = rand(2) + 1
|
|
81
|
-
chain = ::Array.new(chainlen) { FILTERS[rand(FILTERS.size)] }
|
|
72
|
+
def self.junk_dictionary(max_size = 5)
|
|
73
|
+
length = rand(max_size) + 1
|
|
82
74
|
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
hash = Hash.new
|
|
76
|
+
length.times do
|
|
77
|
+
obj = Obfuscator.junk_object
|
|
78
|
+
hash[Obfuscator.junk_name] = obj unless obj.is_a?(Stream)
|
|
79
|
+
end
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
stm.setFilter(chain)
|
|
89
|
-
stm.data = junk_data
|
|
81
|
+
hash.to_o
|
|
82
|
+
end
|
|
90
83
|
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
def self.junk_integer(max = MAX_INT)
|
|
85
|
+
Integer.new(rand(max + 1))
|
|
86
|
+
end
|
|
93
87
|
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
def self.junk_name(max_size = 8)
|
|
89
|
+
length = rand(max_size) + 1
|
|
96
90
|
|
|
97
|
-
|
|
91
|
+
Name.new(::Array.new(length) { PRINTABLE[rand(PRINTABLE.size)] }.join)
|
|
92
|
+
end
|
|
98
93
|
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
def self.junk_null
|
|
95
|
+
Null.new
|
|
96
|
+
end
|
|
101
97
|
|
|
102
|
-
|
|
103
|
-
Real.new(rand * rand(MAX_INT + 1))
|
|
104
|
-
end
|
|
98
|
+
def self.junk_stream(max_data_size = 200)
|
|
105
99
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
gen = rand(max_gen)
|
|
100
|
+
chainlen = rand(2) + 1
|
|
101
|
+
chain = ::Array.new(chainlen) { FILTERS[rand(FILTERS.size)] }
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
length = rand(max_data_size) + 1
|
|
104
|
+
junk_data = ::Array.new(length) { rand(256).chr }.join
|
|
112
105
|
|
|
113
|
-
|
|
106
|
+
stm = Stream.new
|
|
107
|
+
stm.dictionary = Obfuscator.junk_dictionary(5)
|
|
108
|
+
stm.setFilter(chain)
|
|
109
|
+
stm.data = junk_data
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def to_obfuscated_str
|
|
118
|
-
content = TOKENS.first + Obfuscator.junk_spaces
|
|
119
|
-
self.each_pair { |key, value|
|
|
120
|
-
content << Obfuscator.junk_spaces +
|
|
121
|
-
key.to_obfuscated_str + Obfuscator.junk_spaces +
|
|
122
|
-
value.to_obfuscated_str + Obfuscator.junk_spaces
|
|
123
|
-
}
|
|
111
|
+
stm
|
|
112
|
+
end
|
|
124
113
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
end
|
|
114
|
+
def self.junk_string(max_size = 10)
|
|
115
|
+
length = rand(max_size) + 1
|
|
128
116
|
|
|
129
|
-
|
|
117
|
+
strtype = (rand(2).zero?) ? LiteralString : HexaString
|
|
130
118
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
end
|
|
119
|
+
strtype.new(::Array.new(length) { PRINTABLE[rand(PRINTABLE.size)] }.join)
|
|
120
|
+
end
|
|
134
121
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self.each { |entry|
|
|
139
|
-
content << entry.to_o.to_obfuscated_str + Obfuscator.junk_spaces
|
|
140
|
-
}
|
|
122
|
+
def self.junk_real
|
|
123
|
+
Real.new(rand * rand(MAX_INT + 1))
|
|
124
|
+
end
|
|
141
125
|
|
|
142
|
-
|
|
126
|
+
def self.junk_reference(max_no = 300, max_gen = 1)
|
|
127
|
+
no = rand(max_no) + 1
|
|
128
|
+
gen = rand(max_gen)
|
|
143
129
|
|
|
144
|
-
|
|
130
|
+
Reference.new(no, gen)
|
|
131
|
+
end
|
|
145
132
|
end
|
|
146
|
-
end
|
|
147
133
|
|
|
148
|
-
|
|
149
|
-
alias :to_obfuscated_str :to_s
|
|
150
|
-
end
|
|
134
|
+
class Dictionary
|
|
151
135
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
136
|
+
def to_obfuscated_str
|
|
137
|
+
content = TOKENS.first + Obfuscator.junk_spaces
|
|
138
|
+
self.each_pair do |key, value|
|
|
139
|
+
content << Obfuscator.junk_spaces +
|
|
140
|
+
key.to_obfuscated_str + Obfuscator.junk_spaces +
|
|
141
|
+
value.to_obfuscated_str + Obfuscator.junk_spaces
|
|
142
|
+
end
|
|
155
143
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
144
|
+
content << TOKENS.last
|
|
145
|
+
super(content)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
159
148
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
149
|
+
module Object
|
|
150
|
+
alias :to_obfuscated_str :to_s
|
|
151
|
+
end
|
|
163
152
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
153
|
+
class Array
|
|
154
|
+
def to_obfuscated_str
|
|
155
|
+
content = TOKENS.first + Obfuscator.junk_spaces
|
|
156
|
+
self.each do |entry|
|
|
157
|
+
content << entry.to_o.to_obfuscated_str + Obfuscator.junk_spaces
|
|
158
|
+
end
|
|
167
159
|
|
|
168
|
-
|
|
160
|
+
content << TOKENS.last
|
|
161
|
+
|
|
162
|
+
super(content)
|
|
163
|
+
end
|
|
169
164
|
end
|
|
170
|
-
end
|
|
171
165
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
to_s
|
|
166
|
+
class Null
|
|
167
|
+
alias :to_obfuscated_str :to_s
|
|
175
168
|
end
|
|
176
|
-
end
|
|
177
169
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
to_s
|
|
170
|
+
class Boolean
|
|
171
|
+
alias :to_obfuscated_str :to_s
|
|
181
172
|
end
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def to_obfuscated_str(prop = 2)
|
|
186
|
-
name = @value.dup
|
|
187
|
-
|
|
188
|
-
forbiddenchars = [ " ","#","\t","\r","\n","\0","[","]","<",">","(",")","%","/","\\" ]
|
|
189
|
-
|
|
190
|
-
name.gsub!(/./) do |c|
|
|
191
|
-
if rand(prop) == 0 or forbiddenchars.include?(c)
|
|
192
|
-
hexchar = c[0].to_s(base=16)
|
|
193
|
-
hexchar = "0" + hexchar if hexchar.length < 2
|
|
194
|
-
|
|
195
|
-
'#' + hexchar
|
|
196
|
-
else
|
|
197
|
-
c
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
super(TOKENS.first + name)
|
|
173
|
+
|
|
174
|
+
class Integer
|
|
175
|
+
alias :to_obfuscated_str :to_s
|
|
202
176
|
end
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def to_obfuscated_str
|
|
207
|
-
content = ""
|
|
208
|
-
|
|
209
|
-
content << @dictionary.to_obfuscated_str
|
|
210
|
-
content << "stream" + EOL
|
|
211
|
-
content << self.rawdata
|
|
212
|
-
content << EOL << TOKENS.last
|
|
213
|
-
|
|
214
|
-
super(content)
|
|
177
|
+
|
|
178
|
+
class Real
|
|
179
|
+
alias :to_obfuscated_str :to_s
|
|
215
180
|
end
|
|
216
|
-
end
|
|
217
181
|
|
|
218
|
-
|
|
182
|
+
class Reference
|
|
183
|
+
def to_obfuscated_str
|
|
184
|
+
refstr = refno.to_s + Obfuscator.junk_spaces + refgen.to_s + Obfuscator.junk_spaces + "R"
|
|
219
185
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
content << TOKENS.first << EOL << @dictionary.to_obfuscated_str << EOL
|
|
224
|
-
end
|
|
186
|
+
super(refstr)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
225
189
|
|
|
226
|
-
|
|
190
|
+
class LiteralString
|
|
191
|
+
alias :to_obfuscated_str :to_s
|
|
192
|
+
end
|
|
227
193
|
|
|
228
|
-
|
|
194
|
+
class HexaString
|
|
195
|
+
alias :to_obfuscated_str :to_s
|
|
229
196
|
end
|
|
230
197
|
|
|
231
|
-
|
|
198
|
+
class Name
|
|
199
|
+
def to_obfuscated_str(prop = 2)
|
|
200
|
+
name = @value.dup
|
|
201
|
+
|
|
202
|
+
forbiddenchars = [ " ","#","\t","\r","\n","\0","[","]","<",">","(",")","%","/","\\" ]
|
|
203
|
+
|
|
204
|
+
name.gsub!(/./) do |c|
|
|
205
|
+
if rand(prop) == 0 or forbiddenchars.include?(c)
|
|
206
|
+
hexchar = c.ord.to_s(16)
|
|
207
|
+
hexchar = "0" + hexchar if hexchar.length < 2
|
|
208
|
+
|
|
209
|
+
'#' + hexchar
|
|
210
|
+
else
|
|
211
|
+
c
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
super(TOKENS.first + name)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
class Stream
|
|
220
|
+
def to_obfuscated_str
|
|
221
|
+
content = ""
|
|
222
|
+
|
|
223
|
+
content << @dictionary.to_obfuscated_str
|
|
224
|
+
content << "stream" + EOL
|
|
225
|
+
content << self.encoded_data
|
|
226
|
+
content << EOL << TOKENS.last
|
|
227
|
+
|
|
228
|
+
super(content)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
class Trailer
|
|
233
|
+
def to_obfuscated_str
|
|
234
|
+
content = ""
|
|
235
|
+
if self.has_dictionary?
|
|
236
|
+
content << TOKENS.first << EOL << @dictionary.to_obfuscated_str << EOL
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
content << XREF_TOKEN << EOL << @startxref.to_s << EOL << TOKENS.last << EOL
|
|
240
|
+
|
|
241
|
+
content
|
|
242
|
+
end
|
|
243
|
+
end
|
|
232
244
|
|
|
233
245
|
end
|
data/lib/origami/object.rb
CHANGED
|
@@ -1,645 +1,665 @@
|
|
|
1
1
|
=begin
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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é.
|
|
23
5
|
|
|
24
|
-
|
|
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.
|
|
25
10
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
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.
|
|
31
15
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Origami::Integer.new(self)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
class Array #:nodoc:
|
|
39
|
-
def to_o
|
|
40
|
-
Origami::Array.new(self)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
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/>.
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
def to_o
|
|
46
|
-
Origami::Real.new(self)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
19
|
+
=end
|
|
49
20
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
end
|
|
21
|
+
#
|
|
22
|
+
# Module for parsing/generating PDF files.
|
|
23
|
+
#
|
|
24
|
+
module Origami
|
|
55
25
|
|
|
56
|
-
|
|
57
|
-
def to_o
|
|
58
|
-
Origami::Boolean.new(true)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
26
|
+
module TypeConversion
|
|
61
27
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
end
|
|
28
|
+
refine ::Bignum do
|
|
29
|
+
def to_o
|
|
30
|
+
Origami::Integer.new(self)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
67
33
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
end
|
|
34
|
+
refine ::Fixnum do
|
|
35
|
+
def to_o
|
|
36
|
+
Origami::Integer.new(self)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
73
39
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def value
|
|
80
|
-
self
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
end
|
|
40
|
+
refine ::Array do
|
|
41
|
+
def to_o
|
|
42
|
+
Origami::Array.new(self)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
84
45
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
46
|
+
refine ::Float do
|
|
47
|
+
def to_o
|
|
48
|
+
Origami::Real.new(self)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
89
51
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
end
|
|
52
|
+
refine ::Hash do
|
|
53
|
+
def to_o
|
|
54
|
+
Origami::Dictionary.new(self)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
94
57
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
58
|
+
refine ::TrueClass do
|
|
59
|
+
def to_o
|
|
60
|
+
Origami::Boolean.new(true)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
99
63
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
64
|
+
refine ::FalseClass do
|
|
65
|
+
def to_o
|
|
66
|
+
Origami::Boolean.new(false)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
104
69
|
|
|
105
|
-
|
|
70
|
+
refine ::NilClass do
|
|
71
|
+
def to_o
|
|
72
|
+
Origami::Null.new
|
|
73
|
+
end
|
|
74
|
+
end
|
|
106
75
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
76
|
+
refine ::Symbol do
|
|
77
|
+
def to_o
|
|
78
|
+
Origami::Name.new(self)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
111
81
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def fields
|
|
119
|
-
@fields
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def field(name, attributes)
|
|
123
|
-
if attributes[:Required] == true and attributes.has_key?(:Default) and attributes[:Type] == Name
|
|
124
|
-
self.add_type_info(self, name, attributes[:Default])
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
if not @fields.has_key?(name)
|
|
128
|
-
@fields[name] = attributes
|
|
129
|
-
else
|
|
130
|
-
@fields[name].merge! attributes
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
define_field_methods(name)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def define_field_methods(field)
|
|
137
|
-
reader = lambda { obj = self[field]; obj.is_a?(Reference) ? obj.solve : obj }
|
|
138
|
-
writer = lambda { |value| self[field] = value }
|
|
139
|
-
set = lambda { |value| self[field] = value; self }
|
|
140
|
-
|
|
141
|
-
send(:define_method, field.id2name, reader)
|
|
142
|
-
send(:define_method, field.id2name + "=", writer)
|
|
143
|
-
send(:define_method, "set" + field.id2name, set)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
#
|
|
147
|
-
# Returns an array of required fields for the current Object.
|
|
148
|
-
#
|
|
149
|
-
def required_fields
|
|
150
|
-
fields = []
|
|
151
|
-
@fields.each_pair { |name, attributes|
|
|
152
|
-
fields << name if attributes[:Required] == true
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
fields
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def hint_type(name)
|
|
159
|
-
if @fields.has_key?(name)
|
|
160
|
-
@fields[name][:Type]
|
|
161
|
-
end
|
|
162
|
-
end
|
|
82
|
+
refine ::String do
|
|
83
|
+
def to_o
|
|
84
|
+
Origami::LiteralString.new(self)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
163
87
|
end
|
|
164
88
|
|
|
165
|
-
def pre_build #:nodoc:
|
|
166
|
-
|
|
167
|
-
set_default_values
|
|
168
|
-
do_type_check if Origami::OPTIONS[:enable_type_checking] == true
|
|
169
|
-
|
|
170
|
-
super
|
|
171
|
-
end
|
|
172
|
-
|
|
173
89
|
#
|
|
174
|
-
#
|
|
175
|
-
# _attr_:: The attribute name.
|
|
90
|
+
# Common Exception class for Origami errors.
|
|
176
91
|
#
|
|
177
|
-
|
|
178
|
-
not self[field].nil?
|
|
92
|
+
class Error < StandardError
|
|
179
93
|
end
|
|
180
94
|
|
|
181
95
|
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
def pdf_version_required #:nodoc:
|
|
185
|
-
max = [ 1.0, 0 ]
|
|
186
|
-
|
|
187
|
-
self.each_key do |field|
|
|
188
|
-
attributes = self.class.fields[field.value]
|
|
189
|
-
|
|
190
|
-
current_version = attributes.has_key?(:Version) ? attributes[:Version].to_f : 0
|
|
191
|
-
current_level = attributes[:ExtensionLevel] || 0
|
|
192
|
-
current = [ current_version, current_level ]
|
|
193
|
-
|
|
194
|
-
max = current if (current <=> max) > 0
|
|
195
|
-
|
|
196
|
-
sub = self[field.value].pdf_version_required
|
|
197
|
-
max = sub if (sub <=> max) > 0
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
max
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def set_default_value(field) #:nodoc:
|
|
204
|
-
if self.class.fields[field][:Default]
|
|
205
|
-
self[field] = self.class.fields[field][:Default]
|
|
206
|
-
self[field].pre_build
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def set_default_values #:nodoc:
|
|
211
|
-
self.class.required_fields.each do |field|
|
|
212
|
-
set_default_value(field) unless has_field?(field)
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
def do_type_check #:nodoc:
|
|
217
|
-
self.class.fields.each_pair do |field, attributes|
|
|
218
|
-
|
|
219
|
-
if not self[field].nil? and not attributes[:Type].nil?
|
|
220
|
-
types = attributes[:Type].is_a?(::Array) ? attributes[:Type] : [ attributes[:Type] ]
|
|
221
|
-
if not self[field].is_a?(Reference) and types.all? {|type| not self[field].is_a?(type.native_type)}
|
|
222
|
-
puts "Warning: in object #{self.class}, field `#{field.to_s}' has unexpected type #{self[field].class}"
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
class InvalidObjectError < Exception #:nodoc:
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
class UnterminatedObjectError < Exception #:nodoc:
|
|
234
|
-
attr_reader :obj
|
|
235
|
-
def initialize(msg,obj)
|
|
236
|
-
super(msg)
|
|
237
|
-
@obj = obj
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
WHITESPACES = "([ \\f\\t\\r\\n\\0]|%[^\\n]*\\n)*" #:nodoc:
|
|
242
|
-
WHITECHARS_NORET = "[ \\f\\t\\0]*" #:nodoc:
|
|
243
|
-
EOL = "\r\n" #:nodoc:
|
|
244
|
-
WHITECHARS = "[ \\f\\t\\r\\n\\0]*" #:nodoc:
|
|
245
|
-
REGEXP_WHITESPACES = Regexp.new(WHITESPACES) #:nodoc:
|
|
246
|
-
|
|
247
|
-
#
|
|
248
|
-
# Parent module representing a PDF Object.
|
|
249
|
-
# PDF specification declares a set of primitive object types :
|
|
250
|
-
# * Null
|
|
251
|
-
# * Boolean
|
|
252
|
-
# * Integer
|
|
253
|
-
# * Real
|
|
254
|
-
# * Name
|
|
255
|
-
# * String
|
|
256
|
-
# * Array
|
|
257
|
-
# * Dictionary
|
|
258
|
-
# * Stream
|
|
259
|
-
#
|
|
260
|
-
module Object
|
|
261
|
-
|
|
262
|
-
TOKENS = %w{ obj endobj } #:nodoc:
|
|
263
|
-
@@regexp_obj = Regexp.new(WHITESPACES + "(\\d+)" + WHITESPACES + "(\\d+)" + WHITESPACES + TOKENS.first + WHITESPACES)
|
|
264
|
-
@@regexp_endobj = Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)
|
|
265
|
-
|
|
266
|
-
attr_accessor :no, :generation, :file_offset, :objstm_offset
|
|
267
|
-
attr_accessor :parent
|
|
268
|
-
|
|
269
|
-
#
|
|
270
|
-
# Creates a new PDF Object.
|
|
271
|
-
#
|
|
272
|
-
def initialize(*cons)
|
|
273
|
-
@indirect = false
|
|
274
|
-
@no, @generation = 0, 0
|
|
275
|
-
|
|
276
|
-
super(*cons) unless cons.empty?
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
#
|
|
280
|
-
# Sets whether the object is indirect or not.
|
|
281
|
-
# Indirect objects are allocated numbers at build time.
|
|
282
|
-
#
|
|
283
|
-
def set_indirect(bool)
|
|
284
|
-
unless bool == true or bool == false
|
|
285
|
-
raise TypeError, "The argument must be boolean"
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
if not bool
|
|
289
|
-
@no = @generation = 0
|
|
290
|
-
@pdf = nil
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
@indirect = bool
|
|
294
|
-
self
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
#
|
|
298
|
-
# Generic method called just before the object is finalized.
|
|
299
|
-
# At this time, no number nor generation allocation has yet been done.
|
|
96
|
+
# Mixin' module for objects which can store their options into an inner Dictionary.
|
|
300
97
|
#
|
|
301
|
-
|
|
302
|
-
self
|
|
303
|
-
end
|
|
98
|
+
module StandardObject #:nodoc:
|
|
304
99
|
|
|
305
|
-
|
|
306
|
-
# Generic method called just after the object is finalized.
|
|
307
|
-
# At this time, any indirect object has its own number and generation identifier.
|
|
308
|
-
#
|
|
309
|
-
def post_build
|
|
310
|
-
self
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
#
|
|
314
|
-
# Compare two objects from their respective numbers.
|
|
315
|
-
#
|
|
316
|
-
def <=>(obj)
|
|
317
|
-
[@no, @generation] <=> [obj.no, obj.generation]
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
#
|
|
321
|
-
# Returns whether the objects is indirect, which means that it is not embedded into another object.
|
|
322
|
-
#
|
|
323
|
-
def is_indirect?
|
|
324
|
-
@indirect
|
|
325
|
-
end
|
|
100
|
+
DEFAULT_ATTRIBUTES = { :Type => Object, :Version => "1.2" } #:nodoc:
|
|
326
101
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
saved_pdf = @pdf
|
|
332
|
-
saved_parent = @parent
|
|
333
|
-
|
|
334
|
-
saved_xref_cache = @xref_cache
|
|
335
|
-
@pdf = @parent = nil # do not process parent object and document in the copy
|
|
102
|
+
def self.included(receiver) #:nodoc:
|
|
103
|
+
receiver.instance_variable_set(:@fields, Hash.new(DEFAULT_ATTRIBUTES))
|
|
104
|
+
receiver.extend(ClassMethods)
|
|
105
|
+
end
|
|
336
106
|
|
|
337
|
-
|
|
338
|
-
copyobj = Marshal.load(Marshal.dump(self))
|
|
107
|
+
module ClassMethods #:nodoc:all
|
|
339
108
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
109
|
+
def inherited(subclass)
|
|
110
|
+
subclass.instance_variable_set(:@fields, Hash[@fields.map{|name, attributes| [name, attributes.clone]}])
|
|
111
|
+
end
|
|
343
112
|
|
|
344
|
-
|
|
345
|
-
|
|
113
|
+
def fields
|
|
114
|
+
@fields
|
|
115
|
+
end
|
|
346
116
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
# Returns an indirect reference to this object, or a Null object is this object is not indirect.
|
|
352
|
-
#
|
|
353
|
-
def reference
|
|
354
|
-
unless self.is_indirect?
|
|
355
|
-
raise InvalidObjectError, "Cannot reference a direct object"
|
|
356
|
-
end
|
|
117
|
+
def field(name, attributes)
|
|
118
|
+
if attributes[:Required] == true and attributes.has_key?(:Default) and attributes[:Type] == Name
|
|
119
|
+
self.add_type_info(self, name, attributes[:Default])
|
|
120
|
+
end
|
|
357
121
|
|
|
358
|
-
|
|
359
|
-
|
|
122
|
+
if @fields.has_key?(name)
|
|
123
|
+
@fields[name].merge! attributes
|
|
124
|
+
else
|
|
125
|
+
@fields[name] = attributes
|
|
126
|
+
end
|
|
360
127
|
|
|
361
|
-
|
|
362
|
-
|
|
128
|
+
define_field_methods(name)
|
|
129
|
+
end
|
|
363
130
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
131
|
+
def define_field_methods(field)
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# Getter method.
|
|
135
|
+
#
|
|
136
|
+
getter = field.to_s
|
|
137
|
+
remove_method(getter) rescue NameError
|
|
138
|
+
define_method(getter) do
|
|
139
|
+
obj = self[field]
|
|
140
|
+
obj.is_a?(Reference) ? obj.solve : obj
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
#
|
|
144
|
+
# Setter method.
|
|
145
|
+
#
|
|
146
|
+
setter = field.to_s + "="
|
|
147
|
+
remove_method(setter) rescue NameError
|
|
148
|
+
define_method(setter) do |value|
|
|
149
|
+
self[field] = value
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Setter method returning self.
|
|
153
|
+
setter_self = "set" + field.to_s
|
|
154
|
+
remove_method(setter_self) rescue NameError
|
|
155
|
+
define_method(setter_self) do |value|
|
|
156
|
+
self[field] = value
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
# Returns an array of required fields for the current Object.
|
|
163
|
+
#
|
|
164
|
+
def required_fields
|
|
165
|
+
fields = []
|
|
166
|
+
@fields.each_pair do |name, attributes|
|
|
167
|
+
fields << name if attributes[:Required] == true
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
fields
|
|
382
171
|
end
|
|
383
172
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
173
|
+
def hint_type(name)
|
|
174
|
+
if @fields.has_key?(name)
|
|
175
|
+
@fields[name][:Type]
|
|
176
|
+
end
|
|
387
177
|
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def pre_build #:nodoc:
|
|
181
|
+
set_default_values
|
|
182
|
+
do_type_check if Origami::OPTIONS[:enable_type_checking] == true
|
|
183
|
+
|
|
184
|
+
super
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
#
|
|
188
|
+
# Check if an attribute is set in the current Object.
|
|
189
|
+
# _attr_:: The attribute name.
|
|
190
|
+
#
|
|
191
|
+
def has_field? (field)
|
|
192
|
+
not self[field].nil?
|
|
193
|
+
end
|
|
388
194
|
|
|
389
|
-
|
|
390
|
-
|
|
195
|
+
#
|
|
196
|
+
# Returns the version and level required by the current Object.
|
|
197
|
+
#
|
|
198
|
+
def version_required #:nodoc:
|
|
199
|
+
max = [ 1.0, 0 ]
|
|
200
|
+
|
|
201
|
+
self.each_key do |field|
|
|
202
|
+
attributes = self.class.fields[field.value]
|
|
203
|
+
if attributes.nil?
|
|
204
|
+
STDERR.puts "Warning: object #{self.class} has undocumented field #{field.value}"
|
|
205
|
+
next
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
current_version = attributes.has_key?(:Version) ? attributes[:Version].to_f : 0
|
|
209
|
+
current_level = attributes[:ExtensionLevel] || 0
|
|
210
|
+
current = [ current_version, current_level ]
|
|
211
|
+
|
|
212
|
+
max = current if (current <=> max) > 0
|
|
213
|
+
|
|
214
|
+
sub = self[field.value].version_required
|
|
215
|
+
max = sub if (sub <=> max) > 0
|
|
391
216
|
end
|
|
217
|
+
|
|
218
|
+
max
|
|
392
219
|
end
|
|
393
|
-
end
|
|
394
220
|
|
|
395
|
-
|
|
221
|
+
def set_default_value(field) #:nodoc:
|
|
222
|
+
if self.class.fields[field][:Default]
|
|
223
|
+
self[field] = self.class.fields[field][:Default]
|
|
224
|
+
self[field].pre_build
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def set_default_values #:nodoc:
|
|
229
|
+
self.class.required_fields.each do |field|
|
|
230
|
+
set_default_value(field) unless has_field?(field)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def do_type_check #:nodoc:
|
|
235
|
+
self.class.fields.each_pair do |field, attributes|
|
|
236
|
+
next if self[field].nil? or attributes[:Type].nil?
|
|
237
|
+
|
|
238
|
+
begin
|
|
239
|
+
field_value = self[field].solve
|
|
240
|
+
rescue InvalidReferenceError
|
|
241
|
+
STDERR.puts "Warning: in object #{self.class}, field `#{field.to_s}' is an invalid reference (#{self[field].to_s})"
|
|
242
|
+
next
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
types = attributes[:Type].is_a?(::Array) ? attributes[:Type] : [ attributes[:Type] ]
|
|
246
|
+
|
|
247
|
+
unless types.any? {|type| not type.is_a?(Class) or field_value.is_a?(type.native_type)}
|
|
248
|
+
STDERR.puts "Warning: in object #{self.class}, field `#{field.to_s}' has unexpected type #{field_value.class}"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
if attributes.key?(:Assert) and not (attributes[:Assert] === field_value)
|
|
252
|
+
STDERR.puts "Warning: assertion failed for field `#{field.to_s}' in object #{self.class}"
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
396
256
|
end
|
|
397
257
|
|
|
398
|
-
|
|
399
|
-
# Creates an exportable version of current object.
|
|
400
|
-
# The exportable version is a copy of _self_ with solved references, no owning PDF and no parent.
|
|
401
|
-
# References to Catalog or PageTreeNode objects have been destroyed.
|
|
402
|
-
#
|
|
403
|
-
# When exported, an object can be moved into another document without hassle.
|
|
404
|
-
#
|
|
405
|
-
def export
|
|
406
|
-
exported_obj = self.logicalize
|
|
407
|
-
exported_obj.no = exported_obj.generation = 0
|
|
408
|
-
exported_obj.set_pdf(nil) if exported_obj.is_indirect?
|
|
409
|
-
exported_obj.parent = nil
|
|
410
|
-
exported_obj.xref_cache.clear
|
|
411
|
-
|
|
412
|
-
exported_obj
|
|
258
|
+
class InvalidObjectError < Error #:nodoc:
|
|
413
259
|
end
|
|
414
260
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
261
|
+
class UnterminatedObjectError < Error #:nodoc:
|
|
262
|
+
attr_reader :obj
|
|
263
|
+
|
|
264
|
+
def initialize(msg,obj)
|
|
265
|
+
super(msg)
|
|
266
|
+
@obj = obj
|
|
267
|
+
end
|
|
421
268
|
end
|
|
422
269
|
|
|
270
|
+
WHITESPACES = "([ \\f\\t\\r\\n\\0]|%[^\\n]*\\n)*" #:nodoc:
|
|
271
|
+
WHITECHARS_NORET = "[ \\f\\t\\0]*" #:nodoc:
|
|
272
|
+
EOL = "\r\n" #:nodoc:
|
|
273
|
+
WHITECHARS = "[ \\f\\t\\r\\n\\0]*" #:nodoc:
|
|
274
|
+
REGEXP_WHITESPACES = Regexp.new(WHITESPACES) #:nodoc:
|
|
275
|
+
|
|
423
276
|
#
|
|
424
|
-
#
|
|
425
|
-
#
|
|
277
|
+
# Parent module representing a PDF Object.
|
|
278
|
+
# PDF specification declares a set of primitive object types :
|
|
279
|
+
# * Null
|
|
280
|
+
# * Boolean
|
|
281
|
+
# * Integer
|
|
282
|
+
# * Real
|
|
283
|
+
# * Name
|
|
284
|
+
# * String
|
|
285
|
+
# * Array
|
|
286
|
+
# * Dictionary
|
|
287
|
+
# * Stream
|
|
426
288
|
#
|
|
427
|
-
|
|
289
|
+
module Object
|
|
428
290
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
291
|
+
TOKENS = %w{ obj endobj } #:nodoc:
|
|
292
|
+
@@regexp_obj = Regexp.new(WHITESPACES + "(?<no>\\d+)" + WHITESPACES + "(?<gen>\\d+)" +
|
|
293
|
+
WHITESPACES + TOKENS.first + WHITESPACES)
|
|
294
|
+
@@regexp_endobj = Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)
|
|
432
295
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
296
|
+
attr_accessor :no, :generation, :file_offset, :objstm_offset
|
|
297
|
+
attr_accessor :parent
|
|
298
|
+
|
|
299
|
+
#
|
|
300
|
+
# Creates a new PDF Object.
|
|
301
|
+
#
|
|
302
|
+
def initialize(*cons)
|
|
303
|
+
@indirect = false
|
|
304
|
+
@no, @generation = 0, 0
|
|
305
|
+
@document = nil
|
|
306
|
+
@parent = nil
|
|
307
|
+
|
|
308
|
+
super(*cons) unless cons.empty?
|
|
437
309
|
end
|
|
438
310
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
311
|
+
#
|
|
312
|
+
# Sets whether the object is indirect or not.
|
|
313
|
+
# Indirect objects are allocated numbers at build time.
|
|
314
|
+
#
|
|
315
|
+
def set_indirect(bool)
|
|
316
|
+
unless bool == true or bool == false
|
|
317
|
+
raise TypeError, "The argument must be boolean"
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
if bool == false
|
|
321
|
+
@no = @generation = 0
|
|
322
|
+
@document = nil
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
@indirect = bool
|
|
326
|
+
self
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
#
|
|
330
|
+
# Generic method called just before the object is finalized.
|
|
331
|
+
# At this time, no number nor generation allocation has yet been done.
|
|
332
|
+
#
|
|
333
|
+
def pre_build
|
|
334
|
+
self
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
#
|
|
338
|
+
# Generic method called just after the object is finalized.
|
|
339
|
+
# At this time, any indirect object has its own number and generation identifier.
|
|
340
|
+
#
|
|
341
|
+
def post_build
|
|
342
|
+
self
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
#
|
|
346
|
+
# Compare two objects from their respective numbers.
|
|
347
|
+
#
|
|
348
|
+
def <=>(obj)
|
|
349
|
+
[@no, @generation] <=> [obj.no, obj.generation]
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
#
|
|
353
|
+
# Returns whether the objects is indirect, which means that it is not embedded into another object.
|
|
354
|
+
#
|
|
355
|
+
def indirect?
|
|
356
|
+
@indirect
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
#
|
|
360
|
+
# Deep copy of an object.
|
|
361
|
+
#
|
|
362
|
+
def copy
|
|
363
|
+
saved_doc = @document
|
|
364
|
+
saved_parent = @parent
|
|
365
|
+
|
|
366
|
+
@document = @parent = nil # do not process parent object and document in the copy
|
|
367
|
+
|
|
368
|
+
# Perform the recursive copy (quite dirty).
|
|
369
|
+
copyobj = Marshal.load(Marshal.dump(self))
|
|
370
|
+
|
|
371
|
+
# restore saved values
|
|
372
|
+
@document = saved_doc
|
|
373
|
+
@parent = saved_parent
|
|
374
|
+
|
|
375
|
+
copyobj.set_document(saved_doc) if copyobj.indirect?
|
|
376
|
+
copyobj.parent = parent
|
|
377
|
+
|
|
378
|
+
copyobj
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
#
|
|
382
|
+
# Returns an indirect reference to this object, or a Null object is this object is not indirect.
|
|
383
|
+
#
|
|
384
|
+
def reference
|
|
385
|
+
raise InvalidObjectError, "Cannot reference a direct object" unless self.indirect?
|
|
386
|
+
|
|
387
|
+
ref = Reference.new(@no, @generation)
|
|
388
|
+
ref.parent = self
|
|
389
|
+
|
|
390
|
+
ref
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
#
|
|
394
|
+
# Returns an array of references pointing to the current object.
|
|
395
|
+
#
|
|
396
|
+
def xrefs
|
|
397
|
+
raise InvalidObjectError, "Cannot find xrefs to a direct object" unless self.indirect?
|
|
398
|
+
|
|
399
|
+
if self.document.nil?
|
|
400
|
+
raise InvalidObjectError, "Not attached to any document"
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
refs = []
|
|
404
|
+
@document.root_objects.each do |obj|
|
|
405
|
+
if obj.is_a?(ObjectStream)
|
|
406
|
+
obj.each do |child|
|
|
407
|
+
case child
|
|
408
|
+
when Dictionary, Array
|
|
409
|
+
refs.concat child.xref_cache[self.reference] if child.xref_cache.key?(self.reference)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
obj = obj.dictionary if obj.is_a?(Stream)
|
|
415
|
+
|
|
416
|
+
case obj
|
|
417
|
+
when Dictionary, Array
|
|
418
|
+
refs.concat obj.xref_cache[self.reference] if obj.xref_cache.key?(self.reference)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
refs
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
#
|
|
426
|
+
# Creates an exportable version of current object.
|
|
427
|
+
# The exportable version is a copy of _self_ with solved references, no owning PDF and no parent.
|
|
428
|
+
# References to Catalog or PageTreeNode objects have been destroyed.
|
|
429
|
+
#
|
|
430
|
+
# When exported, an object can be moved into another document without hassle.
|
|
431
|
+
#
|
|
432
|
+
def export
|
|
433
|
+
exported_obj = self.logicalize
|
|
434
|
+
exported_obj.no = exported_obj.generation = 0
|
|
435
|
+
exported_obj.set_document(nil) if exported_obj.indirect?
|
|
436
|
+
exported_obj.parent = nil
|
|
437
|
+
exported_obj.xref_cache.clear
|
|
438
|
+
|
|
439
|
+
exported_obj
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
#
|
|
443
|
+
# Returns a logicalized copy of _self_.
|
|
444
|
+
# See logicalize!
|
|
445
|
+
#
|
|
446
|
+
def logicalize #:nodoc:
|
|
447
|
+
self.copy.logicalize!
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
#
|
|
451
|
+
# Transforms recursively every references to the copy of their respective object.
|
|
452
|
+
# Catalog and PageTreeNode objects are excluded to limit the recursion.
|
|
453
|
+
#
|
|
454
|
+
def logicalize! #:nodoc:
|
|
455
|
+
|
|
456
|
+
resolve_all_references = -> (obj, browsed = [], ref_cache = {}) do
|
|
457
|
+
return if browsed.include?(obj)
|
|
458
|
+
browsed.push(obj)
|
|
459
|
+
|
|
460
|
+
if obj.is_a?(ObjectStream)
|
|
461
|
+
obj.each do |subobj|
|
|
462
|
+
resolve_all_references[obj, browsed, ref_cache]
|
|
463
|
+
end
|
|
447
464
|
end
|
|
448
|
-
new_obj.no = new_obj.generation = 0
|
|
449
|
-
new_obj.parent = obj
|
|
450
465
|
|
|
451
|
-
|
|
466
|
+
if obj.is_a?(Dictionary) or obj.is_a?(Array)
|
|
467
|
+
obj.map! do |subobj|
|
|
468
|
+
if subobj.is_a?(Reference)
|
|
469
|
+
new_obj =
|
|
470
|
+
if ref_cache.has_key?(subobj)
|
|
471
|
+
ref_cache[subobj]
|
|
472
|
+
else
|
|
473
|
+
ref_cache[subobj] = subobj.solve.copy
|
|
474
|
+
end
|
|
475
|
+
new_obj.no = new_obj.generation = 0
|
|
476
|
+
new_obj.parent = obj
|
|
477
|
+
|
|
478
|
+
new_obj unless new_obj.is_a?(Catalog) or new_obj.is_a?(PageTreeNode)
|
|
479
|
+
else
|
|
480
|
+
subobj
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
obj.each do |subobj|
|
|
485
|
+
resolve_all_references[subobj, browsed, ref_cache]
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
elsif obj.is_a?(Stream)
|
|
489
|
+
resolve_all_references[obj.dictionary, browsed, ref_cache]
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
resolve_all_references[self]
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
#
|
|
497
|
+
# Returns the indirect object which contains this object.
|
|
498
|
+
# If the current object is already indirect, returns self.
|
|
499
|
+
#
|
|
500
|
+
def indirect_parent
|
|
501
|
+
obj = self
|
|
502
|
+
obj = obj.parent until obj.indirect?
|
|
503
|
+
|
|
504
|
+
obj
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
#
|
|
508
|
+
# Returns self.
|
|
509
|
+
#
|
|
510
|
+
def to_o
|
|
511
|
+
self
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
#
|
|
515
|
+
# Returns self.
|
|
516
|
+
#
|
|
517
|
+
def solve
|
|
518
|
+
self
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
#
|
|
522
|
+
# Returns the PDF which the object belongs to.
|
|
523
|
+
#
|
|
524
|
+
def document
|
|
525
|
+
if self.indirect? then @document
|
|
452
526
|
else
|
|
453
|
-
|
|
527
|
+
@parent.document unless @parent.nil?
|
|
454
528
|
end
|
|
455
|
-
|
|
529
|
+
end
|
|
456
530
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
end
|
|
531
|
+
def set_document(doc)
|
|
532
|
+
raise InvalidObjectError, "You cannot set the document of a direct object" unless self.indirect?
|
|
460
533
|
|
|
461
|
-
|
|
462
|
-
resolve_all_references(obj.dictionary, browsed, ref_cache)
|
|
534
|
+
@document = doc
|
|
463
535
|
end
|
|
464
|
-
end
|
|
465
|
-
|
|
466
|
-
resolve_all_references(self)
|
|
467
|
-
end
|
|
468
536
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
537
|
+
class << self
|
|
538
|
+
|
|
539
|
+
def typeof(stream, noref = false) #:nodoc:
|
|
540
|
+
stream.skip(REGEXP_WHITESPACES)
|
|
541
|
+
|
|
542
|
+
case stream.peek(1)
|
|
543
|
+
when '/' then return Name
|
|
544
|
+
when '<'
|
|
545
|
+
return (stream.peek(2) == '<<') ? Stream : HexaString
|
|
546
|
+
when '(' then return LiteralString
|
|
547
|
+
when '[' then return Origami::Array
|
|
548
|
+
when 'n' then
|
|
549
|
+
return Null if stream.peek(4) == 'null'
|
|
550
|
+
when 't' then
|
|
551
|
+
return Boolean if stream.peek(4) == 'true'
|
|
552
|
+
when 'f' then
|
|
553
|
+
return Boolean if stream.peek(5) == 'false'
|
|
554
|
+
else
|
|
555
|
+
if not noref and stream.check(Reference::REGEXP_TOKEN) then return Reference
|
|
556
|
+
elsif stream.check(Real::REGEXP_TOKEN) then return Real
|
|
557
|
+
elsif stream.check(Integer::REGEXP_TOKEN) then return Integer
|
|
558
|
+
else
|
|
559
|
+
nil
|
|
560
|
+
end
|
|
561
|
+
end
|
|
486
562
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
#
|
|
490
|
-
def solve
|
|
491
|
-
self
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
#
|
|
495
|
-
# Returns the size of this object once converted to PDF code.
|
|
496
|
-
#
|
|
497
|
-
def size
|
|
498
|
-
to_s.size
|
|
499
|
-
end
|
|
563
|
+
nil
|
|
564
|
+
end
|
|
500
565
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
#
|
|
504
|
-
def pdf
|
|
505
|
-
if self.is_indirect? then @pdf
|
|
506
|
-
else
|
|
507
|
-
@parent.pdf if @parent
|
|
508
|
-
end
|
|
509
|
-
end
|
|
566
|
+
def parse(stream, parser = nil) #:nodoc:
|
|
567
|
+
offset = stream.pos
|
|
510
568
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
end
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
class << self
|
|
519
|
-
|
|
520
|
-
def typeof(stream, noref = false) #:nodoc:
|
|
521
|
-
stream.skip(REGEXP_WHITESPACES)
|
|
522
|
-
|
|
523
|
-
case stream.peek(1)
|
|
524
|
-
when '/' then return Name
|
|
525
|
-
when '<'
|
|
526
|
-
return (stream.peek(2) == '<<') ? Stream : HexaString
|
|
527
|
-
when '(' then return ByteString
|
|
528
|
-
when '[' then return Origami::Array
|
|
529
|
-
when 'n' then
|
|
530
|
-
return Null if stream.peek(4) == 'null'
|
|
531
|
-
when 't' then
|
|
532
|
-
return Boolean if stream.peek(4) == 'true'
|
|
533
|
-
when 'f' then
|
|
534
|
-
return Boolean if stream.peek(5) == 'false'
|
|
535
|
-
else
|
|
536
|
-
if not noref and stream.check(Reference::REGEXP_TOKEN) then return Reference
|
|
537
|
-
elsif stream.check(Real::REGEXP_TOKEN) then return Real
|
|
538
|
-
elsif stream.check(Integer::REGEXP_TOKEN) then return Integer
|
|
539
|
-
else
|
|
540
|
-
nil
|
|
541
|
-
end
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
nil
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
def parse(stream, parser = nil) #:nodoc:
|
|
548
|
-
offset = stream.pos
|
|
549
|
-
|
|
550
|
-
#
|
|
551
|
-
# End of body ?
|
|
552
|
-
#
|
|
553
|
-
return nil if stream.match?(/xref/) or stream.match?(/trailer/) or stream.match?(/startxref/)
|
|
554
|
-
|
|
555
|
-
if stream.scan(@@regexp_obj).nil?
|
|
556
|
-
raise InvalidObjectError,
|
|
557
|
-
"Object shall begin with '%d %d obj' statement"
|
|
558
|
-
end
|
|
559
|
-
|
|
560
|
-
no = stream[2].to_i
|
|
561
|
-
gen = stream[4].to_i
|
|
562
|
-
|
|
563
|
-
type = typeof(stream)
|
|
564
|
-
if type.nil?
|
|
565
|
-
raise InvalidObjectError,
|
|
566
|
-
"Cannot determine object (no:#{no},gen:#{gen}) type"
|
|
567
|
-
end
|
|
568
|
-
|
|
569
|
-
begin
|
|
570
|
-
newObj = type.parse(stream, parser)
|
|
571
|
-
rescue Exception => e
|
|
572
|
-
raise InvalidObjectError,
|
|
573
|
-
"Failed to parse object (no:#{no},gen:#{gen})\n\t -> [#{e.class}] #{e.message}"
|
|
574
|
-
end
|
|
575
|
-
|
|
576
|
-
newObj.set_indirect(true)
|
|
577
|
-
newObj.no = no
|
|
578
|
-
newObj.generation = gen
|
|
579
|
-
newObj.file_offset = offset
|
|
580
|
-
|
|
581
|
-
if stream.skip(@@regexp_endobj).nil?
|
|
582
|
-
raise UnterminatedObjectError.new("Object shall end with 'endobj' statement", newObj)
|
|
583
|
-
end
|
|
584
|
-
|
|
585
|
-
newObj
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
def skip_until_next_obj(stream) #:nodoc:
|
|
589
|
-
[ @@regexp_obj, /xref/, /trailer/, /startxref/ ].each do |re|
|
|
590
|
-
if stream.scan_until(re)
|
|
591
|
-
stream.pos -= stream.matched_size
|
|
592
|
-
return true
|
|
593
|
-
end
|
|
594
|
-
end
|
|
595
|
-
|
|
596
|
-
false
|
|
597
|
-
end
|
|
598
|
-
end
|
|
599
|
-
|
|
600
|
-
def pdf_version_required #:nodoc:
|
|
601
|
-
[ 1.0, 0 ]
|
|
602
|
-
end
|
|
603
|
-
|
|
604
|
-
#
|
|
605
|
-
# Returns the symbol type of this Object.
|
|
606
|
-
#
|
|
607
|
-
def type
|
|
608
|
-
self.class.to_s.split("::").last.to_sym
|
|
609
|
-
end
|
|
569
|
+
#
|
|
570
|
+
# End of body ?
|
|
571
|
+
#
|
|
572
|
+
return nil if stream.match?(/xref/) or stream.match?(/trailer/) or stream.match?(/startxref/)
|
|
610
573
|
|
|
611
|
-
|
|
574
|
+
if stream.scan(@@regexp_obj).nil?
|
|
575
|
+
raise InvalidObjectError,
|
|
576
|
+
"Object shall begin with '%d %d obj' statement"
|
|
577
|
+
end
|
|
612
578
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
#
|
|
616
|
-
def native_type
|
|
617
|
-
self.class.native_type
|
|
618
|
-
end
|
|
579
|
+
no = stream['no'].to_i
|
|
580
|
+
gen = stream['gen'].to_i
|
|
619
581
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
582
|
+
type = typeof(stream)
|
|
583
|
+
if type.nil?
|
|
584
|
+
raise InvalidObjectError,
|
|
585
|
+
"Cannot determine object (no:#{no},gen:#{gen}) type"
|
|
586
|
+
end
|
|
624
587
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
588
|
+
begin
|
|
589
|
+
new_obj = type.parse(stream, parser)
|
|
590
|
+
rescue
|
|
591
|
+
raise InvalidObjectError,
|
|
592
|
+
"Failed to parse object (no:#{no},gen:#{gen})\n\t -> [#{$!.class}] #{$!.message}"
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
new_obj.set_indirect(true)
|
|
596
|
+
new_obj.no = no
|
|
597
|
+
new_obj.generation = gen
|
|
598
|
+
new_obj.file_offset = offset
|
|
599
|
+
|
|
600
|
+
if stream.skip(@@regexp_endobj).nil?
|
|
601
|
+
raise UnterminatedObjectError.new("Object shall end with 'endobj' statement", new_obj)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
new_obj
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def skip_until_next_obj(stream) #:nodoc:
|
|
608
|
+
[ @@regexp_obj, /xref/, /trailer/, /startxref/ ].each do |re|
|
|
609
|
+
if stream.scan_until(re)
|
|
610
|
+
stream.pos -= stream.matched_size
|
|
611
|
+
return true
|
|
612
|
+
end
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
false
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def version_required #:nodoc:
|
|
620
|
+
[ 1.0, 0 ]
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
#
|
|
624
|
+
# Returns the symbol type of this Object.
|
|
625
|
+
#
|
|
626
|
+
def type
|
|
627
|
+
name = (self.class.name or self.class.superclass.name or self.native_type.name)
|
|
628
|
+
|
|
629
|
+
name.split("::").last.to_sym
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def self.native_type; Origami::Object end #:nodoc:
|
|
633
|
+
|
|
634
|
+
#
|
|
635
|
+
# Returns the native PDF type of this Object.
|
|
636
|
+
#
|
|
637
|
+
def native_type
|
|
638
|
+
self.class.native_type
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def cast_to(type, _parser = nil) #:nodoc:
|
|
642
|
+
if type.native_type != self.native_type
|
|
643
|
+
raise TypeError, "Incompatible cast from #{self.class} to #{type}"
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
self
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
#
|
|
650
|
+
# Outputs this object into PDF code.
|
|
651
|
+
# _data_:: The object data.
|
|
652
|
+
#
|
|
653
|
+
def to_s(data)
|
|
654
|
+
content = ""
|
|
655
|
+
content << "#{no} #{generation} #{TOKENS.first}" << EOL if self.indirect?
|
|
656
|
+
content << data
|
|
657
|
+
content << EOL << TOKENS.last << EOL if self.indirect?
|
|
658
|
+
|
|
659
|
+
content.force_encoding('binary')
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
alias output to_s
|
|
640
663
|
end
|
|
641
664
|
|
|
642
|
-
alias output to_s
|
|
643
|
-
|
|
644
|
-
end
|
|
645
665
|
end
|