origami-docspring 2.2.0 → 2.3.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/examples/attachments/attachment.rb +7 -8
  4. data/examples/attachments/nested_document.rb +6 -5
  5. data/examples/encryption/encryption.rb +5 -4
  6. data/examples/events/events.rb +7 -6
  7. data/examples/flash/flash.rb +10 -9
  8. data/examples/forms/javascript.rb +14 -13
  9. data/examples/forms/xfa.rb +67 -66
  10. data/examples/javascript/hello_world.rb +6 -5
  11. data/examples/javascript/js_emulation.rb +26 -26
  12. data/examples/loop/goto.rb +12 -11
  13. data/examples/loop/named.rb +17 -16
  14. data/examples/signature/signature.rb +11 -11
  15. data/examples/uri/javascript.rb +25 -24
  16. data/examples/uri/open-uri.rb +5 -4
  17. data/examples/uri/submitform.rb +11 -10
  18. data/lib/origami/3d.rb +330 -334
  19. data/lib/origami/acroform.rb +267 -268
  20. data/lib/origami/actions.rb +266 -278
  21. data/lib/origami/annotations.rb +659 -670
  22. data/lib/origami/array.rb +192 -196
  23. data/lib/origami/boolean.rb +66 -70
  24. data/lib/origami/catalog.rb +360 -363
  25. data/lib/origami/collections.rb +132 -133
  26. data/lib/origami/compound.rb +125 -129
  27. data/lib/origami/destinations.rb +226 -237
  28. data/lib/origami/dictionary.rb +155 -154
  29. data/lib/origami/encryption.rb +967 -923
  30. data/lib/origami/extensions/fdf.rb +270 -275
  31. data/lib/origami/extensions/ppklite.rb +323 -328
  32. data/lib/origami/filespec.rb +170 -173
  33. data/lib/origami/filters/ascii.rb +162 -167
  34. data/lib/origami/filters/ccitt/tables.rb +248 -252
  35. data/lib/origami/filters/ccitt.rb +309 -312
  36. data/lib/origami/filters/crypt.rb +31 -34
  37. data/lib/origami/filters/dct.rb +47 -50
  38. data/lib/origami/filters/flate.rb +57 -60
  39. data/lib/origami/filters/jbig2.rb +50 -53
  40. data/lib/origami/filters/jpx.rb +40 -43
  41. data/lib/origami/filters/lzw.rb +151 -155
  42. data/lib/origami/filters/predictors.rb +250 -255
  43. data/lib/origami/filters/runlength.rb +111 -115
  44. data/lib/origami/filters.rb +319 -325
  45. data/lib/origami/font.rb +173 -177
  46. data/lib/origami/functions.rb +62 -66
  47. data/lib/origami/graphics/colors.rb +203 -208
  48. data/lib/origami/graphics/instruction.rb +79 -81
  49. data/lib/origami/graphics/path.rb +141 -144
  50. data/lib/origami/graphics/patterns.rb +156 -160
  51. data/lib/origami/graphics/render.rb +51 -47
  52. data/lib/origami/graphics/state.rb +144 -142
  53. data/lib/origami/graphics/text.rb +185 -188
  54. data/lib/origami/graphics/xobject.rb +818 -804
  55. data/lib/origami/graphics.rb +25 -26
  56. data/lib/origami/header.rb +63 -65
  57. data/lib/origami/javascript.rb +718 -651
  58. data/lib/origami/linearization.rb +284 -285
  59. data/lib/origami/metadata.rb +156 -135
  60. data/lib/origami/name.rb +98 -100
  61. data/lib/origami/null.rb +49 -51
  62. data/lib/origami/numeric.rb +133 -135
  63. data/lib/origami/obfuscation.rb +180 -182
  64. data/lib/origami/object.rb +634 -631
  65. data/lib/origami/optionalcontent.rb +147 -149
  66. data/lib/origami/outline.rb +46 -48
  67. data/lib/origami/outputintents.rb +76 -77
  68. data/lib/origami/page.rb +637 -596
  69. data/lib/origami/parser.rb +214 -221
  70. data/lib/origami/parsers/fdf.rb +44 -45
  71. data/lib/origami/parsers/pdf/lazy.rb +147 -154
  72. data/lib/origami/parsers/pdf/linear.rb +104 -109
  73. data/lib/origami/parsers/pdf.rb +109 -107
  74. data/lib/origami/parsers/ppklite.rb +44 -46
  75. data/lib/origami/pdf.rb +886 -896
  76. data/lib/origami/reference.rb +116 -120
  77. data/lib/origami/signature.rb +617 -625
  78. data/lib/origami/stream.rb +560 -558
  79. data/lib/origami/string.rb +366 -368
  80. data/lib/origami/template/patterns.rb +50 -52
  81. data/lib/origami/template/widgets.rb +111 -114
  82. data/lib/origami/trailer.rb +153 -157
  83. data/lib/origami/tree.rb +55 -57
  84. data/lib/origami/version.rb +19 -19
  85. data/lib/origami/webcapture.rb +87 -90
  86. data/lib/origami/xfa/config.rb +409 -414
  87. data/lib/origami/xfa/connectionset.rb +113 -117
  88. data/lib/origami/xfa/datasets.rb +38 -42
  89. data/lib/origami/xfa/localeset.rb +33 -37
  90. data/lib/origami/xfa/package.rb +49 -52
  91. data/lib/origami/xfa/pdf.rb +54 -59
  92. data/lib/origami/xfa/signature.rb +33 -37
  93. data/lib/origami/xfa/sourceset.rb +34 -38
  94. data/lib/origami/xfa/stylesheet.rb +35 -39
  95. data/lib/origami/xfa/template.rb +1630 -1634
  96. data/lib/origami/xfa/xdc.rb +33 -37
  97. data/lib/origami/xfa/xfa.rb +132 -123
  98. data/lib/origami/xfa/xfdf.rb +34 -38
  99. data/lib/origami/xfa/xmpmeta.rb +34 -38
  100. data/lib/origami/xfa.rb +50 -53
  101. data/lib/origami/xreftable.rb +462 -462
  102. data/lib/origami.rb +37 -38
  103. data/test/test_actions.rb +22 -20
  104. data/test/test_annotations.rb +54 -52
  105. data/test/test_forms.rb +23 -21
  106. data/test/test_native_types.rb +82 -78
  107. data/test/test_object_tree.rb +25 -24
  108. data/test/test_pages.rb +43 -41
  109. data/test/test_pdf.rb +2 -0
  110. data/test/test_pdf_attachment.rb +23 -21
  111. data/test/test_pdf_create.rb +16 -15
  112. data/test/test_pdf_encrypt.rb +69 -66
  113. data/test/test_pdf_parse.rb +131 -129
  114. data/test/test_pdf_parse_lazy.rb +53 -53
  115. data/test/test_pdf_sign.rb +67 -67
  116. data/test/test_streams.rb +145 -143
  117. data/test/test_xrefs.rb +46 -45
  118. metadata +64 -8
