Class: HexaPDF::Revision
- Inherits:
-
Object
- Object
- HexaPDF::Revision
- 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: PDF2.0 s7.5.6, Revisions
Instance Attribute Summary collapse
-
#loader ⇒ Object
The callable object responsible for loading objects.
-
#trailer ⇒ Object
readonly
The trailer dictionary.
-
#xref_section ⇒ Object
readonly
The associated XRefSection object.
Instance Method Summary collapse
-
#add(obj) ⇒ Object
:call-seq: revision.add(obj) -> obj.
-
#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).
-
#each(only_loaded: false) ⇒ Object
:call-seq: revision.each(only_loaded: false) {|obj| block } -> revision revision.each(only_loaded: false) -> Enumerator.
-
#each_modified_object(delete: false, all: false) ⇒ Object
:call-seq: revision.each_modified_object(delete: false, all: all) {|obj| block } -> revision revision.each_modified_object(delete: false, all: all) -> Enumerator.
-
#initialize(trailer, xref_section: nil, loader: nil, &block) ⇒ Revision
constructor
:call-seq: Revision.new(trailer) -> revision Revision.new(trailer, xref_section: section, loader: loader) -> revision Revision.new(trailer, xref_section: section) {|entry| block } -> revision.
-
#next_free_oid ⇒ Object
Returns the next free object number for adding an object to this revision.
-
#object(ref) ⇒ Object
:call-seq: revision.object(ref) -> obj or nil revision.object(oid) -> obj or nil.
-
#object?(ref) ⇒ Boolean
:call-seq: revision.object?(ref) -> true or false revision.object?(oid) -> true or false.
-
#update(obj) ⇒ Object
:call-seq: revision.update(obj) -> obj or nil.
-
#xref(ref) ⇒ Object
:call-seq: revision.xref(ref) -> xref_entry or nil revision.xref(oid) -> xref_entry or nil.
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
loaderor a block also needs to be specified! - loader
-
The loader object needs to respond to
calltaking a cross-reference entry and returning the loaded object. If noxref_sectionis supplied, this value is not used.If a block is given, it is used instead of the loader object.
83 84 85 86 87 88 89 |
# File 'lib/hexapdf/revision.rb', line 83 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 @all_objects_loaded = false end |
Instance Attribute Details
#loader ⇒ Object
The callable object responsible for loading objects.
60 61 62 |
# File 'lib/hexapdf/revision.rb', line 60 def loader @loader end |
#trailer ⇒ Object (readonly)
The trailer dictionary
57 58 59 |
# File 'lib/hexapdf/revision.rb', line 57 def trailer @trailer end |
#xref_section ⇒ Object (readonly)
The associated XRefSection object.
63 64 65 |
# File 'lib/hexapdf/revision.rb', line 63 def xref_section @xref_section 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.
161 162 163 164 165 166 167 168 |
# File 'lib/hexapdf/revision.rb', line 161 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.
194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/hexapdf/revision.rb', line 194 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(obj.data)) else @xref_section.delete(ref_or_oid) @objects.delete(ref_or_oid) end end |
#each(only_loaded: false) ⇒ Object
:call-seq:
revision.each(only_loaded: false) {|obj| block } -> revision
revision.each(only_loaded: false) -> Enumerator
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.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/hexapdf/revision.rb', line 218 def each(only_loaded: false) return to_enum(__method__, only_loaded: only_loaded) unless block_given? if @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| yield(@objects[oid] || load_object(data)) unless seen.key?(oid) end @all_objects_loaded = true end self end |
#each_modified_object(delete: false, all: false) ⇒ Object
:call-seq:
revision.each_modified_object(delete: false, all: all) {|obj| block } -> revision
revision.each_modified_object(delete: false, all: all) -> Enumerator
Calls the given block once for each object that has been modified since it was loaded. Added or eleted object and cross-reference streams as well as signature dictionaries are ignored.
delete-
If the
deleteargument is set totrue, each modified object is deleted from the active objects. all-
If the
allargument is set totrue, added object and cross-reference streams are also yielded.
Note that this also means that for revisions without an associated cross-reference section all loaded objects will be yielded.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/hexapdf/revision.rb', line 250 def each_modified_object(delete: false, all: false) return to_enum(__method__, delete: delete, all: all) unless block_given? @objects.each do |oid, gen, obj| if @xref_section.entry?(oid, gen) stored_obj = @loader.call(@xref_section[oid, gen]) next if (stored_obj.type == :ObjStm || stored_obj.type == :XRef) && obj.null? || stored_obj.type == :Sig || stored_obj.type == :DocTimeStamp streams_are_same = (obj.data.stream == stored_obj.data.stream) next if obj.value == stored_obj.value && streams_are_same if obj.value.kind_of?(Hash) && stored_obj.value.kind_of?(Hash) keys = obj.value.keys | stored_obj.value.keys values_unchanged = keys.all? do |key| other = stored_obj[key] # Force comparison of values if both are indirect objects other = other.value if other.kind_of?(Object) && !other.indirect? obj[key] == other end next if values_unchanged && streams_are_same end elsif !all && (obj.type == :XRef || obj.type == :ObjStm) next end yield(obj) @objects.delete(oid) if delete end self end |
#next_free_oid ⇒ Object
Returns the next free object number for adding an object to this revision.
92 93 94 |
# File 'lib/hexapdf/revision.rb', line 92 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.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/hexapdf/revision.rb', line 119 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) elsif (xref_entry = @xref_section[oid]) && (obj = load_object(xref_entry))&.gen == gen # This branch handles invalid PDFs with a single revision containing xref entries where the # gen doesn't match the gen of the indirect object. Also see the special handling in # Parser#load_object. obj 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.
149 150 151 152 153 154 155 |
# File 'lib/hexapdf/revision.rb', line 149 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 |
#update(obj) ⇒ Object
:call-seq:
revision.update(obj) -> obj or nil
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.
181 182 183 184 |
# File 'lib/hexapdf/revision.rb', line 181 def update(obj) return nil if object(obj)&.data != obj.data add_without_check(obj) end |
#xref(ref) ⇒ Object
:call-seq:
revision.xref(ref) -> xref_entry or nil
revision.xref(oid) -> xref_entry or nil
Returns an XRefSection::Entry structure for the given reference or object number if it is available, or nil otherwise.
102 103 104 105 106 107 108 |
# File 'lib/hexapdf/revision.rb', line 102 def xref(ref) if ref.respond_to?(:oid) @xref_section[ref.oid, ref.gen] else @xref_section[ref, nil] end end |