class HexaPDF::Object

See: PDF2.0 s7.3.10, s7.3.8
See: HexaPDF::Dictionary, HexaPDF::Stream, HexaPDF::Reference, HexaPDF::Document
and hashes.
that everything will work correctly, especially when using other collection types than arrays
Important: Users of HexaPDF may use other plain Ruby objects but then there is no guarantee
the string representation is handled automatically.
Time objects are represented as specially formatted string objects and conversion from and to
There are also some additional data structures built from these primitive ones. For example,
not necessary (except if it should become an indirect object).
can be used for most things, i.e. wrapping an plain Ruby object into an object of this class is
So working with PDF objects in HexaPDF is rather straightforward since the common Ruby objects
* Indirect Object (mapped to this class)
* Null (mapped to nil)
* Stream (mapped to the Stream class which is a Dictionary with the associated stream data)
* Dictionary (mapped to Hash objects)
* Array (mapped to Array objects)
* Names (mapped to Symbol objects)
* String (mapped to String objects with UTF-8 or binary encoding)
* Real (mapped to Float objects)
* Integer (mapped to Integer object)
* Boolean (mapped to true and false),
The PDF specification knows of the following object types:
== Allowed PDF Object Values
PDF Object is the same as the hash of its corresponding Reference object.
keys. Furthermore the implementation is compatible to the one of Reference, i.e. the hash of a
The methods #hash and #eql? are implemented so that objects of this class can be used as hash
additional functionality.
Most PDF objects in a PDF document are represented by subclasses of this class that provide
A PDF object should be connected to a PDF document, otherwise some methods may not work.
accessible if the subclass Stream is used.
Furthermore a PDF object may have an associated stream. However, this stream is only
an indirect object and can be used for referencing it (from possibly multiple places).
represents a direct object. Otherwise the object identifier uniquely identifies this object as
an object number and a generation number. If the object number is zero, then the PDF object
A PDF object is like a normal object but with an additional *object identifier* consisting of
== Overview
Objects of the PDF object system.

def self.deep_copy(object)

Creates a deep copy of the given object which retains the references to indirect objects.

HexaPDF::Object.deep_copy(object) -> copy
:call-seq:
def self.deep_copy(object)
  case object
  when Hash
    object.transform_values {|value| deep_copy(value) }
  when Array
    object.map {|o| deep_copy(o) }
  when HexaPDF::Object
    (object.indirect? || object.must_be_indirect? ? object : deep_copy(object.value))
  when HexaPDF::Reference
    object
  else
    object.dup
  end
end

def self.field(_name)

Returns +nil+ to end the recursion for field searching in Dictionary.field.
def self.field(_name)
  nil
end

def self.make_direct(object, document)

deleted from the document.
If an indirect object is found, it is turned into a direct object and the indirect object is

that references can be correctly resolved.
The +document+ argument needs to contain the Document instance to which +object+ belongs so

Makes sure that the object itself as well as all nested values are direct objects.
def self.make_direct(object, document)
  if object.kind_of?(HexaPDF::Object) && object.indirect?
    raise HexaPDF::Error, "Can't make a stream object a direct object" if object.data.stream
    object_to_delete = object
    object = object.value
    object_to_delete.document.delete(object_to_delete)
  end
  case object
  when HexaPDF::Object
    object.data.value = make_direct(object.data.value, document)
  when Hash
    object.transform_values! {|val| make_direct(val, document) }
  when Array
    object.map! {|val| make_direct(val, document) }
  when Reference
    object = make_direct(document.object(object), document)
  end
  object
end

def <=>(other)

are ordered first by object number and then by generation number.
If the other object does not respond to +oid+ or +gen+, +nil+ is returned. Otherwise objects

Compares this object to another object.
def <=>(other)
  return nil unless other.respond_to?(:oid) && other.respond_to?(:gen)
  (oid == other.oid ? gen <=> other.gen : oid <=> other.oid)
end

def ==(other)

this object.
* This object is not indirect and the other object is not an Object and equal to the value of
* The other object is a Reference with the same oid/gen.
* The other object is an Object and wraps the same #data structure.

Returns +true+ in the following cases:
def ==(other)
  (other.kind_of?(Object) && data == other.data) || (other.kind_of?(Reference) && other == self) ||
    (!indirect? && !other.kind_of?(Object) && other == data.value)
end

def after_data_change

properly.
A subclass implementing this method has to call +super+! Otherwise things might not work

changed.
This method is called whenever the value or the stream of the wrapped PDFData structure is
def after_data_change
end

def cache(key, value = Document::UNSET, update: false, &block)

This uses Document#cache internally.

Set +update+ to +true+ to force an update of the cached value.

there is already a cached value for the key and +update+ is +false+, it is just returned.
Caches and returns the given +value+ or the value of the block under the given cache key. If
def cache(key, value = Document::UNSET, update: false, &block)
  document.cache(@data, key, value, update: update, &block)
end

def cached?(key)

This uses Document#cached? internally.

Returns +true+ if there is a cached value for the given key.
def cached?(key)
  document.cached?(@data, key)
end

def clear_cache

Clears the cache for this object.
def clear_cache
  document.clear_cache(@data)
end

def config

Returns the configuration object of the PDF document.
def config
  document.config
end

def deep_copy

Note that indirect references are *not* copied! If that is also needed, use Importer::copy.

Makes a deep copy of the source PDF object and resets the object identifier.
def deep_copy
  obj = dup
  obj.instance_variable_set(:@data, @data.dup)
  obj.data.oid = 0
  obj.data.gen = 0
  obj.data.stream = @data.stream.dup if @data.stream.kind_of?(String)
  obj.data.value = self.class.deep_copy(@data.value)
  obj