@@ -1,760 +1,763 @@
1
- =begin
1
+ # frozen_string_literal: true
2
2
 
3
- This file is part of Origami, PDF manipulation framework for Ruby
4
- Copyright (C) 2016 Guillaume Delugré.
3
+ #
4
+ # This file is part of Origami, PDF manipulation framework for Ruby
5
+ # Copyright (C) 2016 Guillaume Delugré.
6
+ #
7
+ # Origami is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # Origami is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with Origami. If not, see <http://www.gnu.org/licenses/>.
19
+ #
5
20
 
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.
21
+ #
22
+ # Module for parsing/generating PDF files.
23
+ #
24
+ module Origami
25
+ #
26
+ # Provides refinements for standard Ruby types.
27
+ # Allows to convert native types to their associated Origami::Object types using method #to_o.
28
+ #
29
+ module TypeConversion
30
+ refine ::Integer do
31
+ def to_o
32
+ Origami::Integer.new(self)
33
+ end
34
+ end
10
35
 
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.
36
+ refine ::Array do
37
+ def to_o
38
+ Origami::Array.new(self)
39
+ end
40
+ end
15
41
 
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/>.
42
+ refine ::Float do
43
+ def to_o
44
+ Origami::Real.new(self)
45
+ end
46
+ end
18
47
 
19
- =end
48
+ refine ::Hash do
49
+ def to_o
50
+ Origami::Dictionary.new(self)
51
+ end
52
+ end
20
53
 
21
- require 'set'
54
+ refine ::TrueClass do
55
+ def to_o
56
+ Origami::Boolean.new(true)
57
+ end
58
+ end
22
59
 
23
- #
24
- # Module for parsing/generating PDF files.
25
- #
26
- module Origami
60
+ refine ::FalseClass do
61
+ def to_o
62
+ Origami::Boolean.new(false)
63
+ end
64
+ end
27
65
 
28
- #
29
- # Provides refinements for standard Ruby types.
30
- # Allows to convert native types to their associated Origami::Object types using method #to_o.
31
- #
32
- module TypeConversion
33
- refine ::Integer do
34
- def to_o
35
- Origami::Integer.new(self)
36
- end
37
- end
66
+ refine ::NilClass do
67
+ def to_o
68
+ Origami::Null.new
69
+ end
70
+ end
38
71
 
39
- refine ::Array do
40
- def to_o
41
- Origami::Array.new(self)
42
- end
43
- end
72
+ refine ::Symbol do
73
+ def to_o
74
+ Origami::Name.new(self)
75
+ end
76
+ end
44
77
 
45
- refine ::Float do
46
- def to_o
47
- Origami::Real.new(self)
48
- end
49
- end
78
+ refine ::String do
79
+ def to_o
80
+ Origami::LiteralString.new(self)
81
+ end
82
+ end
83
+ end
50
84
 
51
- refine ::Hash do
52
- def to_o
53
- Origami::Dictionary.new(self)
54
- end
55
- end
85
+ module TypeGuessing
86
+ using TypeConversion
56
87
 
57
- refine ::TrueClass do
58
- def to_o
59
- Origami::Boolean.new(true)
60
- end
61
- end
88
+ def guess_type(hash)
89
+ return self if (@@type_keys & hash.keys).empty?
90
+ best_match = self
62
91
 
63
- refine ::FalseClass do
64
- def to_o
65
- Origami::Boolean.new(false)
66
- end
67
- end
92
+ @@signatures.each_pair do |klass, keys|
93
+ next unless klass < best_match
68
94
 
69
- refine ::NilClass do
70
- def to_o
71
- Origami::Null.new
72
- end
73
- end
95
+ best_match = klass if keys.all? { |k, v| v.is_a?(Set) ? v.include?(hash[k]) : hash[k] == v }
96
+ end
74
97
 
75
- refine ::Symbol do
76
- def to_o
77
- Origami::Name.new(self)
78
- end
79
- end
98
+ best_match
99
+ end
100
+
101
+ private
102
+
103
+ def add_type_signature(**key_vals)
104
+ @@signatures ||= {}
105
+ @@type_keys ||= Set.new
80
106
 
81
- refine ::String do
82
- def to_o
83
- Origami::LiteralString.new(self)
84
- end
107
+ # Inherit the superclass type information.
108
+ if !@@signatures.key?(self) && @@signatures.key?(superclass)
109
+ @@signatures[self] = @@signatures[superclass].dup
110
+ end
111
+
112
+ @@signatures[self] ||= {}
113
+
114
+ key_vals.each_pair do |key, value|
115
+ key, value = key.to_o, value.to_o
116
+
117
+ if @@signatures[self].key?(key)
118
+ if @@signatures[self][key].is_a?(Set)
119
+ @@signatures[self][key].add(value)
120
+ elsif @@signatures[self][key] != value
121
+ @@signatures[self][key] = Set.new.add(@@signatures[self][key]).add(value)
122
+ end
123
+ else
124
+ @@signatures[self][key] = value
85
125
  end
126
+
127
+ @@type_keys.add(key)
128
+ end
129
+ end
130
+ end
131
+
132
+ #
133
+ # Provides an easier syntax for field access.
134
+ # The object must have the defined the methods #[] and #[]=.
135
+ #
136
+ # Once included, object.Field will automatically resolve to object[:Field].
137
+ # References are automatically followed.
138
+ #
139
+ module FieldAccessor
140
+ def method_missing(field, *args)
141
+ raise NoMethodError, "No method `#{field}' for #{self.class}" unless field =~ /^[[:upper:]]/
142
+
143
+ if field[-1] == '='
144
+ self[field[0..-2].to_sym] = args.first
145
+ else
146
+ object = self[field]
147
+ object.is_a?(Reference) ? object.solve : object
148
+ end
149
+ end
150
+
151
+ def respond_to_missing?(field, *)
152
+ !(field =~ /^[[:upper:]]/).nil? or super
86
153
  end
