origamindee 3.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +89 -0
- data/COPYING.LESSER +165 -0
- data/README.md +131 -0
- data/bin/config/pdfcop.conf.yml +236 -0
- data/bin/pdf2pdfa +87 -0
- data/bin/pdf2ruby +333 -0
- data/bin/pdfcop +476 -0
- data/bin/pdfdecompress +97 -0
- data/bin/pdfdecrypt +91 -0
- data/bin/pdfencrypt +113 -0
- data/bin/pdfexplode +223 -0
- data/bin/pdfextract +277 -0
- data/bin/pdfmetadata +143 -0
- data/bin/pdfsh +12 -0
- data/bin/shell/console.rb +128 -0
- data/bin/shell/hexdump.rb +59 -0
- data/bin/shell/irbrc +69 -0
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/examples/events/events.rb +72 -0
- data/examples/flash/flash.rb +37 -0
- data/examples/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami/3d.rb +364 -0
- data/lib/origami/acroform.rb +321 -0
- data/lib/origami/actions.rb +318 -0
- data/lib/origami/annotations.rb +711 -0
- data/lib/origami/array.rb +242 -0
- data/lib/origami/boolean.rb +90 -0
- data/lib/origami/catalog.rb +418 -0
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/compound.rb +161 -0
- data/lib/origami/destinations.rb +252 -0
- data/lib/origami/dictionary.rb +192 -0
- data/lib/origami/encryption.rb +1084 -0
- data/lib/origami/extensions/fdf.rb +347 -0
- data/lib/origami/extensions/ppklite.rb +422 -0
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters/ascii.rb +211 -0
- data/lib/origami/filters/ccitt/tables.rb +267 -0
- data/lib/origami/filters/ccitt.rb +357 -0
- data/lib/origami/filters/crypt.rb +38 -0
- data/lib/origami/filters/dct.rb +54 -0
- data/lib/origami/filters/flate.rb +69 -0
- data/lib/origami/filters/jbig2.rb +57 -0
- data/lib/origami/filters/jpx.rb +47 -0
- data/lib/origami/filters/lzw.rb +170 -0
- data/lib/origami/filters/predictors.rb +292 -0
- data/lib/origami/filters/runlength.rb +129 -0
- data/lib/origami/filters.rb +364 -0
- data/lib/origami/font.rb +196 -0
- data/lib/origami/functions.rb +79 -0
- data/lib/origami/graphics/colors.rb +230 -0
- data/lib/origami/graphics/instruction.rb +98 -0
- data/lib/origami/graphics/path.rb +182 -0
- data/lib/origami/graphics/patterns.rb +174 -0
- data/lib/origami/graphics/render.rb +62 -0
- data/lib/origami/graphics/state.rb +149 -0
- data/lib/origami/graphics/text.rb +225 -0
- data/lib/origami/graphics/xobject.rb +918 -0
- data/lib/origami/graphics.rb +38 -0
- data/lib/origami/header.rb +75 -0
- data/lib/origami/javascript.rb +713 -0
- data/lib/origami/linearization.rb +330 -0
- data/lib/origami/metadata.rb +172 -0
- data/lib/origami/name.rb +135 -0
- data/lib/origami/null.rb +65 -0
- data/lib/origami/numeric.rb +181 -0
- data/lib/origami/obfuscation.rb +245 -0
- data/lib/origami/object.rb +760 -0
- data/lib/origami/optionalcontent.rb +183 -0
- data/lib/origami/outline.rb +54 -0
- data/lib/origami/outputintents.rb +85 -0
- data/lib/origami/page.rb +722 -0
- data/lib/origami/parser.rb +269 -0
- data/lib/origami/parsers/fdf.rb +56 -0
- data/lib/origami/parsers/pdf/lazy.rb +176 -0
- data/lib/origami/parsers/pdf/linear.rb +122 -0
- data/lib/origami/parsers/pdf.rb +118 -0
- data/lib/origami/parsers/ppklite.rb +57 -0
- data/lib/origami/pdf.rb +1108 -0
- data/lib/origami/reference.rb +134 -0
- data/lib/origami/signature.rb +702 -0
- data/lib/origami/stream.rb +705 -0
- data/lib/origami/string.rb +444 -0
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +190 -0
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +100 -0
- data/lib/origami/xfa/config.rb +453 -0
- data/lib/origami/xfa/connectionset.rb +146 -0
- data/lib/origami/xfa/datasets.rb +49 -0
- data/lib/origami/xfa/localeset.rb +42 -0
- data/lib/origami/xfa/package.rb +59 -0
- data/lib/origami/xfa/pdf.rb +73 -0
- data/lib/origami/xfa/signature.rb +42 -0
- data/lib/origami/xfa/sourceset.rb +43 -0
- data/lib/origami/xfa/stylesheet.rb +44 -0
- data/lib/origami/xfa/template.rb +1691 -0
- data/lib/origami/xfa/xdc.rb +42 -0
- data/lib/origami/xfa/xfa.rb +146 -0
- data/lib/origami/xfa/xfdf.rb +43 -0
- data/lib/origami/xfa/xmpmeta.rb +43 -0
- data/lib/origami/xfa.rb +62 -0
- data/lib/origami/xreftable.rb +557 -0
- data/lib/origami.rb +47 -0
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +36 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +68 -0
- data/test/test_forms.rb +30 -0
- data/test/test_native_types.rb +83 -0
- data/test/test_object_tree.rb +33 -0
- data/test/test_pages.rb +60 -0
- data/test/test_pdf.rb +20 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +102 -0
- data/test/test_pdf_parse.rb +134 -0
- data/test/test_pdf_parse_lazy.rb +69 -0
- data/test/test_pdf_sign.rb +97 -0
- data/test/test_streams.rb +184 -0
- data/test/test_xrefs.rb +67 -0
- metadata +280 -0
@@ -0,0 +1,760 @@
|
|
1
|
+
=begin
|
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/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'set'
|
22
|
+
|
23
|
+
#
|
24
|
+
# Module for parsing/generating PDF files.
|
25
|
+
#
|
26
|
+
module Origami
|
27
|
+
|
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
|
38
|
+
|
39
|
+
refine ::Array do
|
40
|
+
def to_o
|
41
|
+
Origami::Array.new(self)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
refine ::Float do
|
46
|
+
def to_o
|
47
|
+
Origami::Real.new(self)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
refine ::Hash do
|
52
|
+
def to_o
|
53
|
+
Origami::Dictionary.new(self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
refine ::TrueClass do
|
58
|
+
def to_o
|
59
|
+
Origami::Boolean.new(true)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
refine ::FalseClass do
|
64
|
+
def to_o
|
65
|
+
Origami::Boolean.new(false)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
refine ::NilClass do
|
70
|
+
def to_o
|
71
|
+
Origami::Null.new
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
refine ::Symbol do
|
76
|
+
def to_o
|
77
|
+
Origami::Name.new(self)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
refine ::String do
|
82
|
+
def to_o
|
83
|
+
Origami::LiteralString.new(self)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module TypeGuessing
|
89
|
+
using TypeConversion
|
90
|
+
|
91
|
+
def guess_type(hash)
|
92
|
+
return self if (@@type_keys & hash.keys).empty?
|
93
|
+
best_match = self
|
94
|
+
|
95
|
+
@@signatures.each_pair do |klass, keys|
|
96
|
+
next unless klass < best_match
|
97
|
+
|
98
|
+
best_match = klass if keys.all? {|k,v| v.is_a?(Set) ? v.include?(hash[k]) : hash[k] == v }
|
99
|
+
end
|
100
|
+
|
101
|
+
best_match
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def add_type_signature(**key_vals)
|
107
|
+
@@signatures ||= {}
|
108
|
+
@@type_keys ||= Set.new
|
109
|
+
|
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
|
114
|
+
|
115
|
+
@@signatures[self] ||= {}
|
116
|
+
|
117
|
+
key_vals.each_pair do |key, value|
|
118
|
+
key, value = key.to_o, value.to_o
|
119
|
+
|
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
|
129
|
+
|
130
|
+
@@type_keys.add(key)
|
131
|
+
end
|
132
|
+
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
|
+
|
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
|
152
|
+
end
|
153
|
+
|
154
|
+
def respond_to_missing?(field, *)
|
155
|
+
not (field =~ /^[[:upper:]]/).nil? or super
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Mixin' module for objects which can store their options into an inner Dictionary.
|
161
|
+
#
|
162
|
+
module StandardObject #:nodoc:
|
163
|
+
DEFAULT_ATTRIBUTES = { :Type => Object, :Version => "1.2" } #:nodoc:
|
164
|
+
|
165
|
+
def self.included(receiver) #:nodoc:
|
166
|
+
receiver.instance_variable_set(:@fields, Hash.new(DEFAULT_ATTRIBUTES))
|
167
|
+
receiver.extend(ClassMethods)
|
168
|
+
end
|
169
|
+
|
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
|
252
|
+
|
253
|
+
def pre_build #:nodoc:
|
254
|
+
set_default_values
|
255
|
+
do_type_check if Origami::OPTIONS[:enable_type_checking] == true
|
256
|
+
|
257
|
+
super
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
# Returns the version and level required by the current Object.
|
262
|
+
#
|
263
|
+
def version_required #:nodoc:
|
264
|
+
max = [ "1.0", 0 ]
|
265
|
+
|
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
|
272
|
+
|
273
|
+
version = attributes[:Version] || '1.0'
|
274
|
+
level = attributes[:ExtensionLevel] || 0
|
275
|
+
current = [ version, level ]
|
276
|
+
|
277
|
+
max = [ max, current, self[field.value].version_required ].max
|
278
|
+
end
|
279
|
+
|
280
|
+
max
|
281
|
+
end
|
282
|
+
|
283
|
+
private
|
284
|
+
|
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
|
290
|
+
end
|
291
|
+
|
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
|
296
|
+
end
|
297
|
+
|
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?
|
301
|
+
|
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
|
308
|
+
|
309
|
+
types = attributes[:Type].is_a?(::Array) ? attributes[:Type] : [ attributes[:Type] ]
|
310
|
+
|
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
|
314
|
+
|
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
|
320
|
+
end
|
321
|
+
|
322
|
+
class InvalidObjectError < Error #:nodoc:
|
323
|
+
end
|
324
|
+
|
325
|
+
class UnterminatedObjectError < Error #:nodoc:
|
326
|
+
attr_reader :obj
|
327
|
+
|
328
|
+
def initialize(msg,obj)
|
329
|
+
super(msg)
|
330
|
+
@obj = obj
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
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:
|
338
|
+
|
339
|
+
#
|
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
|
351
|
+
#
|
352
|
+
module Object
|
353
|
+
|
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)
|
358
|
+
|
359
|
+
attr_accessor :no, :generation, :file_offset, :objstm_offset
|
360
|
+
attr_accessor :parent
|
361
|
+
|
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
|
369
|
+
|
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
|
375
|
+
|
376
|
+
private
|
377
|
+
|
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
|
384
|
+
|
385
|
+
#
|
386
|
+
# Returns the native type of the Object.
|
387
|
+
#
|
388
|
+
def native_type
|
389
|
+
self.class.native_type
|
390
|
+
end
|
391
|
+
|
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
|
404
|
+
|
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
|
423
|
+
|
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
|
431
|
+
|
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
|
439
|
+
|
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
|
446
|
+
|
447
|
+
#
|
448
|
+
# Returns whether an object number exists for this object.
|
449
|
+
#
|
450
|
+
def numbered?
|
451
|
+
@no > 0
|
452
|
+
end
|
453
|
+
|
454
|
+
#
|
455
|
+
# Deep copy of an object.
|
456
|
+
#
|
457
|
+
def copy
|
458
|
+
saved_doc = @document
|
459
|
+
saved_parent = @parent
|
460
|
+
|
461
|
+
@document = @parent = nil # do not process parent object and document in the copy
|
462
|
+
|
463
|
+
# Perform the recursive copy (quite dirty).
|
464
|
+
copyobj = Marshal.load(Marshal.dump(self))
|
465
|
+
|
466
|
+
# restore saved values
|
467
|
+
@document = saved_doc
|
468
|
+
@parent = saved_parent
|
469
|
+
|
470
|
+
copyobj.set_document(saved_doc) if copyobj.indirect?
|
471
|
+
copyobj.parent = parent
|
472
|
+
|
473
|
+
copyobj
|
474
|
+
end
|
475
|
+
|
476
|
+
#
|
477
|
+
# Casts an object to a new type.
|
478
|
+
#
|
479
|
+
def cast_to(type, parser = nil)
|
480
|
+
assert_cast_type(type)
|
481
|
+
|
482
|
+
cast = type.new(self.copy, parser)
|
483
|
+
cast.file_offset = @file_offset
|
484
|
+
|
485
|
+
transfer_attributes(cast)
|
486
|
+
end
|
487
|
+
|
488
|
+
#
|
489
|
+
# Returns an indirect reference to this object.
|
490
|
+
#
|
491
|
+
def reference
|
492
|
+
raise InvalidObjectError, "Cannot reference a direct object" unless self.indirect?
|
493
|
+
|
494
|
+
ref = Reference.new(@no, @generation)
|
495
|
+
ref.parent = self
|
496
|
+
|
497
|
+
ref
|
498
|
+
end
|
499
|
+
|
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
|
518
|
+
|
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
|
534
|
+
end
|
535
|
+
|
536
|
+
#
|
537
|
+
# Returns a logicalized copy of _self_.
|
538
|
+
# See logicalize!
|
539
|
+
#
|
540
|
+
def logicalize #:nodoc:
|
541
|
+
self.copy.logicalize!
|
542
|
+
end
|
543
|
+
|
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
|
551
|
+
|
552
|
+
#
|
553
|
+
# Returns the indirect object which contains this object.
|
554
|
+
# If the current object is already indirect, returns self.
|
555
|
+
#
|
556
|
+
def indirect_parent
|
557
|
+
obj = self
|
558
|
+
obj = obj.parent until obj.indirect?
|
559
|
+
|
560
|
+
obj
|
561
|
+
end
|
562
|
+
|
563
|
+
#
|
564
|
+
# Returns self.
|
565
|
+
#
|
566
|
+
def to_o
|
567
|
+
self
|
568
|
+
end
|
569
|
+
|
570
|
+
#
|
571
|
+
# Returns self.
|
572
|
+
#
|
573
|
+
def solve
|
574
|
+
self
|
575
|
+
end
|
576
|
+
|
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
|
585
|
+
end
|
586
|
+
|
587
|
+
def set_document(doc)
|
588
|
+
raise InvalidObjectError, "You cannot set the document of a direct object" unless self.indirect?
|
589
|
+
|
590
|
+
@document = doc
|
591
|
+
end
|
592
|
+
|
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
|
673
|
+
|
674
|
+
def version_required #:nodoc:
|
675
|
+
[ '1.0', 0 ]
|
676
|
+
end
|
677
|
+
|
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)
|
683
|
+
|
684
|
+
name.split("::").last.to_sym
|
685
|
+
end
|
686
|
+
|
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?
|
696
|
+
|
697
|
+
content.force_encoding('binary')
|
698
|
+
end
|
699
|
+
alias output to_s
|
700
|
+
|
701
|
+
private
|
702
|
+
|
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
|
711
|
+
|
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
|
725
|
+
end
|
726
|
+
|
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
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
end
|