Module: Origami::Object

Included in:
Array, Boolean, Dictionary, Name, Null, Number, Reference, Stream, String
Defined in:
lib/origami/object.rb,
lib/origami/obfuscation.rb

Overview

Parent module representing a PDF Object. PDF specification declares a set of primitive object types :

  • Null

  • Boolean

  • Integer

  • Real

  • Name

  • String

  • Array

  • Dictionary

  • Stream

Defined Under Namespace

Modules: ClassMethods

Constant Summary collapse

TOKENS =

:nodoc:

%w{ obj endobj }
@@regexp_obj =
Regexp.new(WHITESPACES + "(?<no>\\d+)" + WHITESPACES + "(?<gen>\\d+)" +
WHITESPACES + TOKENS.first + WHITESPACES)
@@regexp_endobj =
Regexp.new(WHITESPACES + TOKENS.last + WHITESPACES)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#file_offsetObject

Returns the value of attribute file_offset.



316
317
318
# File 'lib/origami/object.rb', line 316

def file_offset
  @file_offset
end

#generationObject

Returns the value of attribute generation.



316
317
318
# File 'lib/origami/object.rb', line 316

def generation
  @generation
end

#noObject

Returns the value of attribute no.



316
317
318
# File 'lib/origami/object.rb', line 316

def no
  @no
end

#objstm_offsetObject

Returns the value of attribute objstm_offset.



316
317
318
# File 'lib/origami/object.rb', line 316

def objstm_offset
  @objstm_offset
end

#parentObject

Returns the value of attribute parent.



317
318
319
# File 'lib/origami/object.rb', line 317

def parent
  @parent
end

Class Method Details

.included(base) ⇒ Object

Modules or classes including this module are considered native types.



322
323
324
325
# File 'lib/origami/object.rb', line 322

def self.included(base)
    base.class_variable_set(:@@native_type, base)
    base.extend(ClassMethods)
end

.parse(stream, parser = nil) ⇒ Object

:nodoc:



603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
# File 'lib/origami/object.rb', line 603

def parse(stream, parser = nil) #:nodoc:
    offset = stream.pos

    #
    # End of body ?
    #
    return nil if stream.match?(/xref/) or stream.match?(/trailer/) or stream.match?(/startxref/)

    if stream.scan(@@regexp_obj).nil?
      raise InvalidObjectError,
        "Object shall begin with '%d %d obj' statement"
    end

    no = stream['no'].to_i
    gen = stream['gen'].to_i

    type = typeof(stream)
    if type.nil?
        raise InvalidObjectError,
                "Cannot determine object (no:#{no},gen:#{gen}) type"
    end

    begin
        new_obj = type.parse(stream, parser)
    rescue
        raise InvalidObjectError,
                "Failed to parse object (no:#{no},gen:#{gen})\n\t -> [#{$!.class}] #{$!.message}"
    end

    new_obj.set_indirect(true)
    new_obj.no = no
    new_obj.generation = gen
    new_obj.file_offset = offset

    if stream.skip(@@regexp_endobj).nil?
        raise UnterminatedObjectError.new("Object shall end with 'endobj' statement", new_obj)
    end

    new_obj
end

.skip_until_next_obj(stream) ⇒ Object

:nodoc:



644
645
646
647
648
649
650
651
652
653
# File 'lib/origami/object.rb', line 644

def skip_until_next_obj(stream) #:nodoc:
    [ @@regexp_obj, /xref/, /trailer/, /startxref/ ].each do |re|
        if stream.scan_until(re)
            stream.pos -= stream.matched_size
            return true
        end
    end

    false
end

.typeof(stream, noref = false) ⇒ Object

:nodoc:



576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
# File 'lib/origami/object.rb', line 576