154
+ end
87
155
 
88
- module TypeGuessing
89
- using TypeConversion
156
+ #
157
+ # Mixin' module for objects which can store their options into an inner Dictionary.
158
+ #
159
+ module StandardObject # :nodoc:
160
+ DEFAULT_ATTRIBUTES = {Type: Object, Version: "1.2"} # :nodoc:
90
161
 
91
- def guess_type(hash)
92
- return self if (@@type_keys & hash.keys).empty?
93
- best_match = self
162
+ def self.included(receiver) # :nodoc:
163
+ receiver.instance_variable_set(:@fields, Hash.new(DEFAULT_ATTRIBUTES))
164
+ receiver.extend(ClassMethods)
165
+ end
166
+
167
+ module ClassMethods # :nodoc:all
168
+ include TypeGuessing
94
169
 
95
- @@signatures.each_pair do |klass, keys|
96
- next unless klass < best_match
170
+ def inherited(subclass)
171
+ subclass.instance_variable_set(:@fields, @fields.map { |name, attributes| [name, attributes.clone] }.to_h)
172
+ end
97
173
 
98
- best_match = klass if keys.all? {|k,v| v.is_a?(Set) ? v.include?(hash[k]) : hash[k] == v }
99
- end
174
+ def fields
175
+ @fields
176
+ end
100
177
 
101
- best_match
178
+ #
179
+ # Define a new field with given attributes.
180
+ #
181
+ def field(name, attributes)
182
+ if attributes[:Required] && attributes.key?(:Default) && (attributes[:Type] == Name)
183
+ signature = {}
184
+ signature[name] = attributes[:Default]
185
+
186
+ add_type_signature(**signature)
102
187
  end
103
188
 
104
- private
189
+ if @fields.key?(name)
190
+ @fields[name].merge! attributes
191
+ else
192
+ @fields[name] = attributes
193
+ end
105
194
 
106
- def add_type_signature(**key_vals)
107
- @@signatures ||= {}
108
- @@type_keys ||= Set.new
195
+ define_field_methods(name)
196
+ end
109
197
 
110
- # Inherit the superclass type information.
111
- if not @@signatures.key?(self) and @@signatures.key?(self.superclass)
112
- @@signatures[self] = @@signatures[self.superclass].dup
113
- end
198
+ #
199
+ # Returns an array of required fields for the current Object.
200
+ #
201
+ def required_fields
202
+ fields = []
203
+ @fields.each_pair do |name, attributes|
204
+ fields << name if attributes[:Required] == true
205
+ end
114
206
 
115
- @@signatures[self] ||= {}
207
+ fields
208
+ end
116
209
 
117
- key_vals.each_pair do |key, value|
118
- key, value = key.to_o, value.to_o
210
+ #
211
+ # Returns the expected type for a field name.
212
+ #
213
+ def hint_type(name)
214
+ @fields[name][:Type] if @fields.key?(name)
215
+ end
119
216
 
120
- if @@signatures[self].key?(key)
121
- if @@signatures[self][key].is_a?(Set)
122
- @@signatures[self][key].add(value)
123
- elsif @@signatures[self][key] != value
124
- @@signatures[self][key] = Set.new.add(@@signatures[self][key]).add(value)
125
- end
126
- else
127
- @@signatures[self][key] = value
128
- end
217
+ private
129
218
 
130
- @@type_keys.add(key)
131
- end
219
+ def define_field_methods(field) # :nodoc:
220
+ #
221
+ # Getter method.
222
+ #
223
+ getter = field.to_s
224
+ begin
225
+ remove_method(getter)
226
+ rescue
227
+ NameError
228
+ end
229
+ define_method(getter) do
230
+ obj = self[field]
231
+ obj.is_a?(Reference) ? obj.solve : obj
132
232
  end
133
- end
134
-
135
- #
136
- # Provides an easier syntax for field access.
137
- # The object must have the defined the methods #[] and #[]=.
138
- #
139
- # Once included, object.Field will automatically resolve to object[:Field].
140
- # References are automatically followed.
141
- #
142
- module FieldAccessor
143
- def method_missing(field, *args)
144
- raise NoMethodError, "No method `#{field}' for #{self.class}" unless field =~ /^[[:upper:]]/
145
233
 
146
- if field[-1] == '='
147
- self[field[0..-2].to_sym] = args.first
148
- else
149
- object = self[field]
150
- object.is_a?(Reference) ? object.solve : object
151
- end
234
+ #
235
+ # Setter method.
236
+ #
237
+ setter = field.to_s + "="
238
+ begin
239
+ remove_method(setter)
240
+ rescue
241
+ NameError
242
+ end
243
+ define_method(setter) do |value|
244
+ self[field] = value
152
245
  end
153
246
 
154
- def respond_to_missing?(field, *)
155
- not (field =~ /^[[:upper:]]/).nil? or super
247
+ # Setter method returning self.
248
+ setter_self = "set" + field.to_s
249
+ begin
250
+ remove_method(setter_self)
251
+ rescue
252
+ NameError
156
253
  end
254
+ define_method(setter_self) do |value|
255
+ self[field] = value
256
+ self
257
+ end
258
+ end
259
+ end
260
+
261
+ def pre_build # :nodoc:
262
+ set_default_values
263
+ do_type_check if Origami::OPTIONS[:enable_type_checking] == true
264
+
265
+ super
157
266
  end
158
267
 
159
268
  #
160
- # Mixin' module for objects which can store their options into an inner Dictionary.
269
+ # Returns the version and level required by the current Object.
161
270
  #
162
- module StandardObject #:nodoc:
163
- DEFAULT_ATTRIBUTES = { :Type => Object, :Version => "1.2" } #:nodoc:
271
+ def version_required # :nodoc:
272
+ max = ["1.0", 0]
164
273
 
165
- def self.included(receiver) #:nodoc:
166
- receiver.instance_variable_set(:@fields, Hash.new(DEFAULT_ATTRIBUTES))
167
- receiver.extend(ClassMethods)
274
+ each_key do |field|
275
+ attributes = self.class.fields[field.value]
276
+ if attributes.nil?
277
+ warn "Warning: object #{self.class} has undocumented field #{field.value}"
278
+ next
168
279
  end
169
280
 
