Module: Origami::Object

Included in:
Boolean, CompoundObject, 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.



359
360
361
# File 'lib/origami/object.rb', line 359

def file_offset
  @file_offset
end

#generationObject

Returns the value of attribute generation.



359
360
361
# File 'lib/origami/object.rb', line 359

def generation
  @generation
end

#noObject

Returns the value of attribute no.



359
360
361
# File 'lib/origami/object.rb', line 359

def no
  @no
end

#objstm_offsetObject

Returns the value of attribute objstm_offset.



359
360
361
# File 'lib/origami/object.rb', line 359

def objstm_offset
  @objstm_offset
end

#parentObject

Returns the value of attribute parent.



360
361
362
# File 'lib/origami/object.rb', line 360

def parent
  @parent
end

Class Method Details

.included(base) ⇒ Object

Modules or classes including this module are considered native types.



365
366
367
368
# File 'lib/origami/object.rb', line 365

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

.parse(stream, parser = nil) ⇒ Object

:nodoc:



623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
# File 'lib/origami/object.rb', line 623

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

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

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

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

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

    begin
        new_obj = type.parse(scanner, 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 scanner.skip(@@regexp_endobj).nil?
        raise UnterminatedObjectError.new("Object shall end with 'endobj' statement", new_obj)
    end

    new_obj
end

.skip_until_next_obj(scanner) ⇒ Object

:nodoc:



662
663
664
665
666
667
668
669
670
671
# File 'lib/origami/object.rb', line 662

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

    false
end

.typeof(stream) ⇒ Object

:nodoc:



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
# File 'lib/origami/object.rb', line 595

def typeof(stream) #:nodoc:
    scanner = Parser.init_scanner(stream)
    scanner.skip(REGEXP_WHITESPACES)

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

    nil
end

Instance Method Details

#cast_to(type, parser = nil) ⇒ Object

Casts an object to a new type.



479
480
481
482
483
484
485
486
# File 'lib/origami/object.rb', line 479

def cast_to(type, parser = nil)
    assert_cast_type(type)

    cast = type.new(self.copy, parser)
    cast.file_offset = @file_offset

    transfer_attributes(cast)
end

#copyObject

Deep copy of an object.



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/origami/object.rb', line 457

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.



580
581
582
583
584
585
# File 'lib/origami/object.rb', line 580

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.



526
527
528
529
530
531
532
533
534
# File 'lib/origami/object.rb', line 526

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:



443
444
445
# File 'lib/origami/object.rb', line 443

def indirect?
    @indirect
end

#indirect_parentObject

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



556
557
558
559
560
561
# File 'lib/origami/object.rb', line 556

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

    obj
end

#initialize(*cons) ⇒ Object

Creates a new PDF Object.



395
396
397
398
399
400
401
402
403
# File 'lib/origami/object.rb', line 395

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

    super(*cons) unless cons.empty?
end

#logicalizeObject

Returns a logicalized copy of self. See logicalize!



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

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.



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

def logicalize! #:nodoc:
    resolve_all_references(self)
end

#native_typeObject

Returns the native type of the Object.



388
389
390
# File 'lib/origami/object.rb', line 388

def native_type
    self.class.native_type
end

#numbered?Boolean

Returns whether an object number exists for this object.

Returns:



450
451
452
# File 'lib/origami/object.rb', line 450

def numbered?
    @no > 0
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.



436
437
438
# File 'lib/origami/object.rb', line 436

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.



428
429
430
# File 'lib/origami/object.rb', line 428

def pre_build
    self
end

#referenceObject

Returns an indirect reference to this object.

Raises:



491
492
493
494
495
496
497
498
# File 'lib/origami/object.rb', line 491

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:



587
588
589
590
591
# File 'lib/origami/object.rb', line 587

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.



409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/origami/object.rb', line 409

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
        @file_offset = nil
    end

    @indirect = bool
    self
end

#solveObject

Returns self.



573
574
575
# File 'lib/origami/object.rb', line 573

def solve
    self
end

#to_oObject

Returns self.



566
567
568
# File 'lib/origami/object.rb', line 566

def to_o
    self
end

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

Outputs this object into PDF code.

data

The object data.



691
692
693
694
695
696
697
698
# File 'lib/origami/object.rb', line 691

def to_s(data, eol: $/)
    content = ""
    content << "#{no} #{generation} #{TOKENS.first}" << eol if indirect? and numbered?
    content << data
    content << eol << TOKENS.last << eol if indirect? and numbered?

    content.force_encoding('binary')
end

#typeObject

Returns the symbol type of this Object.



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

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:



674
675
676
# File 'lib/origami/object.rb', line 674

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

#xrefsObject

Returns an array of references pointing to the current object.

Raises:



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
# File 'lib/origami/object.rb', line 503

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 ObjectCache
                    object.xref_cache[self.reference]
                end
             }
             .compact!
end