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,24 +1,20 @@
1
1
  =begin
2
- = File
3
- dictionary.rb
4
-
5
- = Info
6
- This file is part of Origami, PDF manipulation framework for Ruby
7
- Copyright (C) 2010 Guillaume DelugrÈ <guillaume AT security-labs DOT org>
8
- All right reserved.
9
-
10
- Origami is free software: you can redistribute it and/or modify
11
- it under the terms of the GNU Lesser General Public License as published by
12
- the Free Software Foundation, either version 3 of the License, or
13
- (at your option) any later version.
14
-
15
- Origami is distributed in the hope that it will be useful,
16
- but WITHOUT ANY WARRANTY; without even the implied warranty of
17
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
- GNU Lesser General Public License for more details.
19
-
20
- You should have received a copy of the GNU Lesser General Public License
21
- along with Origami. If not, see <http://www.gnu.org/licenses/>.
2
+
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
5
+
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
22
18
 
23
19
  =end
24
20
 
@@ -26,247 +22,272 @@ module Origami
26
22
 
27
23
  class InvalidDictionaryObjectError < InvalidObjectError #:nodoc:
28
24
  end
29
-
25
+
30
26
  #
31
27
  # Class representing a Dictionary Object.
32
28
  # Dictionaries are containers associating a Name to an embedded Object.
33
29
  #
34
30
  class Dictionary < Hash
35
- include Origami::Object
36
-
37
- TOKENS = %w{ << >> } #:nodoc:
38
- @@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first) + WHITESPACES)
39
- @@regexp_close = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.last) + WHITESPACES)
40
-
41
- @@cast_fingerprints = {}
42
- attr_reader :strings_cache, :names_cache, :xref_cache
43
-
44
- #
45
- # Creates a new Dictionary.
46
- # _hash_:: The hash representing the new Dictionary.
47
- #
48
- def initialize(hash = {})
49
- raise TypeError, "Expected type Hash, received #{hash.class}." unless hash.is_a?(Hash)
50
- super()
51
-
52
- @strings_cache = []
53
- @names_cache = []
54
- @xref_cache = {}
55
-
56
- hash.each_pair do |k,v|
57
- @names_cache.push(k.to_o)
58
- case val = v.to_o
59
- when String then @strings_cache.push(val)
60
- when Name then @names_cache.push(val)
61
- when Reference then
62
- (@xref_cache[val] ||= []).push(self)
63
- when Dictionary,Array then
64
- @strings_cache.concat(val.strings_cache)
65
- @names_cache.concat(val.names_cache)
66
- @xref_cache.update(val.xref_cache) do |ref, cache1, cache2|
67
- cache1.concat(cache2)
68
- end
69
-
70
- val.strings_cache.clear
71
- val.names_cache.clear
72
- val.xref_cache.clear
73
- end
74
-
75
- self[k.to_o] = val unless k.nil?
76
- end
77
- end
78
-
79
- def self.parse(stream, parser = nil) #:nodoc:
80
-
81
- offset = stream.pos
82
-
83
- if stream.skip(@@regexp_open).nil?
84
- raise InvalidDictionaryObjectError, "No token '#{TOKENS.first}' found"
85
- end
86
-
87
- pairs = {}
88
- while stream.skip(@@regexp_close).nil? do
89
- key = Name.parse(stream, parser)
90
-
91
- type = Object.typeof(stream)
92
- if type.nil?
93
- raise InvalidDictionaryObjectError, "Invalid object for field #{key.to_s}"
94
- end
95
-
96
- value = type.parse(stream, parser)
97
- pairs[key] = value
98
- end
99
-
100
- dict =
101
- if Origami::OPTIONS[:enable_type_guessing]
102
- guessed_type = self.guess_type(pairs)
103
-
104
- if Origami::OPTIONS[:enable_type_propagation]
105
- guessed_type.new(
106
- Hash[
107
- pairs.map {|key, value|
108
- hint_type = guessed_type.hint_type(key.value)
109
- if hint_type.is_a?(::Array) and not value.is_a?(Reference) # Choose best match
110
- hint_type.find {|type| type.native_type == value.native_type}
31
+ include Origami::Object
32
+ using TypeConversion
33
+
34
+ TOKENS = %w{ << >> } #:nodoc:
35
+ @@regexp_open = Regexp.new(WHITESPACES + TOKENS.first + WHITESPACES)
36
+ @@regexp_close = Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)
37
+
38
+ @@cast_fingerprints = {}
39
+ @@cast_keys = []
40
+
41
+ attr_reader :strings_cache, :names_cache, :xref_cache
42
+
43
+ #
44
+ # Creates a new Dictionary.
45
+ # _hash_:: The hash representing the new Dictionary.
46
+ #
47
+ def initialize(hash = {}, parser = nil)
48
+ raise TypeError, "Expected type Hash, received #{hash.class}." unless hash.is_a?(Hash)
49
+ super()
50
+
51
+ @strings_cache = []
52
+ @names_cache = []
53
+ @xref_cache = {}
54
+
55
+ hash.each_pair do |k,v|
56
+ next if k.nil?
57
+
58
+ # Turns the values into Objects.
59
+ key, value = k.to_o, v.to_o
60
+
61
+ if Origami::OPTIONS[:enable_type_guessing]
62
+ hint_type = guess_value_type(key, value)
63
+
64
+ if hint_type.is_a?(Class) and hint_type < value.class
65
+ value = value.cast_to(hint_type, parser)
111
66
  end
112
67
 
113
- if hint_type.is_a?(Class) and hint_type.native_type == value.native_type
114
- [key, value.cast_to(hint_type)]
115
- elsif hint_type and value.is_a?(Reference) and parser
116
- parser.defer_type_cast(value, hint_type)
117
- [key, value]
118
- else
119
- [key, value]
68
+ if hint_type and parser and Origami::OPTIONS[:enable_type_propagation]
69
+ if value.is_a?(Reference)
70
+ parser.defer_type_cast(value, hint_type)
71
+ end
120
72
  end
121
- }])
73
+ end
74
+
75
+ # Cache keys and values for fast search.
76
+ cache_key(key)
77
+ cache_value(value)
78
+
79
+ self[key] = value
80
+ end
81
+ end
82
+
83
+ def self.parse(stream, parser = nil) #:nodoc:
84
+ offset = stream.pos
85
+
86
+ if stream.skip(@@regexp_open).nil?
87
+ raise InvalidDictionaryObjectError, "No token '#{TOKENS.first}' found"
88
+ end
89
+
90
+ hash = {}
91
+ while stream.skip(@@regexp_close).nil? do
92
+ key = Name.parse(stream, parser)
93
+
94
+ type = Object.typeof(stream)
95
+ raise InvalidDictionaryObjectError, "Invalid object for field #{key.to_s}" if type.nil?
96
+
97
+ value = type.parse(stream, parser)
98
+ hash[key] = value
99
+ end
100
+
101
+ if Origami::OPTIONS[:enable_type_guessing] and not (@@cast_keys & hash.keys).empty?
102
+ dict_type = self.guess_type(hash)
122
103
  else