170
- module ClassMethods #:nodoc:all
171
- include TypeGuessing
172
-
173
- def inherited(subclass)
174
- subclass.instance_variable_set(:@fields, Hash[@fields.map{|name, attributes| [name, attributes.clone]}])
175
- end
176
-
177
- def fields
178
- @fields
179
- end
180
-
181
- #
182
- # Define a new field with given attributes.
183
- #
184
- def field(name, attributes)
185
- if attributes[:Required] and attributes.key?(:Default) and attributes[:Type] == Name
186
- signature = {}
187
- signature[name] = attributes[:Default]
188
-
189
- add_type_signature(**signature)
190
- end
191
-
192
- if @fields.key?(name)
193
- @fields[name].merge! attributes
194
- else
195
- @fields[name] = attributes
196
- end
197
-
198
- define_field_methods(name)
199
- end
200
-
201
- #
202
- # Returns an array of required fields for the current Object.
203
- #
204
- def required_fields
205
- fields = []
206
- @fields.each_pair do |name, attributes|
207
- fields << name if attributes[:Required] == true
208
- end
209
-
210
- fields
211
- end
212
-
213
- #
214
- # Returns the expected type for a field name.
215
- #
216
- def hint_type(name)
217
- @fields[name][:Type] if @fields.key?(name)
218
- end
219
-
220
- private
221
-
222
- def define_field_methods(field) #:nodoc:
223
-
224
- #
225
- # Getter method.
226
- #
227
- getter = field.to_s
228
- remove_method(getter) rescue NameError
229
- define_method(getter) do
230
- obj = self[field]
231
- obj.is_a?(Reference) ? obj.solve : obj
232
- end
233
-
234
- #
235
- # Setter method.
236
- #
237
- setter = field.to_s + "="
238
- remove_method(setter) rescue NameError
239
- define_method(setter) do |value|
240
- self[field] = value
241
- end
242
-
243
- # Setter method returning self.
244
- setter_self = "set" + field.to_s
245
- remove_method(setter_self) rescue NameError
246
- define_method(setter_self) do |value|
247
- self[field] = value
248
- self
249
- end
250
- end
251
- end
281
+ version = attributes[:Version] || '1.0'
282
+ level = attributes[:ExtensionLevel] || 0
283
+ current = [version, level]
252
284
 
253
- def pre_build #:nodoc:
254
- set_default_values
255
- do_type_check if Origami::OPTIONS[:enable_type_checking] == true
285
+ max = [max, current, self[field.value].version_required].max
286
+ end
256
287
 
257
- super
258
- end
288
+ max
289
+ end
259
290
 
260
- #
261
- # Returns the version and level required by the current Object.
262
- #
263
- def version_required #:nodoc:
264
- max = [ "1.0", 0 ]
291
+ private
265
292
 
266
- self.each_key do |field|
267
- attributes = self.class.fields[field.value]
268
- if attributes.nil?
269
- STDERR.puts "Warning: object #{self.class} has undocumented field #{field.value}"
270
- next
271
- end
293
+ def set_default_value(field) # :nodoc:
294
+ if self.class.fields[field][:Default]
295
+ self[field] = self.class.fields[field][:Default]
296
+ self[field].pre_build
297
+ end
298
+ end
272
299
 
273
- version = attributes[:Version] || '1.0'
274
- level = attributes[:ExtensionLevel] || 0
275
- current = [ version, level ]
300
+ def set_default_values # :nodoc:
301
+ self.class.required_fields.each do |field|
302
+ set_default_value(field) unless key?(field)
303
+ end
304
+ end
276
305
 
277
- max = [ max, current, self[field.value].version_required ].max
278
- end
306
+ def do_type_check # :nodoc:
307
+ self.class.fields.each_pair do |field, attributes|
308
+ next if self[field].nil? || attributes[:Type].nil?
279
309
 
280
- max
310
+ begin
311
+ field_value = self[field].solve
312
+ rescue InvalidReferenceError
313
+ warn "Warning: in object #{self.class}, field `#{field}' is an invalid reference (#{self[field]})"
314
+ next
281
315
  end
282
316
 
283
- private
317
+ types = attributes[:Type].is_a?(::Array) ? attributes[:Type] : [attributes[:Type]]
284
318
 
285
- def set_default_value(field) #:nodoc:
286
- if self.class.fields[field][:Default]
287
- self[field] = self.class.fields[field][:Default]
288
- self[field].pre_build
289
- end
319
+ unless types.any? { |type| !type.is_a?(Class) || field_value.is_a?(type.native_type) }
320
+ warn "Warning: in object #{self.class}, field `#{field}' has unexpected type #{field_value.class}"
290
321
  end
291
322
 
292
- def set_default_values #:nodoc:
293
- self.class.required_fields.each do |field|
294
- set_default_value(field) unless self.key?(field)
295
- end
323
+ if attributes.key?(:Assert) && !(attributes[:Assert] === field_value)
324
+ warn "Warning: assertion failed for field `#{field}' in object #{self.class}"
296
325
  end
326
+ end
327
+ end
328
+ end
297
329
 
298
- def do_type_check #:nodoc:
299
- self.class.fields.each_pair do |field, attributes|
300
- next if self[field].nil? or attributes[:Type].nil?
330
+ class InvalidObjectError < Error # :nodoc:
331
+ end
301
332
 
302
- begin
303
- field_value = self[field].solve
304
- rescue InvalidReferenceError
305
- STDERR.puts "Warning: in object #{self.class}, field `#{field}' is an invalid reference (#{self[field]})"
306
- next
307
- end
333
+ class UnterminatedObjectError < Error # :nodoc:
334
+ attr_reader :obj
308
335
 
309
- types = attributes[:Type].is_a?(::Array) ? attributes[:Type] : [ attributes[:Type] ]
336
+ def initialize(msg, obj)
337
+ super(msg)
338
+ @obj = obj
339
+ end
340
+ end
341
+
342
+ WHITESPACES = "([ \\f\\t\\r\\n\\0]|%[^\\n\\r]*(\\r\\n|\\r|\\n))*" # :nodoc:
343
+ WHITECHARS_NORET = "[ \\f\\t\\0]*" # :nodoc:
344
+ WHITECHARS = "[ \\f\\t\\r\\n\\0]*" # :nodoc:
345
+ REGEXP_WHITESPACES = Regexp.new(WHITESPACES) # :nodoc:
346
+
347
+ #
348
+ # Parent module representing a PDF Object.
349
+ # PDF specification declares a set of primitive object types :
350
+ # * Null
351
+ # * Boolean
352
+ # * Integer
353
+ # * Real
354
+ # * Name
355
+ # * String
356
+ # * Array
357
+ # * Dictionary
358
+ # * Stream
359
+ #
360
+ module Object
361
+ TOKENS = %w[obj endobj] # :nodoc:
362
+ @@regexp_obj = Regexp.new(WHITESPACES + "(?<no>\\d+)" + WHITESPACES + "(?<gen>\\d+)" +
363
+ WHITESPACES + TOKENS.first + WHITESPACES)
364
+ @@regexp_endobj = Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)
365
+
366
+ attr_accessor :no, :generation, :file_offset, :objstm_offset
367
+ attr_accessor :parent
310
368
 