def typeof(stream, noref = false) #:nodoc:
    stream.skip(REGEXP_WHITESPACES)

    case stream.peek(1)
    when '/' then return Name
    when '<'
        return (stream.peek(2) == '<<') ? Stream : HexaString
    when '(' then return LiteralString
    when '[' then return Origami::Array
    when 'n' then
        return Null if stream.peek(4) == 'null'
    when 't' then
        return Boolean if stream.peek(4) == 'true'
    when 'f' then
        return Boolean if stream.peek(5) == 'false'
    else
        if not noref and stream.check(Reference::REGEXP_TOKEN) then return Reference
        elsif stream.check(Real::REGEXP_TOKEN) then return Real
        elsif stream.check(Integer::REGEXP_TOKEN) then return Integer
        else
            nil
        end
    end

    nil
end

Instance Method Details

#<=>(obj) ⇒ Object

Compare two objects from their respective numbers.



398
399
400
# File 'lib/origami/object.rb', line 398

def <=>(obj)
    [@no, @generation] <=> [obj.no, obj.generation]
end

#cast_to(type, _parser = nil) ⇒ Object

:nodoc:



669
670
671
672
673
674
675
# File 'lib/origami/object.rb', line 669

def cast_to(type, _parser = nil) #:nodoc:
    if type.native_type != self.native_type
        raise TypeError, "Incompatible cast from #{self.class} to #{type}"
    end

    self
end

#copyObject

Deep copy of an object.



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/origami/object.rb', line 412

def copy
    saved_doc = @document
    saved_parent = @parent

    @document = @parent = nil # do not process parent object and document in the copy

    # Perform the recursive copy (quite dirty).
    copyobj = Marshal.load(Marshal.dump(self))

    # restore saved values
    @document = saved_doc
    @parent = saved_parent

    copyobj.set_document(saved_doc) if copyobj.indirect?
    copyobj.parent = parent

    copyobj
end

#documentObject

Returns the PDF which the object belongs to.



561
562
563
564
565
566
# File 'lib/origami/object.rb', line 561

def document
    if self.indirect? then @document
    else
        @parent.document unless @parent.nil?
    end
end

#exportObject

Creates an exportable version of current object. The exportable version is a copy of self with solved references, no owning PDF and no parent. References to Catalog or PageTreeNode objects have been destroyed.

When exported, an object can be moved into another document without hassle.



469
470
471
472
473
474
475
476
477
# File 'lib/origami/object.rb', line 469

def export
    exported_obj = self.logicalize
    exported_obj.no = exported_obj.generation = 0
    exported_obj.set_document(nil) if exported_obj.indirect?
    exported_obj.parent = nil
    exported_obj.xref_cache.clear

    exported_obj
end

#indirect?Boolean

Returns whether the objects is indirect, which means that it is not embedded into another object.

Returns:



405
406
407
# File 'lib/origami/object.rb', line 405

def indirect?
    @indirect
end

#indirect_parentObject

Returns the indirect object which contains this object. If the current object is already indirect, returns self.



537
538
539
540
541
542
# File 'lib/origami/object.rb', line 537

def indirect_parent
    obj = self
    obj = obj.parent until obj.indirect?

    obj
end

#initialize(*cons) ⇒ Object

Creates a new PDF Object.



352
353
354
355
356
357
358
359
# File 'lib/origami/object.rb', line 352

def initialize(*cons)
    @indirect = false
    @no, @generation = 0, 0
    @document = nil
    @parent = nil

    super(*cons) unless cons.empty?
end

#logicalizeObject

Returns a logicalized copy of self. See logicalize!



483
484
485
# File 'lib/origami/object.rb', line 483

def logicalize #:nodoc:
    self.copy.logicalize!
end

#logicalize!Object

Transforms recursively every references to the copy of their respective object. Catalog and PageTreeNode objects are excluded to limit the recursion.



491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/origami/object.rb', line 491

