Class: HexaPDF::Revision

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/hexapdf/revision.rb

Overview

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(trailer, xref_section: nil, loader: nil, &block) ⇒ Revision

:call-seq:

Revision.new(trailer)                                           -> revision
Revision.new(trailer, xref_section: section, loader: loader)    -> revision
Revision.new(trailer, xref_section: section) {|entry| block }   -> revision

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 a loader 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 no xref_section is supplied, this value is not used.

If a block is given, it is used instead of the loader object.



77
78
79
80
81
82
# File 'lib/hexapdf/revision.rb', line 77

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

Instance Attribute Details

#loaderObject

The callable object responsible for loading objects.



57
58
59
# File 'lib/hexapdf/revision.rb', line 57

def loader
  @loader
end

#trailerObject (readonly)

The trailer dictionary



54
55
56
# File 'lib/hexapdf/revision.rb', line 54

def trailer
  @trailer
end

Instance Method Details

#add(obj) ⇒ Object

:call-seq:

revision.add(obj)   -> obj

Adds the given object (needs to be a HexaPDF::Object) to this revision and returns it.



135
136
137
138
139
140
141
142
# File 'lib/hexapdf/revision.rb', line 135

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

#delete(ref_or_oid, mark_as_free: true) ⇒ Object

:call-seq:

revision.delete(ref, mark_as_free: true)
revision.delete(oid, mark_as_free: true)

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.



152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/hexapdf/revision.rb', line 152

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
  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

#eachObject

:call-seq:

revision.each {|obj| block }   -> revision
revision.each                  -> Enumerator

Calls the given block once for every object of the revision.

Objects that are loadable via an associated cross-reference section but are currently not, are loaded automatically.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/hexapdf/revision.rb', line 174

def each
  return to_enum(__method__) unless block_given?

  if defined?(@all_objects_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

#next_free_oidObject

Returns the next free object number for adding an object to this revision.



85
86
87
# File 'lib/hexapdf/revision.rb', line 85

def next_free_oid
  ((a = @xref_section.max_oid) < (b = @objects.max_oid) ? b : a) + 1
end

#object(ref) ⇒ Object

:call-seq:

revision.object(ref)    -> obj or nil
revision.object(oid)    -> obj or nil

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.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/hexapdf/revision.rb', line 98

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

#object?(ref) ⇒ Boolean

:call-seq:

revision.object?(ref)    -> true or false
revision.object?(oid)    -> true or false

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.



123
124
125
126
127
128
129
# File 'lib/hexapdf/revision.rb', line 123

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