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.
Files changed (139) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +89 -0
  3. data/COPYING.LESSER +165 -0
  4. data/README.md +131 -0
  5. data/bin/config/pdfcop.conf.yml +236 -0
  6. data/bin/pdf2pdfa +87 -0
  7. data/bin/pdf2ruby +333 -0
  8. data/bin/pdfcop +476 -0
  9. data/bin/pdfdecompress +97 -0
  10. data/bin/pdfdecrypt +91 -0
  11. data/bin/pdfencrypt +113 -0
  12. data/bin/pdfexplode +223 -0
  13. data/bin/pdfextract +277 -0
  14. data/bin/pdfmetadata +143 -0
  15. data/bin/pdfsh +12 -0
  16. data/bin/shell/console.rb +128 -0
  17. data/bin/shell/hexdump.rb +59 -0
  18. data/bin/shell/irbrc +69 -0
  19. data/examples/README.md +34 -0
  20. data/examples/attachments/attachment.rb +38 -0
  21. data/examples/attachments/nested_document.rb +51 -0
  22. data/examples/encryption/encryption.rb +28 -0
  23. data/examples/events/events.rb +72 -0
  24. data/examples/flash/flash.rb +37 -0
  25. data/examples/flash/helloworld.swf +0 -0
  26. data/examples/forms/javascript.rb +54 -0
  27. data/examples/forms/xfa.rb +115 -0
  28. data/examples/javascript/hello_world.rb +22 -0
  29. data/examples/javascript/js_emulation.rb +54 -0
  30. data/examples/loop/goto.rb +32 -0
  31. data/examples/loop/named.rb +33 -0
  32. data/examples/signature/signature.rb +65 -0
  33. data/examples/uri/javascript.rb +56 -0
  34. data/examples/uri/open-uri.rb +21 -0
  35. data/examples/uri/submitform.rb +47 -0
  36. data/lib/origami/3d.rb +364 -0
  37. data/lib/origami/acroform.rb +321 -0
  38. data/lib/origami/actions.rb +318 -0
  39. data/lib/origami/annotations.rb +711 -0
  40. data/lib/origami/array.rb +242 -0
  41. data/lib/origami/boolean.rb +90 -0
  42. data/lib/origami/catalog.rb +418 -0
  43. data/lib/origami/collections.rb +144 -0
  44. data/lib/origami/compound.rb +161 -0
  45. data/lib/origami/destinations.rb +252 -0
  46. data/lib/origami/dictionary.rb +192 -0
  47. data/lib/origami/encryption.rb +1084 -0
  48. data/lib/origami/extensions/fdf.rb +347 -0
  49. data/lib/origami/extensions/ppklite.rb +422 -0
  50. data/lib/origami/filespec.rb +197 -0
  51. data/lib/origami/filters/ascii.rb +211 -0
  52. data/lib/origami/filters/ccitt/tables.rb +267 -0
  53. data/lib/origami/filters/ccitt.rb +357 -0
  54. data/lib/origami/filters/crypt.rb +38 -0
  55. data/lib/origami/filters/dct.rb +54 -0
  56. data/lib/origami/filters/flate.rb +69 -0
  57. data/lib/origami/filters/jbig2.rb +57 -0
  58. data/lib/origami/filters/jpx.rb +47 -0
  59. data/lib/origami/filters/lzw.rb +170 -0
  60. data/lib/origami/filters/predictors.rb +292 -0
  61. data/lib/origami/filters/runlength.rb +129 -0
  62. data/lib/origami/filters.rb +364 -0
  63. data/lib/origami/font.rb +196 -0
  64. data/lib/origami/functions.rb +79 -0
  65. data/lib/origami/graphics/colors.rb +230 -0
  66. data/lib/origami/graphics/instruction.rb +98 -0
  67. data/lib/origami/graphics/path.rb +182 -0
  68. data/lib/origami/graphics/patterns.rb +174 -0
  69. data/lib/origami/graphics/render.rb +62 -0
  70. data/lib/origami/graphics/state.rb +149 -0
  71. data/lib/origami/graphics/text.rb +225 -0
  72. data/lib/origami/graphics/xobject.rb +918 -0
  73. data/lib/origami/graphics.rb +38 -0
  74. data/lib/origami/header.rb +75 -0
  75. data/lib/origami/javascript.rb +713 -0
  76. data/lib/origami/linearization.rb +330 -0
  77. data/lib/origami/metadata.rb +172 -0
  78. data/lib/origami/name.rb +135 -0
  79. data/lib/origami/null.rb +65 -0
  80. data/lib/origami/numeric.rb +181 -0
  81. data/lib/origami/obfuscation.rb +245 -0
  82. data/lib/origami/object.rb +760 -0
  83. data/lib/origami/optionalcontent.rb +183 -0
  84. data/lib/origami/outline.rb +54 -0
  85. data/lib/origami/outputintents.rb +85 -0
  86. data/lib/origami/page.rb +722 -0
  87. data/lib/origami/parser.rb +269 -0
  88. data/lib/origami/parsers/fdf.rb +56 -0
  89. data/lib/origami/parsers/pdf/lazy.rb +176 -0
  90. data/lib/origami/parsers/pdf/linear.rb +122 -0
  91. data/lib/origami/parsers/pdf.rb +118 -0
  92. data/lib/origami/parsers/ppklite.rb +57 -0
  93. data/lib/origami/pdf.rb +1108 -0
  94. data/lib/origami/reference.rb +134 -0
  95. data/lib/origami/signature.rb +702 -0
  96. data/lib/origami/stream.rb +705 -0
  97. data/lib/origami/string.rb +444 -0
  98. data/lib/origami/template/patterns.rb +56 -0
  99. data/lib/origami/template/widgets.rb +151 -0
  100. data/lib/origami/trailer.rb +190 -0
  101. data/lib/origami/tree.rb +62 -0
  102. data/lib/origami/version.rb +23 -0
  103. data/lib/origami/webcapture.rb +100 -0
  104. data/lib/origami/xfa/config.rb +453 -0
  105. data/lib/origami/xfa/connectionset.rb +146 -0
  106. data/lib/origami/xfa/datasets.rb +49 -0
  107. data/lib/origami/xfa/localeset.rb +42 -0
  108. data/lib/origami/xfa/package.rb +59 -0
  109. data/lib/origami/xfa/pdf.rb +73 -0
  110. data/lib/origami/xfa/signature.rb +42 -0
  111. data/lib/origami/xfa/sourceset.rb +43 -0
  112. data/lib/origami/xfa/stylesheet.rb +44 -0
  113. data/lib/origami/xfa/template.rb +1691 -0
  114. data/lib/origami/xfa/xdc.rb +42 -0
  115. data/lib/origami/xfa/xfa.rb +146 -0
  116. data/lib/origami/xfa/xfdf.rb +43 -0
  117. data/lib/origami/xfa/xmpmeta.rb +43 -0
  118. data/lib/origami/xfa.rb +62 -0
  119. data/lib/origami/xreftable.rb +557 -0
  120. data/lib/origami.rb +47 -0
  121. data/test/dataset/calc.pdf +85 -0
  122. data/test/dataset/crypto.pdf +36 -0
  123. data/test/dataset/empty.pdf +49 -0
  124. data/test/test_actions.rb +27 -0
  125. data/test/test_annotations.rb +68 -0
  126. data/test/test_forms.rb +30 -0
  127. data/test/test_native_types.rb +83 -0
  128. data/test/test_object_tree.rb +33 -0
  129. data/test/test_pages.rb +60 -0
  130. data/test/test_pdf.rb +20 -0
  131. data/test/test_pdf_attachment.rb +34 -0
  132. data/test/test_pdf_create.rb +24 -0
  133. data/test/test_pdf_encrypt.rb +102 -0
  134. data/test/test_pdf_parse.rb +134 -0
  135. data/test/test_pdf_parse_lazy.rb +69 -0
  136. data/test/test_pdf_sign.rb +97 -0
  137. data/test/test_streams.rb +184 -0
  138. data/test/test_xrefs.rb +67 -0
  139. 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