123
- guessed_type.new(pairs)
104
+ dict_type = self
124
105
  end
125
- else
126
- self.new(pairs)
127
- end
128
-
129
- dict.file_offset = offset
130
-
131
- dict
132
- end
133
-
134
- alias to_h to_hash
135
-
136
- def to_s(indent = 1) #:nodoc:
137
- if indent > 0
138
- content = TOKENS.first + EOL
139
- self.each_pair do |key,value|
140
- content << "\t" * indent + key.to_s + " " + (value.is_a?(Dictionary) ? value.to_s(indent + 1) : value.to_s) + EOL
141
- end
142
-
143
- content << "\t" * (indent - 1) + TOKENS.last
144
- else
145
- content = TOKENS.first.dup
146
- self.each_pair do |key,value|
147
- content << "#{key.to_s} #{value.is_a?(Dictionary) ? value.to_s(0) : value.to_s}"
148
- end
149
- content << TOKENS.last
106
+
107
+ # Creates the Dictionary.
108
+ dict = dict_type.new(hash, parser)
109
+
110
+ dict.file_offset = offset
111
+ dict
150
112
  end
151
113
 
152
- super(content)
153
- end
154
-
155
- def map!(&b)
156
- self.each_pair do |k,v|
157
- self[k] = b.call(v)
114
+ def to_s(indent: 1, tab: "\t") #:nodoc:
115
+ if indent > 0
116
+ content = TOKENS.first + EOL
117
+ self.each_pair do |key,value|
118
+ content << tab * indent << key.to_s << ' '
119
+ content << (value.is_a?(Dictionary) ? value.to_s(indent: indent+1) : value.to_s)
120
+ content << EOL
121
+ end
122
+
123
+ content << tab * (indent - 1) << TOKENS.last
124
+ else
125
+ content = TOKENS.first.dup
126
+ self.each_pair do |key,value|
127
+ content << "#{key.to_s} #{value.is_a?(Dictionary) ? value.to_s(indent: 0) : value.to_s}"
128
+ end
129
+ content << TOKENS.last
130
+ end
131
+
132
+ super(content)
158
133
  end
