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.



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

def file_offset
  @file_offset
end

#generationObject

Returns the value of attribute generation.



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

def generation
  @generation
end

#noObject

Returns the value of attribute no.



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

def no
  @no
end

#objstm_offsetObject

Returns the value of attribute objstm_offset.



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

def objstm_offset
  @objstm_offset
end

#parentObject

Returns the value of attribute parent.



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

def parent
  @parent
end

Class Method Details

.included(base) ⇒ Object

Modules or classes including this module are considered native types.



372
373
374
375
# File 'lib/origami/object.rb', line 372

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

.parse(stream, parser = nil) ⇒ Object

:nodoc:



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
661
662
663
664
# File 'lib/origami/object.rb', line 627

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

  #
  # End of body ?
  #
  return nil if scanner.match?(/xref/) || scanner.match?(/trailer/) || 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:



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

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:



601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
# File 'lib/origami/object.rb', line 601

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'
    return Null if scanner.peek(4) == 'null'
  when 't'
    return Boolean if scanner.peek(4) == 'true'
  when 'f'
    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
    end
  end

  nil
end

Instance Method Details

#cast_to(type, parser = nil) ⇒ Object

Casts an object to a new type.



486
487
488
489
490
491
492
493
# File 'lib/origami/object.rb', line 486

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

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

  transfer_attributes(cast)
end

#copyObject

Deep copy of an object.



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/origami/object.rb', line 464

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.



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

def document
  if indirect? then @document
  else
    @parent&.document
  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.



533
534
535
536
537
538
539
540
541
# File 'lib/origami/object.rb', line 533

def export
  exported_obj = 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:



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

def indirect?
  @indirect
end

#indirect_parentObject

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



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

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

  obj
end

#initialize(*cons) ⇒ Object

Creates a new PDF Object.



402
403
404
405
406
407
408
409
410
# File 'lib/origami/object.rb', line 402

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

  super unless cons.empty?
end

#logicalizeObject

Returns a logicalized copy of self. See logicalize!



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

def logicalize # :nodoc:
  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.



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

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

#native_typeObject

Returns the native type of the Object.



395
396
397
# File 'lib/origami/object.rb', line 395

def native_type
  self.class.native_type
end

#numbered?Boolean

Returns whether an object number exists for this object.

Returns:



457
458
459
# File 'lib/origami/object.rb', line 457

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.



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

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.



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

def pre_build
  self
end

#referenceObject

Returns an indirect reference to this object.

Raises:



498
499
500
501
502
503
504
505
# File 'lib/origami/object.rb', line 498

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

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

  ref
end

#set_document(doc) ⇒ Object

Raises:



594
595
596
597
598
# File 'lib/origami/object.rb', line 594

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

  @document = doc
end

#set_indirect(bool) ⇒ Object

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



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

def set_indirect(bool)
  unless (bool == true) || (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.



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

def solve
  self
end

#to_oObject

Returns self.



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

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.



695
696
697
698
699
700
701
702
# File 'lib/origami/object.rb', line 695

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

  content.force_encoding('binary')
end

#typeObject

Returns the symbol type of this Object.



685
686
687
688
689
# File 'lib/origami/object.rb', line 685

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

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

#version_requiredObject

:nodoc:



678
679
680
# File 'lib/origami/object.rb', line 678

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

#xrefsObject

Returns an array of references pointing to the current object.

Raises:



510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
# File 'lib/origami/object.rb', line 510

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

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