311
- unless types.any? {|type| not type.is_a?(Class) or field_value.is_a?(type.native_type)}
312
- STDERR.puts "Warning: in object #{self.class}, field `#{field}' has unexpected type #{field_value.class}"
313
- end
369
+ #
370
+ # Modules or classes including this module are considered native types.
371
+ #
372
+ def self.included(base)
373
+ base.class_variable_set(:@@native_type, base)
374
+ base.extend(ClassMethods)
375
+ end
314
376
 
315
- if attributes.key?(:Assert) and not (attributes[:Assert] === field_value)
316
- STDERR.puts "Warning: assertion failed for field `#{field}' in object #{self.class}"
317
- end
318
- end
319
- end
377
+ module ClassMethods
378
+ # Returns the native type of the derived class or module.
379
+ def native_type
380
+ class_variable_get(:@@native_type)
381
+ end
382
+
383
+ private
384
+
385
+ # Propagate native type to submodules.
386
+ def included(klass)
387
+ klass.class_variable_set(:@@native_type, self)
388
+ klass.extend(ClassMethods)
389
+ end
320
390
  end
321
391
 
322
- class InvalidObjectError < Error #:nodoc:
392
+ #
393
+ # Returns the native type of the Object.
394
+ #
395
+ def native_type
396
+ self.class.native_type
323
397
  end
324
398
 
325
- class UnterminatedObjectError < Error #:nodoc:
326
- attr_reader :obj
399
+ #
400
+ # Creates a new PDF Object.
401
+ #
402
+ def initialize(*cons)
403
+ @indirect = false
404
+ @no, @generation = 0, 0
405
+ @document = nil
406
+ @parent = nil
407
+ @file_offset = nil
408
+
409
+ super unless cons.empty?
410
+ end
327
411
 
328
- def initialize(msg,obj)
329
- super(msg)
330
- @obj = obj
331
- end
412
+ #
413
+ # Sets whether the object is indirect or not.
414
+ # Indirect objects are allocated numbers at build time.
415
+ #
416
+ def set_indirect(bool)
417
+ unless (bool == true) || (bool == false)
418
+ raise TypeError, "The argument must be boolean"
419
+ end
420
+
421
+ if bool == false
422
+ @no = @generation = 0
423
+ @document = nil
424
+ @file_offset = nil
425
+ end
426
+
427
+ @indirect = bool
428
+ self
332
429
  end
333
430
 
334
- WHITESPACES = "([ \\f\\t\\r\\n\\0]|%[^\\n\\r]*(\\r\\n|\\r|\\n))*" #:nodoc:
335
- WHITECHARS_NORET = "[ \\f\\t\\0]*" #:nodoc:
336
- WHITECHARS = "[ \\f\\t\\r\\n\\0]*" #:nodoc:
337
- REGEXP_WHITESPACES = Regexp.new(WHITESPACES) #:nodoc:
431
+ #
432
+ # Generic method called just before the object is finalized.
433
+ # At this time, no number nor generation allocation has yet been done.
434
+ #
435
+ def pre_build
436
+ self
437
+ end
338
438
 
339
439
  #
340
- # Parent module representing a PDF Object.
341
- # PDF specification declares a set of primitive object types :
342
- # * Null
343
- # * Boolean
344
- # * Integer
345
- # * Real
346
- # * Name
347
- # * String
348
- # * Array
349
- # * Dictionary
350
- # * Stream
440
+ # Generic method called just after the object is finalized.
441
+ # At this time, any indirect object has its own number and generation identifier.
351
442
  #
352
- module Object
443
+ def post_build
444
+ self
445
+ end
353
446
 
354
- TOKENS = %w{ obj endobj } #:nodoc:
355
- @@regexp_obj = Regexp.new(WHITESPACES + "(?<no>\\d+)" + WHITESPACES + "(?<gen>\\d+)" +
356
- WHITESPACES + TOKENS.first + WHITESPACES)
357
- @@regexp_endobj = Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)
447
+ #
448
+ # Returns whether the objects is indirect, which means that it is not embedded into another object.
449
+ #
450
+ def indirect?
451
+ @indirect
452
+ end
358
453
 
359
- attr_accessor :no, :generation, :file_offset, :objstm_offset
360
- attr_accessor :parent
454
+ #
455
+ # Returns whether an object number exists for this object.
456
+ #
457
+ def numbered?
458
+ @no > 0
459
+ end
361
460
 
362
- #
363
- # Modules or classes including this module are considered native types.
364
- #
365
- def self.included(base)
366
- base.class_variable_set(:@@native_type, base)
367
- base.extend(ClassMethods)
368
- end
461
+ #
462
+ # Deep copy of an object.
463
+ #
464
+ def copy
465
+ saved_doc = @document
466
+ saved_parent = @parent
369
467
 
370
- module ClassMethods
371
- # Returns the native type of the derived class or module.
372
- def native_type
373
- self.class_variable_get(:@@native_type)
374
- end
468
+ @document = @parent = nil # do not process parent object and document in the copy
375
469
 
376
- private
470
+ # Perform the recursive copy (quite dirty).
471
+ copyobj = Marshal.load(Marshal.dump(self))
377
472
 
378
- # Propagate native type to submodules.
379
- def included(klass)
380
- klass.class_variable_set(:@@native_type, self)
381
- klass.extend(ClassMethods)
382
- end
383
- end
473
+ # restore saved values
474
+ @document = saved_doc
475
+ @parent = saved_parent
384
476
 
385
- #
386
- # Returns the native type of the Object.
387
- #
388
- def native_type
389
- self.class.native_type
390
- end
477
+ copyobj.set_document(saved_doc) if copyobj.indirect?
478
+ copyobj.parent = parent
391
479
 
