origami 1.2.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -1,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