origami 1.2.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -1,233 +1,245 @@
1
- module Origami
1
+ =begin
2
2
 
3
- module Obfuscator
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
4
5
 
5
- WHITECHARS = [ " ", "\t", "\r", "\n", "\0" ]
6
- OBJECTS = [ Array, Boolean, Dictionary, Integer, Name, Null, Stream, String, Real, Reference ]
7
- MAX_INT = 0xFFFFFFFF
8
- PRINTABLE = ("!".."9").to_a + (':'..'Z').to_a + ('['..'z').to_a + ('{'..'~').to_a
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
- def self.junk_spaces(max_size = 3)
12
- length = rand(max_size) + 1
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
- ::Array.new(length) { WHITECHARS[rand(WHITECHARS.size)] }.join
15
- 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/>.
16
18
 
17
- def self.junk_comment(max_size = 15)
18
- length = rand(max_size) + 1
19
+ =end
19
20
 
20
- junk_comment = ::Array.new(length) {
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
- def self.junk_object(type = nil)
23
+ module Obfuscator
24
+ using TypeConversion
28
25
 
29
- if type.nil?
30
- type = OBJECTS[rand(OBJECTS.size)]
31
- end
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
- unless type.include?(Origami::Object)
34
- raise TypeError, "Not a valid object type"
35
- end
32
+ def self.junk_spaces(max_size = 3)
33
+ length = rand(max_size) + 1
36
34
 
37
- Obfuscator.send("junk_#{type.to_s.split('::').last.downcase}")
38
- end
35
+ ::Array.new(length) { WHITECHARS[rand(WHITECHARS.size)] }.join
36
+ end
39
37
 
40
- def self.junk_array(max_size = 5)
41
- length = rand(max_size) + 1
38
+ def self.junk_comment(max_size = 15)
39
+ length = rand(max_size) + 1
42
40
 
43
- ::Array.new(length) {
44
- obj = Obfuscator.junk_object until (not obj.nil? and not obj.is_a?(Stream)) ; obj
45
- }.to_o
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
- def self.junk_boolean
49
- Boolean.new(rand(2).zero?)
50
- end
45
+ "%#{junk_comment}#{EOL}"
46
+ end
51
47
 
52
- def self.junk_dictionary(max_size = 5)
53
- length = rand(max_size) + 1
48
+ def self.junk_object(type = nil)
49
+ if type.nil?
50
+ type = OBJECTS[rand(OBJECTS.size)]
51
+ end
54
52
 
55
- hash = Hash.new
56
- length.times do
57
- obj = Obfuscator.junk_object
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
- def self.junk_integer(max = MAX_INT)
65
- Integer.new(rand(max + 1))
66
- end
57
+ Obfuscator.send("junk_#{type.to_s.split('::').last.downcase}")
58
+ end
67
59
 
68
- def self.junk_name(max_size = 8)
69
- length = rand(max_size) + 1
60
+ def self.junk_array(max_size = 5)
61
+ length = rand(max_size) + 1
70
62
 
71
- Name.new(::Array.new(length) { PRINTABLE[rand(PRINTABLE.size)] }.join)
72
- end
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
- def self.junk_null
75
- Null.new
76
- end
68
+ def self.junk_boolean
69
+ Boolean.new(rand(2).zero?)
70
+ end
77
71
 
78
- def self.junk_stream(max_data_size = 200)
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
- length = rand(max_data_size) + 1
84
- junk_data = ::Array.new(length) { rand(256).chr }.join
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
- stm = Stream.new
87
- stm.dictionary = Obfuscator.junk_dictionary(5)
88
- stm.setFilter(chain)
89
- stm.data = junk_data
81
+ hash.to_o
82
+ end
90
83
 
91
- stm
92
- end
84
+ def self.junk_integer(max = MAX_INT)
85
+ Integer.new(rand(max + 1))
86
+ end
93
87
 
94
- def self.junk_string(max_size = 10)
95
- length = rand(max_size) + 1
88
+ def self.junk_name(max_size = 8)
89
+ length = rand(max_size) + 1
96
90
 
97
- strtype = (rand(2).zero?) ? ByteString : HexaString
91
+ Name.new(::Array.new(length) { PRINTABLE[rand(PRINTABLE.size)] }.join)
92
+ end
98
93
 
99
- strtype.new(::Array.new(length) { PRINTABLE[rand(PRINTABLE.size)] }.join)
100
- end
94
+ def self.junk_null
95
+ Null.new
96
+ end
101
97
 
102
- def self.junk_real
103
- Real.new(rand * rand(MAX_INT + 1))
104
- end
98
+ def self.junk_stream(max_data_size = 200)
105
99
 
106
- def self.junk_reference(max_no = 300, max_gen = 1)
107
- no = rand(max_no) + 1
108
- gen = rand(max_gen)
100
+ chainlen = rand(2) + 1
101
+ chain = ::Array.new(chainlen) { FILTERS[rand(FILTERS.size)] }
109
102
 
110
- Reference.new(no, gen)
111
- end
103
+ length = rand(max_data_size) + 1
104
+ junk_data = ::Array.new(length) { rand(256).chr }.join
112
105
 
113
- end
106
+ stm = Stream.new
107
+ stm.dictionary = Obfuscator.junk_dictionary(5)
108
+ stm.setFilter(chain)
109
+ stm.data = junk_data
114
110
 
115
- class Dictionary
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
- content << TOKENS.last
126
- super(content)
127
- end
114
+ def self.junk_string(max_size = 10)
115
+ length = rand(max_size) + 1
128
116
 
129
- end
117
+ strtype = (rand(2).zero?) ? LiteralString : HexaString
130
118
 
131
- module Object
132
- alias :to_obfuscated_str :to_s
133
- end
119
+ strtype.new(::Array.new(length) { PRINTABLE[rand(PRINTABLE.size)] }.join)
120
+ end
134
121
 
135
- class Array
136
- def to_obfuscated_str
137
- content = TOKENS.first + Obfuscator.junk_spaces
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
- content << TOKENS.last
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
- super(content)
130
+ Reference.new(no, gen)
131
+ end
145
132
  end
146
- end
147
133
 
148
- class Null
149
- alias :to_obfuscated_str :to_s
150
- end
134
+ class Dictionary
151
135
 
152
- class Boolean
153
- alias :to_obfuscated_str :to_s
154
- end
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
- class Integer
157
- alias :to_obfuscated_str :to_s
158
- end
144
+ content << TOKENS.last
145
+ super(content)
146
+ end
147
+ end
159
148
 
160
- class Real
161
- alias :to_obfuscated_str :to_s
162
- end
149
+ module Object
150
+ alias :to_obfuscated_str :to_s
151
+ end
163
152
 
164
- class Reference
165
- def to_obfuscated_str
166
- refstr = refno.to_s + Obfuscator.junk_spaces + refgen.to_s + Obfuscator.junk_spaces + "R"
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
- super(refstr)
160
+ content << TOKENS.last
161
+
162
+ super(content)
163
+ end
169
164
  end
170
- end
171
165
 
172
- class ByteString
173
- def to_obfuscated_str
174
- to_s
166
+ class Null
167
+ alias :to_obfuscated_str :to_s
175
168
  end
176
- end
177
169
 
178
- class HexaString
179
- def to_obfuscated_str
180
- to_s
170
+ class Boolean
171
+ alias :to_obfuscated_str :to_s
181
172
  end
182
- end
183
-
184
- class Name
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
- end
204
-
205
- class Stream
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
- class Trailer
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
- def to_obfuscated_str
221
- content = ""
222
- if self.has_dictionary?
223
- content << TOKENS.first << EOL << @dictionary.to_obfuscated_str << EOL
224
- end
186
+ super(refstr)
187
+ end
188
+ end
225
189
 
226
- content << XREF_TOKEN << EOL << @startxref.to_s << EOL << TOKENS.last << EOL
190
+ class LiteralString
191
+ alias :to_obfuscated_str :to_s
192
+ end
227
193
 
228
- content
194
+ class HexaString
195
+ alias :to_obfuscated_str :to_s
229
196
  end
230
197
 
231
- end
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
@@ -1,645 +1,665 @@
1
1
  =begin
2
2
 
3
- = File
4
- object.rb
5
-
6
- = Info
7
- This file is part of Origami, PDF manipulation framework for Ruby
8
- Copyright (C) 2010 Guillaume Delugré <guillaume AT security-labs DOT org>
9
- All right reserved.
10
-
11
- Origami is free software: you can redistribute it and/or modify
12
- it under the terms of the GNU Lesser General Public License as published by
13
- the Free Software Foundation, either version 3 of the License, or
14
- (at your option) any later version.
15
-
16
- Origami is distributed in the hope that it will be useful,
17
- but WITHOUT ANY WARRANTY; without even the implied warranty of
18
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
- GNU Lesser General Public License for more details.
20
-
21
- You should have received a copy of the GNU Lesser General Public License
22
- along with Origami. If not, see <http://www.gnu.org/licenses/>.
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
23
5
 
24
- =end
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
- class Bignum #:nodoc:
27
- def to_o
28
- Origami::Integer.new(self)
29
- end
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
- class Fixnum #:nodoc:
33
- def to_o
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
- class Float #:nodoc:
45
- def to_o
46
- Origami::Real.new(self)
47
- end
48
- end
19
+ =end
49
20
 
50
- class Hash #:nodoc:
51
- def to_o
52
- Origami::Dictionary.new(self)
53
- end
54
- end
21
+ #
22
+ # Module for parsing/generating PDF files.
23
+ #
24
+ module Origami
55
25
 
56
- class TrueClass #:nodoc:
57
- def to_o
58
- Origami::Boolean.new(true)
59
- end
60
- end
26
+ module TypeConversion
61
27
 
62
- class FalseClass #:nodoc:
63
- def to_o
64
- Origami::Boolean.new(false)
65
- end
66
- end
28
+ refine ::Bignum do
29
+ def to_o
30
+ Origami::Integer.new(self)
31
+ end
32
+ end
67
33
 
68
- class NilClass #:nodoc:
69
- def to_o
70
- Origami::Null.new
71
- end
72
- end
34
+ refine ::Fixnum do
35
+ def to_o
36
+ Origami::Integer.new(self)
37
+ end
38
+ end
73
39
 
74
- class Symbol #:nodoc:
75
- def to_o
76
- Origami::Name.new(self)
77
- end
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
- class String #:nodoc:
86
- def to_o
87
- Origami::ByteString.new(self)
88
- end
46
+ refine ::Float do
47
+ def to_o
48
+ Origami::Real.new(self)
49
+ end
50
+ end
89
51
 
90
- def is_binary_data?
91
- ( self.count( "\x00" ) > 0 ) unless empty?
92
- end
93
- end
52
+ refine ::Hash do
53
+ def to_o
54
+ Origami::Dictionary.new(self)
55
+ end
56
+ end
94
57
 
95
- #
96
- # Module for parsing/generating PDF files.
97
- #
98
- module Origami
58
+ refine ::TrueClass do
59
+ def to_o
60
+ Origami::Boolean.new(true)
61
+ end
62
+ end
99
63
 
100
- #
101
- # Mixin' module for objects which can store their options into an inner Dictionary.
102
- #
103
- module StandardObject #:nodoc:
64
+ refine ::FalseClass do
65
+ def to_o
66
+ Origami::Boolean.new(false)
67
+ end
68
+ end
104
69
 
105
- DEFAULT_ATTRIBUTES = { :Type => Object, :Version => "1.2" } #:nodoc:
70
+ refine ::NilClass do
71
+ def to_o
72
+ Origami::Null.new
73
+ end
74
+ end
106
75
 
107
- def self.included(receiver) #:nodoc:
108
- receiver.instance_variable_set(:@fields, Hash.new(DEFAULT_ATTRIBUTES))
109
- receiver.extend(ClassMethods)
110
- end
76
+ refine ::Symbol do
77
+ def to_o
78
+ Origami::Name.new(self)
79
+ end
80
+ end
111
81
 
112
- module ClassMethods #:nodoc:all
113
-
114
- def inherited(subclass)
115
- subclass.instance_variable_set(:@fields, Marshal.load(Marshal.dump(@fields)))
116
- end
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
- # Check if an attribute is set in the current Object.
175
- # _attr_:: The attribute name.
90
+ # Common Exception class for Origami errors.
176
91
  #
177
- def has_field? (field)
178
- not self[field].nil?
92
+ class Error < StandardError
179
93
  end
180
94
 
181
95
  #
182
- # Returns the version and level required by the current Object.
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
- def pre_build
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
- # Deep copy of an object.
329
- #
330
- def copy
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
- # Perform the recursive copy (quite dirty).
338
- copyobj = Marshal.load(Marshal.dump(self))
107
+ module ClassMethods #:nodoc:all
339
108
 
340
- # restore saved values
341
- @pdf = saved_pdf
342
- @parent = saved_parent
109
+ def inherited(subclass)
110
+ subclass.instance_variable_set(:@fields, Hash[@fields.map{|name, attributes| [name, attributes.clone]}])
111
+ end
343
112
 
344
- copyobj.set_pdf(saved_pdf) if copyobj.is_indirect?
345
- copyobj.parent = parent
113
+ def fields
114
+ @fields
115
+ end
346
116
 
347
- copyobj
348
- end
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
- ref = Reference.new(@no, @generation)
359
- ref.parent = self
122
+ if @fields.has_key?(name)
123
+ @fields[name].merge! attributes
124
+ else
125
+ @fields[name] = attributes
126
+ end
360
127
 
361
- ref
362
- end
128
+ define_field_methods(name)
129
+ end
363
130
 
364
- #
365
- # Returns an array of references pointing to the current object.
366
- #
367
- def xrefs
368
- unless self.is_indirect?
369
- raise InvalidObjectError, "Cannot find xrefs to a direct object"
370
- end
371
-
372
- if self.pdf.nil?
373
- raise InvalidObjectError, "Not attached to any PDF"
374
- end
375
-
376
- xref_cache = Hash.new([])
377
- @pdf.root_objects.each do |obj|
378
- case obj
379
- when Dictionary,Array then
380
- xref_cache.update(obj.xref_cache) do |ref, cache1, cache2|
381
- cache1.concat(cache2)
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
- when Stream then
385
- obj.dictionary.xref_cache.each do |ref, cache|
386
- cache.map!{obj}
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
- xref_cache.update(obj.dictionary.xref_cache) do |ref, cache1, cache2|
390
- cache1.concat(cache2)
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
- xref_cache[self.reference]
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
- # Returns a logicalized copy of _self_.
417
- # See logicalize!
418
- #
419
- def logicalize #:nodoc:
420
- self.copy.logicalize!
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
- # Transforms recursively every references to the copy of their respective object.
425
- # Catalog and PageTreeNode objects are excluded to limit the recursion.
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
- def logicalize! #:nodoc:
289
+ module Object
428
290
 
429
- def resolve_all_references(obj, browsed = [], ref_cache = {})
430
- return if browsed.include?(obj)
431
- browsed.push(obj)
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
- if obj.is_a?(ObjectStream)
434
- obj.each do |subobj|
435
- resolve_all_references(obj, browsed, ref_cache)
436
- end
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
- if obj.is_a?(Dictionary) or obj.is_a?(Array)
440
- obj.map! do |subobj|
441
- if subobj.is_a?(Reference)
442
- new_obj =
443
- if ref_cache.has_key?(subobj)
444
- ref_cache[subobj]
445
- else
446
- ref_cache[subobj] = subobj.solve.copy
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
- new_obj unless new_obj.is_a?(Catalog) or new_obj.is_a?(PageTreeNode)
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
- subobj
527
+ @parent.document unless @parent.nil?
454
528
  end
455
- end
529
+ end
456
530
 
457
- obj.each do |subobj|
458
- resolve_all_references(subobj, browsed, ref_cache)
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
- elsif obj.is_a?(Stream)
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
- # Returns the indirect object which contains this object.
471
- # If the current object is already indirect, returns self.
472
- #
473
- def indirect_parent
474
- obj = self
475
- obj = obj.parent until obj.is_indirect?
476
-
477
- obj
478
- end
479
-
480
- #
481
- # Returns self.
482
- #
483
- def to_o
484
- self
485
- end
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
- # Returns self.
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
- # Returns the PDF which the object belongs to.
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
- def set_pdf(pdf)
512
- if self.is_indirect? then @pdf = pdf
513
- else
514
- raise InvalidObjectError, "You cannot set the PDF parent of a direct object"
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
- def self.native_type; Origami::Object end #:nodoc:
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
- # Returns the native PDF type of this Object.
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
- def cast_to(type) #:nodoc:
621
- if type.native_type != self.native_type
622
- raise TypeError, "Incompatible cast from #{self.class} to #{type}"
623
- end
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
- self
626
- end
627
-
628
- #
629
- # Outputs this object into PDF code.
630
- # _data_:: The object data.
631
- #
632
- def to_s(data)
633
-
634
- content = ""
635
- content << "#{no} #{generation} obj" << EOL if self.is_indirect?
636
- content << data
637
- content << EOL << "endobj" << EOL if self.is_indirect?
638
-
639
- content
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