159
- end
160
-
161
- def merge(dict)
162
- Dictionary.new(super(dict))
163
- end
164
-
165
- def []=(key,val)
166
- unless key.is_a?(Symbol) or key.is_a?(Name)
167
- fail "Expecting a Name for a Dictionary entry, found #{key.class} instead."
134
+
135
+ def map!(&b)
136
+ self.each_pair do |k,v|
137
+ self[k] = b.call(v)
138
+ end
168
139
  end
169
-
170
- key = key.to_o
171
- if not val.nil?
172
- val = val.to_o
173
- super(key,val)
174
-
175
- key.parent = self
176
- val.parent = self unless val.is_indirect? or val.parent.equal?(self)
177
-
178
- val
179
- else
180
- delete(key)
140
+
141
+ def merge(dict)
142
+ Dictionary.new(super(dict))
181
143
  end
182
- end
183
-
184
- def [](key)
185
- super(key.to_o)
186
- end
187
-
188
- def has_key?(key)
189
- super(key.to_o)
190
- end
191
-
192
- def delete(key)
193
- super(key.to_o)
194
- end
195
-
196
- def cast_to(type)
197
- super(type)
198
-
199
- cast = type.new(self)
200
- cast.parent = self.parent
201
- cast.no, cast.generation = self.no, self.generation
202
- if self.is_indirect?
203
- cast.set_indirect(true)
204
- cast.set_pdf(self.pdf)
205
- cast.file_offset = self.file_offset # cast can replace self
144
+
145
+ def []=(key,val)
146
+ unless key.is_a?(Symbol) or key.is_a?(Name)
147
+ fail "Expecting a Name for a Dictionary entry, found #{key.class} instead."
148
+ end
149
+
150
+ key = key.to_o
151
+ if val.nil?
152
+ delete(key)
153
+ return
154
+ end
155
+
156
+ val = val.to_o
157
+ super(key,val)
158
+
159
+ key.parent = self
160
+ val.parent = self unless val.indirect? or val.parent.equal?(self)
161
+
162
+ val
206
163
  end
207
164
 
208
- cast.xref_cache.update(self.xref_cache)
209
- cast.names_cache.concat(self.names_cache)
210
- cast.strings_cache.concat(self.strings_cache)
165
+ def [](key)
166
+ super(key.to_o)
167
+ end
211
168
 
212
- cast
213
- end
169
+ def key?(key)
170
+ super(key.to_o)
171
+ end
172
+ alias include? key?
173
+ alias has_key? key?
214
174
 
215
- alias each each_value
175
+ def delete(key)
176
+ super(key.to_o)
177
+ end
216
178
 
217
- alias value to_h
179
+ def cast_to(type, parser = nil)
180
+ super(type)
218
181
 
219
- def method_missing(field, *args) #:nodoc:
220
- raise NoMethodError, "No method `#{field}' for #{self.class}" unless field.to_s[0,1] =~ /[A-Z]/
182
+ cast = type.new(self, parser)
183
+ cast.parent = self.parent
184
+ cast.no, cast.generation = self.no, self.generation
185
+ if self.indirect?
186
+ cast.set_indirect(true)
187
+ cast.set_document(self.document)
188
+ cast.file_offset = self.file_offset # cast can replace self
189
+ end
221
190
 
222
- if field.to_s[-1,1] == '='
223
- self[field.to_s[0..-2].to_sym] = args.first
224
- else
225
- obj = self[field];
226
- obj.is_a?(Reference) ? obj.solve : obj
191
+ cast
227
192
  end
228
- end
229
-
230
- def copy
231
- copy = self.class.new
232
- self.each_pair do |k,v|
233
- copy[k] = v.copy
234
- end
235
-
236
- copy.parent = @parent
237
- copy.no, copy.generation = @no, @generation
238
- copy.set_indirect(true) if is_indirect?
239
- copy.set_pdf(@pdf) if is_indirect?
240
- copy
241
- end
242
-
243
- def self.native_type; Dictionary end
244
-
245
- def self.add_type_info(typeclass, key, value) #:nodoc:
246
- if not @@cast_fingerprints.has_key?(typeclass) and typeclass.superclass != Dictionary and
247
- @@cast_fingerprints.has_key?(typeclass.superclass)
248
- @@cast_fingerprints[typeclass] = @@cast_fingerprints[typeclass.superclass].dup
193
+
194
+ alias each each_value
195
+
196
+ def to_h
197
+ Hash[self.to_a.map!{|k, v| [ k.value, v.value ]}]
249
198
  end