392
- #
393
- # Creates a new PDF Object.
394
- #
395
- def initialize(*cons)
396
- @indirect = false
397
- @no, @generation = 0, 0
398
- @document = nil
399
- @parent = nil
400
- @file_offset = nil
401
-
402
- super(*cons) unless cons.empty?
403
- end
480
+ copyobj
481
+ end
404
482
 
405
- #
406
- # Sets whether the object is indirect or not.
407
- # Indirect objects are allocated numbers at build time.
408
- #
409
- def set_indirect(bool)
410
- unless bool == true or bool == false
411
- raise TypeError, "The argument must be boolean"
412
- end
413
-
414
- if bool == false
415
- @no = @generation = 0
416
- @document = nil
417
- @file_offset = nil
418
- end
419
-
420
- @indirect = bool
421
- self
422
- end
483
+ #
484
+ # Casts an object to a new type.
485
+ #
486
+ def cast_to(type, parser = nil)
487
+ assert_cast_type(type)
423
488
 
424
- #
425
- # Generic method called just before the object is finalized.
426
- # At this time, no number nor generation allocation has yet been done.
427
- #
428
- def pre_build
429
- self
430
- end
489
+ cast = type.new(copy, parser)
490
+ cast.file_offset = @file_offset
431
491
 
432
- #
433
- # Generic method called just after the object is finalized.
434
- # At this time, any indirect object has its own number and generation identifier.
435
- #
436
- def post_build
437
- self
438
- end
492
+ transfer_attributes(cast)
493
+ end
439
494
 
440
- #
441
- # Returns whether the objects is indirect, which means that it is not embedded into another object.
442
- #
443
- def indirect?
444
- @indirect
445
- end
495
+ #
496
+ # Returns an indirect reference to this object.
497
+ #
498
+ def reference
499
+ raise InvalidObjectError, "Cannot reference a direct object" unless indirect?
446
500
 
447
- #
448
- # Returns whether an object number exists for this object.
449
- #
450
- def numbered?
451
- @no > 0
452
- end
501
+ ref = Reference.new(@no, @generation)
502
+ ref.parent = self
453
503
 
454
- #
455
- # Deep copy of an object.
456
- #
457
- def copy
458
- saved_doc = @document
459
- saved_parent = @parent
504
+ ref
505
+ end
460
506
 
461
- @document = @parent = nil # do not process parent object and document in the copy
507
+ #
508
+ # Returns an array of references pointing to the current object.
509
+ #
510
+ def xrefs
511
+ raise InvalidObjectError, "Cannot find xrefs to a direct object" unless indirect?
512
+ raise InvalidObjectError, "Not attached to any document" if document.nil?
513
+
514
+ @document.each_object(compressed: true)
515
+ .flat_map { |object|
516
+ case object
517
+ when Stream
518
+ object.dictionary.xref_cache[reference]
519
+ when ObjectCache
520
+ object.xref_cache[reference]
521
+ end
522
+ }
523
+ .compact!
524
+ end
462
525
 
463
- # Perform the recursive copy (quite dirty).
464
- copyobj = Marshal.load(Marshal.dump(self))
526
+ #
527
+ # Creates an exportable version of current object.
528
+ # The exportable version is a copy of _self_ with solved references, no owning PDF and no parent.
529
+ # References to Catalog or PageTreeNode objects have been destroyed.
530
+ #
531
+ # When exported, an object can be moved into another document without hassle.
532
+ #
533
+ def export
534
+ exported_obj = logicalize
535
+ exported_obj.no = exported_obj.generation = 0
536
+ exported_obj.set_document(nil) if exported_obj.indirect?
537
+ exported_obj.parent = nil
538
+ exported_obj.xref_cache.clear
539
+
540
+ exported_obj
541
+ end
465
542
 
466
- # restore saved values
467
- @document = saved_doc
468
- @parent = saved_parent
543
+ #
544
+ # Returns a logicalized copy of _self_.
545
+ # See logicalize!
546
+ #
547
+ def logicalize # :nodoc:
548
+ copy.logicalize!
549
+ end
469
550
 
470
- copyobj.set_document(saved_doc) if copyobj.indirect?
471
- copyobj.parent = parent
551
+ #
552
+ # Transforms recursively every references to the copy of their respective object.
553
+ # Catalog and PageTreeNode objects are excluded to limit the recursion.
554
+ #
555
+ def logicalize! # :nodoc:
556
+ resolve_all_references(self)
557
+ end
472
558
 
473
- copyobj
474
- end
559
+ #
560
+ # Returns the indirect object which contains this object.
561
+ # If the current object is already indirect, returns self.
562
+ #
563
+ def indirect_parent
564
+ obj = self
565
+ obj = obj.parent until obj.indirect?
475
566
 
476
- #
477
- # Casts an object to a new type.
478
- #
479
- def cast_to(type, parser = nil)
480
- assert_cast_type(type)
567
+ obj
568
+ end
481
569
 
482
- cast = type.new(self.copy, parser)
483
- cast.file_offset = @file_offset
570
+ #
571
+ # Returns self.
572
+ #
573
+ def to_o
574
+ self
575
+ end
484
576
 
485
- transfer_attributes(cast)
486
- end
577
+ #
578
+ # Returns self.
579
+ #
580
+ def solve
581
+ self
582
+ end
487
583
 
488
- #
489
- # Returns an indirect reference to this object.
490
- #
491
- def reference
492
- raise InvalidObjectError, "Cannot reference a direct object" unless self.indirect?
584
+ #
585
+ # Returns the PDF which the object belongs to.
586
+ #
587
+ def document
588
+ if indirect? then @document
589
+ else
590
+ @parent&.document
591
+ end
592
+ end
493
593
 
494
- ref = Reference.new(@no, @generation)
495
- ref.parent = self
594
+ def set_document(doc)
595
+ raise InvalidObjectError, "You cannot set the document of a direct object" unless indirect?
496
596
 
497
- ref
498
- end
597
+ @document = doc
598
+ end
499
599
 
500
- #
501
- # Returns an array of references pointing to the current object.
502
- #
503
- def xrefs
504
- raise InvalidObjectError, "Cannot find xrefs to a direct object" unless self.indirect?
505
- raise InvalidObjectError, "Not attached to any document" if self.document.nil?
506
-
507
- @document.each_object(compressed: true)
508
- .flat_map { |object|
509
- case object
510
- when Stream
511
- object.dictionary.xref_cache[self.reference]
512
- when ObjectCache
513
- object.xref_cache[self.reference]
514
- end
515
- }
516
- .compact!
517
- end
600
+ class << self
601
+ def typeof(stream) # :nodoc:
602
+ scanner = Parser.init_scanner(stream)
603
+ scanner.skip(REGEXP_WHITESPACES)
518
604
 