def logicalize! #:nodoc:

    resolve_all_references = -> (obj, browsed = [], ref_cache = {}) do
        return if browsed.include?(obj)
        browsed.push(obj)

        if obj.is_a?(ObjectStream)
            obj.each do |subobj|
                resolve_all_references[subobj, browsed, ref_cache]
            end
        end

        if obj.is_a?(Dictionary) or obj.is_a?(Array)
            obj.map! do |subobj|
                if subobj.is_a?(Reference)
                    new_obj =
                        if ref_cache.has_key?(subobj)
                            ref_cache[subobj]
                        else
                            ref_cache[subobj] = subobj.solve.copy
                        end
                    new_obj.no = new_obj.generation = 0
                    new_obj.parent = obj

                    new_obj unless new_obj.is_a?(Catalog) or new_obj.is_a?(PageTreeNode)
                else
                    subobj
                end
            end

            obj.each do |subobj|
                resolve_all_references[subobj, browsed, ref_cache]
            end

        elsif obj.is_a?(Stream)
            resolve_all_references[obj.dictionary, browsed, ref_cache]
        end
    end

    resolve_all_references[self]
end

#native_typeObject

Returns the native type of the Object.



345
346
347
# File 'lib/origami/object.rb', line 345

def native_type
    self.class.native_type
end

#post_buildObject

Generic method called just after the object is finalized. At this time, any indirect object has its own number and generation identifier.



391
392
393
# File 'lib/origami/object.rb', line 391

def post_build
    self
end

#pre_buildObject

Generic method called just before the object is finalized. At this time, no number nor generation allocation has yet been done.



383
384
385
# File 'lib/origami/object.rb', line 383

def pre_build
    self
end

#referenceObject

Returns an indirect reference to this object, or a Null object is this object is not indirect.

Raises:



434
435
436
437
438
439
440
441
# File 'lib/origami/object.rb', line 434

def reference
    raise InvalidObjectError, "Cannot reference a direct object" unless self.indirect?

    ref = Reference.new(@no, @generation)
    ref.parent = self

    ref
end

#set_document(doc) ⇒ Object

Raises:



568
569
570
571
572
# File 'lib/origami/object.rb', line 568

def set_document(doc)
    raise InvalidObjectError, "You cannot set the document of a direct object" unless self.indirect?

    @document = doc
end

#set_indirect(bool) ⇒ Object

Sets whether the object is indirect or not. Indirect objects are allocated numbers at build time.



365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/origami/object.rb', line 365

def set_indirect(bool)
    unless bool == true or bool == false
        raise TypeError, "The argument must be boolean"
    end

    if bool == false
        @no = @generation = 0
        @document = nil
    end

    @indirect = bool
    self
end

#solveObject

Returns self.



554
555
556
# File 'lib/origami/object.rb', line 554

def solve
    self
end

#to_oObject

Returns self.



547
548
549
# File 'lib/origami/object.rb', line 547

def to_o
    self
end

#to_s(data) ⇒ Object Also known as: output, to_obfuscated_str

Outputs this object into PDF code.

data

The object data.



681
682
683
684
685
686
687
688
# File 'lib/origami/object.rb', line 681

def to_s(data)
    content = ""
    content << "#{no} #{generation} #{TOKENS.first}" << EOL if self.indirect?
    content << data
    content << EOL << TOKENS.last << EOL if self.indirect?

    content.force_encoding('binary')
end

#typeObject

Returns the symbol type of this Object.



663
664
665
666
667
# File 'lib/origami/object.rb', line 663

def type
    name = (self.class.name or self.class.superclass.name or self.native_type.name)

    name.split("::").last.to_sym
end

#version_requiredObject

:nodoc:



656
657
658
# File 'lib/origami/object.rb', line 656

def version_required #:nodoc:
    [ 1.0, 0 ]
end

#xrefsObject

Returns an array of references pointing to the current object.

Raises:



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/origami/object.rb', line 446

def xrefs
    raise InvalidObjectError, "Cannot find xrefs to a direct object" unless self.indirect?
    raise InvalidObjectError, "Not attached to any document" if self.document.nil?

    @document.each_object(compressed: true)
             .flat_map { |object|
                case object
                when Stream
                    object.dictionary.xref_cache[self.reference]
                when Dictionary, Array
                    object.xref_cache[self.reference]
                end
             }
             .compact!
end