199
+ alias value to_h
250
200
 
251
- @@cast_fingerprints[typeclass] ||= {}
252
- @@cast_fingerprints[typeclass][key.to_o] = value.to_o
253
- end
201
+ def method_missing(field, *args) #:nodoc:
202
+ raise NoMethodError, "No method `#{field}' for #{self.class}" unless field.to_s[0,1] =~ /[A-Z]/
254
203
 
255
- def self.guess_type(hash) #:nodoc:
256
- best_type = self
204
+ if field.to_s[-1,1] == '='
205
+ self[field.to_s[0..-2].to_sym] = args.first
206
+ else
207
+ obj = self[field];
208
+ obj.is_a?(Reference) ? obj.solve : obj
209
+ end
210
+ end
211
+
212
+ def copy
213
+ copy = self.class.new
214
+ self.each_pair do |k,v|
215
+ copy[k] = v.copy
216
+ end
217
+
218
+ copy.parent = @parent
219
+ copy.no, copy.generation = @no, @generation
220
+ copy.set_indirect(true) if self.indirect?
221
+ copy.set_document(@document) if self.indirect?
257
222
 
258
- @@cast_fingerprints.each_pair do |typeclass, keys|
259
- best_type = typeclass if keys.all? { |k,v|
260
- hash.has_key?(k) and hash[k] == v
261
- } and typeclass < best_type
223
+ copy
262
224
  end
263
225
 
264
- best_type
265
- end
226
+ def self.native_type; Dictionary end
227
+
228
+ def self.add_type_info(klass, key, value) #:nodoc:
229
+ raise TypeError, "Invalid class #{klass}" unless klass.is_a?(Class) and klass < Dictionary
230
+
231
+ key, value = key.to_o, value.to_o
232
+
233
+ # Inherit the superclass type information.
234
+ if not @@cast_fingerprints.key?(klass) and @@cast_fingerprints.key?(klass.superclass)
235
+ @@cast_fingerprints[klass] = @@cast_fingerprints[klass.superclass].dup
236
+ end
237
+
238
+ @@cast_fingerprints[klass] ||= {}
239
+ @@cast_fingerprints[klass][key] = value
266
240
 
267
- def self.hint_type(name); nil end #:nodoc:
241
+ @@cast_keys.push(key) unless @@cast_keys.include?(key)
242
+ end
243
+
244
+ def self.guess_type(hash) #:nodoc:
245
+ best_type = self
246
+
247
+ @@cast_fingerprints.each_pair do |klass, keys|
248
+ next unless klass < best_type
249
+
250
+ best_type = klass if keys.all? { |k,v| hash[k] == v }
251
+ end
252
+
253
+ best_type
254
+ end
255
+
256
+ def self.hint_type(name); nil end #:nodoc:
257
+
258
+ private
259
+
260
+ def cache_key(key)
261
+ @names_cache.push(key)
262
+ end
263
+
264
+ def cache_value(value)
265
+ case value
266
+ when String then @strings_cache.push(value)
267
+ when Name then @names_cache.push(value)
268
+ when Reference then
269
+ (@xref_cache[value] ||= []).push(self)
270
+ when Dictionary, Array
271
+ @strings_cache.concat(value.strings_cache)
272
+ @names_cache.concat(value.names_cache)
273
+ @xref_cache.update(value.xref_cache) do |_ref, cache1, cache2|
274
+ cache1.concat(cache2)
275
+ end
276
+
277
+ value.strings_cache.clear
278
+ value.names_cache.clear
279
+ value.xref_cache.clear
280
+ end
281
+ end
282
+
283
+ def guess_value_type(key, value)
284
+ hint_type = self.class.hint_type(key.value)
285
+ if hint_type.is_a?(::Array) and not value.is_a?(Reference) # Choose best match
286
+ hint_type = hint_type.find {|type| type < value.class }
287
+ end
288
+
289
+ hint_type
290
+ end
291
+ end
268
292
 
269
- end #class
270
-
271
293
  end
272
- # Origami