519
- #
520
- # Creates an exportable version of current object.
521
- # The exportable version is a copy of _self_ with solved references, no owning PDF and no parent.
522
- # References to Catalog or PageTreeNode objects have been destroyed.
523
- #
524
- # When exported, an object can be moved into another document without hassle.
525
- #
526
- def export
527
- exported_obj = self.logicalize
528
- exported_obj.no = exported_obj.generation = 0
529
- exported_obj.set_document(nil) if exported_obj.indirect?
530
- exported_obj.parent = nil
531
- exported_obj.xref_cache.clear
532
-
533
- exported_obj
605
+ case scanner.peek(1)
606
+ when '/' then return Name
607
+ when '<'
608
+ return (scanner.peek(2) == '<<') ? Stream : HexaString
609
+ when '(' then return LiteralString
610
+ when '[' then return Origami::Array
611
+ when 'n'
612
+ return Null if scanner.peek(4) == 'null'
613
+ when 't'
614
+ return Boolean if scanner.peek(4) == 'true'
615
+ when 'f'
616
+ return Boolean if scanner.peek(5) == 'false'
617
+ else
618
+ if scanner.check(Reference::REGEXP_TOKEN) then return Reference
619
+ elsif scanner.check(Real::REGEXP_TOKEN) then return Real
620
+ elsif scanner.check(Integer::REGEXP_TOKEN) then return Integer
621
+ end
534
622
  end
535
623
 
536
- #
537
- # Returns a logicalized copy of _self_.
538
- # See logicalize!
539
- #
540
- def logicalize #:nodoc:
541
- self.copy.logicalize!
542
- end
624
+ nil
625
+ end
543
626
 
544
- #
545
- # Transforms recursively every references to the copy of their respective object.
546
- # Catalog and PageTreeNode objects are excluded to limit the recursion.
547
- #
548
- def logicalize! #:nodoc:
549
- resolve_all_references(self)
550
- end
627
+ def parse(stream, parser = nil) # :nodoc:
628
+ scanner = Parser.init_scanner(stream)
629
+ offset = scanner.pos
551
630
 
552
631
  #
553
- # Returns the indirect object which contains this object.
554
- # If the current object is already indirect, returns self.
632
+ # End of body ?
555
633
  #
556
- def indirect_parent
557
- obj = self
558
- obj = obj.parent until obj.indirect?
634
+ return nil if scanner.match?(/xref/) || scanner.match?(/trailer/) || scanner.match?(/startxref/)
559
635
 
560
- obj
636
+ if scanner.scan(@@regexp_obj).nil?
637
+ raise InvalidObjectError, "Object shall begin with '%d %d obj' statement"
561
638
  end
562
639
 
563
- #
564
- # Returns self.
565
- #
566
- def to_o
567
- self
568
- end
640
+ no = scanner['no'].to_i
641
+ gen = scanner['gen'].to_i
569
642
 
570
- #
571
- # Returns self.
572
- #
573
- def solve
574
- self
643
+ type = typeof(scanner)
644
+ if type.nil?
645
+ raise InvalidObjectError, "Cannot determine object (no:#{no},gen:#{gen}) type"
575
646
  end
576
647
 
577
- #
578
- # Returns the PDF which the object belongs to.
579
- #
580
- def document
581
- if self.indirect? then @document
582
- else
583
- @parent.document unless @parent.nil?
584
- end
648
+ begin
649
+ new_obj = type.parse(scanner, parser)
650
+ rescue
651
+ raise InvalidObjectError, "Failed to parse object (no:#{no},gen:#{gen})\n\t -> [#{$!.class}] #{$!.message}"
585
652
  end
586
653
 
587
- def set_document(doc)
588
- raise InvalidObjectError, "You cannot set the document of a direct object" unless self.indirect?
654
+ new_obj.set_indirect(true)
655
+ new_obj.no = no
656
+ new_obj.generation = gen
657
+ new_obj.file_offset = offset
589
658
 
590
- @document = doc
659
+ if scanner.skip(@@regexp_endobj).nil?
660
+ raise UnterminatedObjectError.new("Object shall end with 'endobj' statement", new_obj)
591
661
  end
592
662
 
593
- class << self
594
-
595
- def typeof(stream) #:nodoc:
596
- scanner = Parser.init_scanner(stream)
597
- scanner.skip(REGEXP_WHITESPACES)
598
-
599
- case scanner.peek(1)
600
- when '/' then return Name
601
- when '<'
602
- return (scanner.peek(2) == '<<') ? Stream : HexaString
603
- when '(' then return LiteralString
604
- when '[' then return Origami::Array
605
- when 'n' then
606
- return Null if scanner.peek(4) == 'null'
607
- when 't' then
608
- return Boolean if scanner.peek(4) == 'true'
609
- when 'f' then
610
- return Boolean if scanner.peek(5) == 'false'
611
- else
612
- if scanner.check(Reference::REGEXP_TOKEN) then return Reference
613
- elsif scanner.check(Real::REGEXP_TOKEN) then return Real
614
- elsif scanner.check(Integer::REGEXP_TOKEN) then return Integer
615
- else
616
- nil
617
- end
618
- end
619
-
620
- nil
621
- end
622
-
623
- def parse(stream, parser = nil) #:nodoc:
624
- scanner = Parser.init_scanner(stream)
625
- offset = scanner.pos
626
-
627
- #
628
- # End of body ?
629
- #
630
- return nil if scanner.match?(/xref/) or scanner.match?(/trailer/) or scanner.match?(/startxref/)
631
-
632
- if scanner.scan(@@regexp_obj).nil?
633
- raise InvalidObjectError, "Object shall begin with '%d %d obj' statement"
634
- end
635
-
636
- no = scanner['no'].to_i
637
- gen = scanner['gen'].to_i
638
-
639
- type = typeof(scanner)
640
- if type.nil?
641
- raise InvalidObjectError, "Cannot determine object (no:#{no},gen:#{gen}) type"
642
- end
643
-
644
- begin
645
- new_obj = type.parse(scanner, parser)
646
- rescue
647
- raise InvalidObjectError, "Failed to parse object (no:#{no},gen:#{gen})\n\t -> [#{$!.class}] #{$!.message}"
648
- end
649
-
650
- new_obj.set_indirect(true)
651
- new_obj.no = no
652
- new_obj.generation = gen
653
- new_obj.file_offset = offset
654
-
655
- if scanner.skip(@@regexp_endobj).nil?
656
- raise UnterminatedObjectError.new("Object shall end with 'endobj' statement", new_obj)
657
- end
658
-
659
- new_obj
660
- end
661
-
662
- def skip_until_next_obj(scanner) #:nodoc:
663
- [ @@regexp_obj, /xref/, /trailer/, /startxref/ ].each do |re|
664
- if scanner.scan_until(re)
665
- scanner.pos -= scanner.matched_size
666
- return true
667
- end
668
- end
669
-
670
- false
671
- end
672
- end
663
+ new_obj
664
+ end
673
665
 