end

def document

If no document is associated, an error is raised.

Returns the associated PDF document.
def document
  @document || raise(HexaPDF::Error, "No document associated with this object (#{inspect})")
end

def document?

Returns +true+ if a PDF document is associated.
def document?
  !@document.nil?
end

def eql?(other)

Returns +true+ if the other object references the same PDF object as this object.
def eql?(other)
  other.respond_to?(:oid) && oid == other.oid && other.respond_to?(:gen) && gen == other.gen
end

def gen

Returns the generation number of the PDF object.
def gen
  data.gen
end

def gen=(gen)

Sets the generation number of the PDF object.
def gen=(gen)
  data.gen = gen
end

def hash

Computes the hash value based on the object and generation numbers.
def hash
  oid.hash ^ gen.hash
end

def indirect?

zero).
Returns +true+ if the object is an indirect object (i.e. has an object number unequal to
def indirect?
  oid != 0
end

def initialize(value, document: nil, oid: nil, gen: nil, stream: nil)

oid, gen and stream values may be overridden by the corresponding keyword arguments.
Object, then its data is used. Otherwise the +value+ object is used as is. In all cases, the
The +value+ can either be a PDFData object in which case it is used directly. If it is a PDF

Creates a new PDF object wrapping the value.
def initialize(value, document: nil, oid: nil, gen: nil, stream: nil)
  @data = case value
          when PDFData then value
          when Object then value.data
          else PDFData.new(value)
          end
  @data.oid = oid if oid
  @data.gen = gen if gen
  @data.stream = stream if stream
  self.document = document
  self.must_be_indirect = false
  after_data_change
end

def inspect #:nodoc:

:nodoc:
def inspect #:nodoc:
  "#<#{self.class.name} [#{oid}, #{gen}] value=#{value.inspect}>"
end

def must_be_indirect?

Returns +true+ if the object must be an indirect object once it is written.
def must_be_indirect?
  @must_be_indirect
end

def null?

Returns +true+ if the object represents the PDF null object.
def null?
  value.nil?
end

def oid

Returns the object number of the PDF object.
def oid
  data.oid
end

def oid=(oid)

Sets the object number of the PDF object.
def oid=(oid)
  data.oid = oid
end

def perform_validation(&block)

end
end
yield("/OtherKey needs to contain an odd number of elements")
if value[:OtherKey] % 2 == 0

end
# No need to return early here because following check doesn't rely on /SomeKey
yield("Length of /SomeKey is invalid")
if value[:SomeKey].length != 7

super
def perform_validation

Here is a sample validation routine for a dictionary object type:

and not correcting would lead to exceptions the method has to return early.
After yielding, the problem has to be corrected if it is correctable. If it is not correctable

auto-correction is used).
the object that gets validated if it is different from this object (may happen when
description and whether the problem can be corrected. An optional third argument may contain
When the validation routine finds that the object is invalid, it has to yield a problem

are also performed!
A subclass needs to call the super method so that the validation routines of the superclasses

== Implementation Hint for Subclasses

Validates the basic object properties.
def perform_validation(&block)
  # Validate that the object is indirect if #must_be_indirect? is +true+.
  if must_be_indirect? && !indirect?
    yield("Object must be an indirect object", true)
    document.add(self)
  end
  validate_nested(value, &block)
end

def type

For basic objects this always returns +:Unknown+.

and therefore doesn't clash with names defined by the PDF specification.
that don't have such fields may use a unique name that has to begin with XX (see PDF2.0 sE.2)
However, the Type and Subtype fields can easily be used for this. Subclasses for PDF objects

type.
specific types, the class of an object can't be reliably used for determining the actual
Since the type system is implemented in such a way as to allow exchanging implementations of

Returns the type (symbol) of the object.
def type
  :Unknown
end

def validate(auto_correct: true)

certainly a problem!
currently implement the full PDF spec. However, if the return value is +false+, there is
*Note*: Even if the return value is +true+ there may be problems since HexaPDF doesn't

its documentation for more information.
The validation routine itself has to be implemented in the #perform_validation method - see

validated.
this object but may be another object if during auto-correction a new object was created and
whether the problem is automatically correctable. The third argument to the block is usually
If a block is given, it is called on validation problems with a problem description and

returns +true+ if the object is deemed valid and +false+ otherwise.
Validates the object, optionally corrects problems when the option +auto_correct+ is set and

obj.validate(auto_correct: true) {|msg, correctable, obj| block } -> true or false
obj.validate(auto_correct: true) -> true or false
:call-seq:
def validate(auto_correct: true)
  result = true
  perform_validation do |msg, correctable, object|
    yield(msg, correctable, object || self) if block_given?
    result = false unless correctable
    return false unless auto_correct
  end
  result
end

def validate_nested(obj, &block)

Validates all nested values of the object, i.e. values inside collection objects.
def validate_nested(obj, &block)
  if obj.kind_of?(HexaPDF::Object) && !obj.indirect?
    obj.validate(&block)
  elsif obj.kind_of?(Hash)
    obj.each_value {|val| validate_nested(val, &block) }
  elsif obj.kind_of?(Array)
    obj.each {|val| validate_nested(val, &block) }
  end
end

def value

Returns the object value.
def value
  data.value
end

def value=(val)

Sets the object value. Unlike in #initialize the value is used as is!
def value=(val)
  data.value = val
  after_data_change
end