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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/examples/attachments/attachment.rb +7 -8
- data/examples/attachments/nested_document.rb +6 -5
- data/examples/encryption/encryption.rb +5 -4
- data/examples/events/events.rb +7 -6
- data/examples/flash/flash.rb +10 -9
- data/examples/forms/javascript.rb +14 -13
- data/examples/forms/xfa.rb +67 -66
- data/examples/javascript/hello_world.rb +6 -5
- data/examples/javascript/js_emulation.rb +26 -26
- data/examples/loop/goto.rb +12 -11
- data/examples/loop/named.rb +17 -16
- data/examples/signature/signature.rb +11 -11
- data/examples/uri/javascript.rb +25 -24
- data/examples/uri/open-uri.rb +5 -4
- data/examples/uri/submitform.rb +11 -10
- data/lib/origami/3d.rb +330 -334
- data/lib/origami/acroform.rb +267 -268
- data/lib/origami/actions.rb +266 -278
- data/lib/origami/annotations.rb +659 -670
- data/lib/origami/array.rb +192 -196
- data/lib/origami/boolean.rb +66 -70
- data/lib/origami/catalog.rb +360 -363
- data/lib/origami/collections.rb +132 -133
- data/lib/origami/compound.rb +125 -129
- data/lib/origami/destinations.rb +226 -237
- data/lib/origami/dictionary.rb +155 -154
- data/lib/origami/encryption.rb +967 -923
- data/lib/origami/extensions/fdf.rb +270 -275
- data/lib/origami/extensions/ppklite.rb +323 -328
- data/lib/origami/filespec.rb +170 -173
- data/lib/origami/filters/ascii.rb +162 -167
- data/lib/origami/filters/ccitt/tables.rb +248 -252
- data/lib/origami/filters/ccitt.rb +309 -312
- data/lib/origami/filters/crypt.rb +31 -34
- data/lib/origami/filters/dct.rb +47 -50
- data/lib/origami/filters/flate.rb +57 -60
- data/lib/origami/filters/jbig2.rb +50 -53
- data/lib/origami/filters/jpx.rb +40 -43
- data/lib/origami/filters/lzw.rb +151 -155
- data/lib/origami/filters/predictors.rb +250 -255
- data/lib/origami/filters/runlength.rb +111 -115
- data/lib/origami/filters.rb +319 -325
- data/lib/origami/font.rb +173 -177
- data/lib/origami/functions.rb +62 -66
- data/lib/origami/graphics/colors.rb +203 -208
- data/lib/origami/graphics/instruction.rb +79 -81
- data/lib/origami/graphics/path.rb +141 -144
- data/lib/origami/graphics/patterns.rb +156 -160
- data/lib/origami/graphics/render.rb +51 -47
- data/lib/origami/graphics/state.rb +144 -142
- data/lib/origami/graphics/text.rb +185 -188
- data/lib/origami/graphics/xobject.rb +818 -804
- data/lib/origami/graphics.rb +25 -26
- data/lib/origami/header.rb +63 -65
- data/lib/origami/javascript.rb +718 -651
- data/lib/origami/linearization.rb +284 -285
- data/lib/origami/metadata.rb +156 -135
- data/lib/origami/name.rb +98 -100
- data/lib/origami/null.rb +49 -51
- data/lib/origami/numeric.rb +133 -135
- data/lib/origami/obfuscation.rb +180 -182
- data/lib/origami/object.rb +634 -631
- data/lib/origami/optionalcontent.rb +147 -149
- data/lib/origami/outline.rb +46 -48
- data/lib/origami/outputintents.rb +76 -77
- data/lib/origami/page.rb +637 -596
- data/lib/origami/parser.rb +214 -221
- data/lib/origami/parsers/fdf.rb +44 -45
- data/lib/origami/parsers/pdf/lazy.rb +147 -154
- data/lib/origami/parsers/pdf/linear.rb +104 -109
- data/lib/origami/parsers/pdf.rb +109 -107
- data/lib/origami/parsers/ppklite.rb +44 -46
- data/lib/origami/pdf.rb +886 -896
- data/lib/origami/reference.rb +116 -120
- data/lib/origami/signature.rb +617 -625
- data/lib/origami/stream.rb +560 -558
- data/lib/origami/string.rb +366 -368
- data/lib/origami/template/patterns.rb +50 -52
- data/lib/origami/template/widgets.rb +111 -114
- data/lib/origami/trailer.rb +153 -157
- data/lib/origami/tree.rb +55 -57
- data/lib/origami/version.rb +19 -19
- data/lib/origami/webcapture.rb +87 -90
- data/lib/origami/xfa/config.rb +409 -414
- data/lib/origami/xfa/connectionset.rb +113 -117
- data/lib/origami/xfa/datasets.rb +38 -42
- data/lib/origami/xfa/localeset.rb +33 -37
- data/lib/origami/xfa/package.rb +49 -52
- data/lib/origami/xfa/pdf.rb +54 -59
- data/lib/origami/xfa/signature.rb +33 -37
- data/lib/origami/xfa/sourceset.rb +34 -38
- data/lib/origami/xfa/stylesheet.rb +35 -39
- data/lib/origami/xfa/template.rb +1630 -1634
- data/lib/origami/xfa/xdc.rb +33 -37
- data/lib/origami/xfa/xfa.rb +132 -123
- data/lib/origami/xfa/xfdf.rb +34 -38
- data/lib/origami/xfa/xmpmeta.rb +34 -38
- data/lib/origami/xfa.rb +50 -53
- data/lib/origami/xreftable.rb +462 -462
- data/lib/origami.rb +37 -38
- data/test/test_actions.rb +22 -20
- data/test/test_annotations.rb +54 -52
- data/test/test_forms.rb +23 -21
- data/test/test_native_types.rb +82 -78
- data/test/test_object_tree.rb +25 -24
- data/test/test_pages.rb +43 -41
- data/test/test_pdf.rb +2 -0
- data/test/test_pdf_attachment.rb +23 -21
- data/test/test_pdf_create.rb +16 -15
- data/test/test_pdf_encrypt.rb +69 -66
- data/test/test_pdf_parse.rb +131 -129
- data/test/test_pdf_parse_lazy.rb +53 -53
- data/test/test_pdf_sign.rb +67 -67
- data/test/test_streams.rb +145 -143
- data/test/test_xrefs.rb +46 -45
- metadata +64 -8
data/lib/origami/object.rb
CHANGED
@@ -1,760 +1,763 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
36
|
+
refine ::Array do
|
37
|
+
def to_o
|
38
|
+
Origami::Array.new(self)
|
39
|
+
end
|
40
|
+
end
|
15
41
|
|
16
|
-
|
17
|
-
|
42
|
+
refine ::Float do
|
43
|
+
def to_o
|
44
|
+
Origami::Real.new(self)
|
45
|
+
end
|
46
|
+
end
|
18
47
|
|
19
|
-
|
48
|
+
refine ::Hash do
|
49
|
+
def to_o
|
50
|
+
Origami::Dictionary.new(self)
|
51
|
+
end
|
52
|
+
end
|
20
53
|
|
21
|
-
|
54
|
+
refine ::TrueClass do
|
55
|
+
def to_o
|
56
|
+
Origami::Boolean.new(true)
|
57
|
+
end
|
58
|
+
end
|
22
59
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
60
|
+
refine ::FalseClass do
|
61
|
+
def to_o
|
62
|
+
Origami::Boolean.new(false)
|
63
|
+
end
|
64
|
+
end
|
27
65
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
72
|
+
refine ::Symbol do
|
73
|
+
def to_o
|
74
|
+
Origami::Name.new(self)
|
75
|
+
end
|
76
|
+
end
|
44
77
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
78
|
+
refine ::String do
|
79
|
+
def to_o
|
80
|
+
Origami::LiteralString.new(self)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
50
84
|
|
51
|
-
|
52
|
-
|
53
|
-
Origami::Dictionary.new(self)
|
54
|
-
end
|
55
|
-
end
|
85
|
+
module TypeGuessing
|
86
|
+
using TypeConversion
|
56
87
|
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
70
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
89
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
96
|
-
|
170
|
+
def inherited(subclass)
|
171
|
+
subclass.instance_variable_set(:@fields, @fields.map { |name, attributes| [name, attributes.clone] }.to_h)
|
172
|
+
end
|
97
173
|
|
98
|
-
|
99
|
-
|
174
|
+
def fields
|
175
|
+
@fields
|
176
|
+
end
|
100
177
|
|
101
|
-
|
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
|
-
|
189
|
+
if @fields.key?(name)
|
190
|
+
@fields[name].merge! attributes
|
191
|
+
else
|
192
|
+
@fields[name] = attributes
|
193
|
+
end
|
105
194
|
|
106
|
-
|
107
|
-
|
108
|
-
@@type_keys ||= Set.new
|
195
|
+
define_field_methods(name)
|
196
|
+
end
|
109
197
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
207
|
+
fields
|
208
|
+
end
|
116
209
|
|
117
|
-
|
118
|
-
|
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
|
-
|
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
|
-
|
131
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
155
|
-
|
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
|
-
#
|
269
|
+
# Returns the version and level required by the current Object.
|
161
270
|
#
|
162
|
-
|
163
|
-
|
271
|
+
def version_required # :nodoc:
|
272
|
+
max = ["1.0", 0]
|
164
273
|
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
171
|
-
|
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
|
-
|
254
|
-
|
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
|
-
|
258
|
-
|
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
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
-
|
274
|
-
|
275
|
-
|
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
|
-
|
278
|
-
|
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
|
-
|
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
|
-
|
317
|
+
types = attributes[:Type].is_a?(::Array) ? attributes[:Type] : [attributes[:Type]]
|
284
318
|
|
285
|
-
|
286
|
-
|
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
|
-
|
293
|
-
|
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
|
-
|
299
|
-
|
300
|
-
next if self[field].nil? or attributes[:Type].nil?
|
330
|
+
class InvalidObjectError < Error # :nodoc:
|
331
|
+
end
|
301
332
|
|
302
|
-
|
303
|
-
|
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
|
-
|
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
|
-
|
312
|
-
|
313
|
-
|
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
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
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
|
-
|
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
|
-
|
326
|
-
|
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
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
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
|
-
#
|
341
|
-
#
|
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
|
-
|
443
|
+
def post_build
|
444
|
+
self
|
445
|
+
end
|
353
446
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
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
|
-
|
360
|
-
|
454
|
+
#
|
455
|
+
# Returns whether an object number exists for this object.
|
456
|
+
#
|
457
|
+
def numbered?
|
458
|
+
@no > 0
|
459
|
+
end
|
361
460
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
end
|
461
|
+
#
|
462
|
+
# Deep copy of an object.
|
463
|
+
#
|
464
|
+
def copy
|
465
|
+
saved_doc = @document
|
466
|
+
saved_parent = @parent
|
369
467
|
|
370
|
-
|
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
|
-
|
470
|
+
# Perform the recursive copy (quite dirty).
|
471
|
+
copyobj = Marshal.load(Marshal.dump(self))
|
377
472
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
klass.extend(ClassMethods)
|
382
|
-
end
|
383
|
-
end
|
473
|
+
# restore saved values
|
474
|
+
@document = saved_doc
|
475
|
+
@parent = saved_parent
|
384
476
|
|
385
|
-
|
386
|
-
|
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
|
-
|
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
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
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
|
-
|
449
|
-
#
|
450
|
-
def numbered?
|
451
|
-
@no > 0
|
452
|
-
end
|
501
|
+
ref = Reference.new(@no, @generation)
|
502
|
+
ref.parent = self
|
453
503
|
|
454
|
-
|
455
|
-
|
456
|
-
#
|
457
|
-
def copy
|
458
|
-
saved_doc = @document
|
459
|
-
saved_parent = @parent
|
504
|
+
ref
|
505
|
+
end
|
460
506
|
|
461
|
-
|
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
|
-
|
464
|
-
|
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
|
-
|
467
|
-
|
468
|
-
|
543
|
+
#
|
544
|
+
# Returns a logicalized copy of _self_.
|
545
|
+
# See logicalize!
|
546
|
+
#
|
547
|
+
def logicalize # :nodoc:
|
548
|
+
copy.logicalize!
|
549
|
+
end
|
469
550
|
|
470
|
-
|
471
|
-
|
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
|
-
|
474
|
-
|
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
|
-
|
478
|
-
#
|
479
|
-
def cast_to(type, parser = nil)
|
480
|
-
assert_cast_type(type)
|
567
|
+
obj
|
568
|
+
end
|
481
569
|
|
482
|
-
|
483
|
-
|
570
|
+
#
|
571
|
+
# Returns self.
|
572
|
+
#
|
573
|
+
def to_o
|
574
|
+
self
|
575
|
+
end
|
484
576
|
|
485
|
-
|
486
|
-
|
577
|
+
#
|
578
|
+
# Returns self.
|
579
|
+
#
|
580
|
+
def solve
|
581
|
+
self
|
582
|
+
end
|
487
583
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
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
|
-
|
495
|
-
|
594
|
+
def set_document(doc)
|
595
|
+
raise InvalidObjectError, "You cannot set the document of a direct object" unless indirect?
|
496
596
|
|
497
|
-
|
498
|
-
|
597
|
+
@document = doc
|
598
|
+
end
|
499
599
|
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
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
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
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
|
-
|
538
|
-
# See logicalize!
|
539
|
-
#
|
540
|
-
def logicalize #:nodoc:
|
541
|
-
self.copy.logicalize!
|
542
|
-
end
|
624
|
+
nil
|
625
|
+
end
|
543
626
|
|
544
|
-
|
545
|
-
|
546
|
-
|
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
|
-
#
|
554
|
-
# If the current object is already indirect, returns self.
|
632
|
+
# End of body ?
|
555
633
|
#
|
556
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
579
|
-
|
580
|
-
|
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
|
-
|
588
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
675
|
-
|
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
|
-
|
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
|
-
|
685
|
-
|
678
|
+
def version_required # :nodoc:
|
679
|
+
['1.0', 0]
|
680
|
+
end
|
686
681
|
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
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
|
-
|
698
|
-
|
699
|
-
alias output to_s
|
688
|
+
name.split("::").last.to_sym
|
689
|
+
end
|
700
690
|
|
701
|
-
|
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
|
-
|
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
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
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
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
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
|
-
|
758
|
+
end
|
759
759
|
|
760
|
+
obj
|
761
|
+
end
|
762
|
+
end
|
760
763
|
end
|