674
- def version_required #:nodoc:
675
- [ '1.0', 0 ]
666
+ def skip_until_next_obj(scanner) # :nodoc:
667
+ [@@regexp_obj, /xref/, /trailer/, /startxref/].each do |re|
668
+ if scanner.scan_until(re)
669
+ scanner.pos -= scanner.matched_size
670
+ return true
671
+ end
676
672
  end
677
673
 
678
- #
679
- # Returns the symbol type of this Object.
680
- #
681
- def type
682
- name = (self.class.name or self.class.superclass.name or self.native_type.name)
674
+ false
675
+ end
676
+ end
683
677
 
684
- name.split("::").last.to_sym
685
- end
678
+ def version_required # :nodoc:
679
+ ['1.0', 0]
680
+ end
686
681
 
687
- #
688
- # Outputs this object into PDF code.
689
- # _data_:: The object data.
690
- #
691
- def to_s(data, eol: $/)
692
- content = ""
693
- content << "#{no} #{generation} #{TOKENS.first}" << eol if indirect? and numbered?
694
- content << data
695
- content << eol << TOKENS.last << eol if indirect? and numbered?
682
+ #
683
+ # Returns the symbol type of this Object.
684
+ #
685
+ def type
686
+ name = (self.class.name or self.class.superclass.name or native_type.name)
696
687
 
697
- content.force_encoding('binary')
698
- end
699
- alias output to_s
688
+ name.split("::").last.to_sym
689
+ end
700
690
 
701
- private
691
+ #
692
+ # Outputs this object into PDF code.
693
+ # _data_:: The object data.
694
+ #
695
+ def to_s(data, eol: $/)
696
+ content = +""
697
+ content << "#{no} #{generation} #{TOKENS.first}" << eol if indirect? && numbered?
698
+ content << data
699
+ content << eol << TOKENS.last << eol if indirect? && numbered?
702
700
 
703
- #
704
- # Raises a TypeError exception if the current object is not castable to the provided type.
705
- #
706
- def assert_cast_type(type) #:nodoc:
707
- if type.native_type != self.native_type
708
- raise TypeError, "Incompatible cast from #{self.class} to #{type}"
709
- end
710
- end
701
+ content.force_encoding('binary')
702
+ end
703
+ alias_method :output, :to_s
711
704
 
712
- #
713
- # Copy the attributes of the current object to another object.
714
- # Copied attributes do not include the file offset.
715
- #
716
- def transfer_attributes(target)
717
- target.no, target.generation = @no, @generation
718
- target.parent = @parent
719
- if self.indirect?
720
- target.set_indirect(true)
721
- target.set_document(@document)
722
- end
723
-
724
- target
705
+ private
706
+
707
+ #
708
+ # Raises a TypeError exception if the current object is not castable to the provided type.
709
+ #
710
+ def assert_cast_type(type) # :nodoc:
711
+ if type.native_type != native_type
712
+ raise TypeError, "Incompatible cast from #{self.class} to #{type}"
713
+ end
714
+ end
715
+
716
+ #
717
+ # Copy the attributes of the current object to another object.
718
+ # Copied attributes do not include the file offset.
719
+ #
720
+ def transfer_attributes(target)
721
+ target.no, target.generation = @no, @generation
722
+ target.parent = @parent
723
+ if indirect?
724
+ target.set_indirect(true)
725
+ target.set_document(@document)
726
+ end
727
+
728
+ target
729
+ end
730
+
731
+ #
732
+ # Replace all references of an object by their actual object value.
733
+ #
734
+ def resolve_all_references(obj, browsed: [], cache: {})
735
+ return obj if browsed.include?(obj)
736
+ browsed.push(obj)
737
+
738
+ if obj.is_a?(ObjectStream)
739
+ obj.each do |subobj|
740
+ resolve_all_references(subobj, browsed: browsed, cache: cache)
725
741
  end
742
+ end
726
743
 
727
- #
728
- # Replace all references of an object by their actual object value.
729
- #
730
- def resolve_all_references(obj, browsed: [], cache: {})
731
- return obj if browsed.include?(obj)
732
- browsed.push(obj)
733
-
734
- if obj.is_a?(ObjectStream)
735
- obj.each do |subobj|
736
- resolve_all_references(subobj, browsed: browsed, cache: cache)
737
- end
738
- end
739
-
740
- if obj.is_a?(Stream)
741
- resolve_all_references(obj.dictionary, browsed: browsed, cache: cache)
742
- end
743
-
744
- if obj.is_a?(CompoundObject)
745
- obj.update_values! do |subobj|
746
- if subobj.is_a?(Reference)
747
- subobj = (cache[subobj] ||= subobj.solve.copy)
748
- subobj.no = subobj.generation = 0
749
- subobj.parent = obj
750
- end
751
-
752
- resolve_all_references(subobj, browsed: browsed, cache: cache)
753
- end
754
- end
755
-
756
- obj
744
+ if obj.is_a?(Stream)
745
+ resolve_all_references(obj.dictionary, browsed: browsed, cache: cache)
746
+ end
747
+
748
+ if obj.is_a?(CompoundObject)
749
+ obj.update_values! do |subobj|
750
+ if subobj.is_a?(Reference)
751
+ subobj = (cache[subobj] ||= subobj.solve.copy)
752
+ subobj.no = subobj.generation = 0
753
+ subobj.parent = obj
754
+ end
755
+
756
+ resolve_all_references(subobj, browsed: browsed, cache: cache)
757
757
  end
758
- end
758
+ end
759
759
 
760
+ obj
761
+ end
762
+ end
760
763
  end