class HexaPDF::Revision
Embodies one revision of a PDF file, either the initial version or an incremental update.
The purpose of a Revision
object is to manage the objects and the trailer of one revision. These objects can either be added manually or loaded from a cross-reference section or stream. Since a PDF file can be incrementally updated, it can have multiple revisions.
If a revision doesn't have an associated cross-reference section, it wasn't created from a PDF file.
See: PDF1.7 s7.5.6, Revisions
Attributes
The callable object responsible for loading objects.
The trailer dictionary
Public Class Methods
Creates a new Revision
object.
Options:
- xref_section
-
An
XRefSection
object that contains information on how to load objects. If this option is specified, then aloader
or a block also needs to be specified! - loader
-
The loader object needs to respond to
call
taking a cross-reference entry and returning the loaded object. If noxref_section
is supplied, this value is not used.If a block is given, it is used instead of the loader object.
# File lib/hexapdf/revision.rb, line 79 def initialize(trailer, xref_section: nil, loader: nil, &block) @trailer = trailer @loader = xref_section && (block || loader) @xref_section = xref_section || XRefSection.new @objects = HexaPDF::Utils::ObjectHash.new end
Public Instance Methods
Adds the given object (needs to be a HexaPDF::Object
) to this revision and returns it.
# File lib/hexapdf/revision.rb, line 151 def add(obj) if object?(obj.oid) raise HexaPDF::Error, "A revision can only contain one object with a given object number" elsif !obj.indirect? raise HexaPDF::Error, "A revision can only contain indirect objects" end add_without_check(obj) end
Deletes the object specified either by reference or by object number from this revision by marking it as free.
If the mark_as_free
option is set to false
, the object is really deleted.
# File lib/hexapdf/revision.rb, line 184 def delete(ref_or_oid, mark_as_free: true) return unless object?(ref_or_oid) ref_or_oid = ref_or_oid.oid if ref_or_oid.respond_to?(:oid) obj = object(ref_or_oid) obj.data.value = nil obj.document = nil if mark_as_free add_without_check(HexaPDF::Object.new(nil, oid: obj.oid, gen: obj.gen)) else @xref_section.delete(ref_or_oid) @objects.delete(ref_or_oid) end end
Calls the given block for every object of the revision, or, if only_loaded
is true
, for every already loaded object.
Objects that are loadable via an associated cross-reference section but are currently not loaded, are loaded automatically if only_loaded
is false
.
# File lib/hexapdf/revision.rb, line 208 def each(only_loaded: false) return to_enum(__method__, only_loaded: only_loaded) unless block_given? if defined?(@all_objects_loaded) || only_loaded @objects.each {|_oid, _gen, data| yield(data) } else seen = {} @objects.each {|oid, _gen, data| seen[oid] = true; yield(data) } @xref_section.each do |oid, _gen, data| next if seen.key?(oid) yield(@objects[oid] || load_object(data)) end @all_objects_loaded = true end self end
Calls the given block once for each object that has been modified since it was loaded.
Note that this also means that for revisions without an associated cross-reference section all loaded objects will be yielded.
# File lib/hexapdf/revision.rb, line 234 def each_modified_object return to_enum(__method__) unless block_given? @objects.each do |oid, gen, obj| if @xref_section.entry?(oid, gen) stored_obj = @loader.call(@xref_section[oid, gen]) if obj.data.value != stored_obj.data.value || obj.data.stream != stored_obj.data.stream yield(obj) end else yield(obj) end end self end
Returns the next free object number for adding an object to this revision.
# File lib/hexapdf/revision.rb, line 87 def next_free_oid ((a = @xref_section.max_oid) < (b = @objects.max_oid) ? b : a) + 1 end
Returns the object for the given reference or object number if such an object is available in this revision, or nil
otherwise.
If the revision has an entry but one that is pointing to a free entry in the cross-reference section, an object representing PDF null is returned.
# File lib/hexapdf/revision.rb, line 114 def object(ref) if ref.respond_to?(:oid) oid = ref.oid gen = ref.gen else oid = ref end if @objects.entry?(oid, gen) @objects[oid, gen] elsif (xref_entry = @xref_section[oid, gen]) load_object(xref_entry) else nil end end
Returns true
if the revision contains an object
-
for the exact reference if the argument responds to :oid, or else
-
for the given object number.
# File lib/hexapdf/revision.rb, line 139 def object?(ref) if ref.respond_to?(:oid) @objects.entry?(ref.oid, ref.gen) || @xref_section.entry?(ref.oid, ref.gen) else @objects.entry?(ref) || @xref_section.entry?(ref) end end
Updates the stored object to point to the given HexaPDF::Object
wrapper, returning the object if successful or nil
otherwise.
If obj
isn't stored in this revision or the stored object doesn't contain the same HexaPDF::PDFData
object as the given object, nothing is done.
This method should only be used if the wrong wrapper class is stored (e.g. because auto-detection didn't or couldn't work correctly) and thus needs correction.
# File lib/hexapdf/revision.rb, line 171 def update(obj) return nil if object(obj)&.data != obj.data add_without_check(obj) end
Returns an XRefSection::Entry structure for the given reference or object number if it is available, or nil
otherwise.
# File lib/hexapdf/revision.rb, line 97 def xref(ref) if ref.respond_to?(:oid) @xref_section[ref.oid, ref.gen] else @xref_section[ref, nil] end end
Private Instance Methods
Adds the object to the available objects of this revision and returns it.
# File lib/hexapdf/revision.rb, line 259 def add_without_check(obj) @objects[obj.oid, obj.gen] = obj end
Loads a single object from the associated cross-reference section.
# File lib/hexapdf/revision.rb, line 254 def load_object(xref_entry) add_without_check(@loader.call(xref_entry)) end