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)
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)
def self.field(_name) nil end
def self.make_direct(object, 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)
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 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
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)
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)
Returns +true+ if there is a cached value for the given key.
def cached?(key) document.cached?(@data, key) end
def clear_cache
def clear_cache document.clear_cache(@data) end
def config
def config document.config end
def deep_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
Returns the associated PDF document.
def document @document || raise(HexaPDF::Error, "No document associated with this object (#{inspect})") end
def document?
def document? !@document.nil? end
def eql?(other)
def eql?(other) other.respond_to?(:oid) && oid == other.oid && other.respond_to?(:gen) && gen == other.gen end
def gen
def gen data.gen end
def gen=(gen)
def gen=(gen) data.gen = gen end
def hash
def hash oid.hash ^ gen.hash end
def indirect?
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)
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:
def inspect #:nodoc: "#<#{self.class.name} [#{oid}, #{gen}] value=#{value.inspect}>" end
def must_be_indirect?
def must_be_indirect? @must_be_indirect end
def null?
def null? value.nil? end
def oid
def oid data.oid end
def oid=(oid)
def oid=(oid) data.oid = oid end
def perform_validation(&block)
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
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)
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)
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
def value data.value end
def value=(val)
def value=(val) data.value = val after_data_change end