Class: PDF::Reader::ObjectHash

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/pdf/reader/object_hash.rb

Overview

Provides low level access to the objects in a PDF file via a hash-like object.

A PDF file can be viewed as a large hash map. It is a series of objects stored at precise byte offsets, and a table that maps object IDs to byte offsets. Given an object ID, looking up an object is an O(1) operation.

Each PDF object can be mapped to a ruby object, so by passing an object ID to the [] method, a ruby representation of that object will be retrieved.

The class behaves much like a standard Ruby hash, including the use of the Enumerable mixin. The key difference is no []= method - the hash is read only.

Basic Usage

h = PDF::Reader::ObjectHash.new("somefile.pdf")
h[1]
=> 3469

h[PDF::Reader::Reference.new(1,0)]
=> 3469

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, opts = {}) ⇒ ObjectHash

Creates a new ObjectHash object. Input can be a string with a valid filename or an IO-like object.

Valid options:

:password - the user password to decrypt the source PDF

Signature:

  • (untyped, ?Hash[Symbol, untyped]) -> void



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/pdf/reader/object_hash.rb', line 63

def initialize(input, opts = {})
  @io          = extract_io_from(input) #: untyped
  @xref        = PDF::Reader::XRef.new(@io) #: PDF::Reader::XRef[PDF::Reader::Reference]
  @pdf_version = read_version #: Float
  @trailer     = @xref.trailer #: Hash[Symbol, untyped]
  @cache       = opts[:cache] || PDF::Reader::ObjectCache.new #: PDF::Reader::ObjectCache
  @sec_handler = NullSecurityHandler.new #: securityHandler
  @sec_handler = SecurityHandlerFactory.build(
    deref(trailer[:Encrypt]),
    deref(trailer[:ID]),
    opts[:password]
  )
  @page_references = nil #: Array[PDF::Reader::Reference | Hash[Symbol, untyped]]?
  @object_streams = nil #: Hash[PDF::Reader::Reference, PDF::Reader::ObjectStream]?
end

Instance Attribute Details

#defaultObject

Signature:

  • untyped



44
45
46
# File 'lib/pdf/reader/object_hash.rb', line 44

def default
  @default
end

#pdf_versionObject (readonly)

Signature:

  • Float



50
51
52
# File 'lib/pdf/reader/object_hash.rb', line 50

def pdf_version
  @pdf_version
end

#sec_handlerObject (readonly)

Signature:

  • securityHandler



53
54
55
# File 'lib/pdf/reader/object_hash.rb', line 53

def sec_handler
  @sec_handler
end

#trailerObject (readonly)

Signature:

  • Hash[Symbol, untyped]



47
48
49
# File 'lib/pdf/reader/object_hash.rb', line 47

def trailer
  @trailer
end

Instance Method Details

#[](key) ⇒ Object

Access an object from the PDF. key can be an int or a PDF::Reader::Reference object.

If an int is used, the object with that ID and a generation number of 0 will be returned.

If a PDF::Reader::Reference object is used the exact ID and generation number can be specified.

Signature:

  • ((Integer | PDF::Reader::Reference)) -> untyped



103
104
105
106
107
108
109
110
111
112
113
# File 'lib/pdf/reader/object_hash.rb', line 103

def [](key)
  return default if key.to_i <= 0

  unless key.is_a?(PDF::Reader::Reference)
    key = PDF::Reader::Reference.new(key.to_i, 0)
  end

  @cache[key] ||= fetch_object(key) || fetch_object_stream(key)
rescue InvalidObjectError
  return default
end

#deref!(key) ⇒ Object

Recursively dereferences the object refered to be key. If key is not a PDF::Reader::Reference, the key is returned unchanged.

Signature:

  • (untyped) -> untyped



355
356
357
# File 'lib/pdf/reader/object_hash.rb', line 355

def deref!(key)
  deref_internal!(key, {})
end

#deref_array(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return an Array or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting an Array and no other type will do.

Signature:

  • (untyped) -> Array[untyped]?



132
133
134
135
136
137
138
139
140
# File 'lib/pdf/reader/object_hash.rb', line 132

def deref_array(key)
  obj = deref(key)

  return obj if obj.nil?

  obj.tap { |obj|
    raise MalformedPDFError, "expected object to be an Array or nil" if !obj.is_a?(Array)
  }
end

#deref_array!(key) ⇒ Object

Signature:

  • (untyped) -> Array[untyped]?



360
361
362
363
364
365
366
# File 'lib/pdf/reader/object_hash.rb', line 360

def deref_array!(key)
  deref!(key).tap { |obj|
    if !obj.nil? && !obj.is_a?(Array)
      raise MalformedPDFError, "expected object (#{obj.inspect}) to be an Array or nil"
    end
  }
end

#deref_array_of_numbers(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return an Array of Numerics or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting an Array and no other type will do.

Some effort to cast array elements to a number is made for any non-numeric elements.

Signature:

  • (untyped) -> Array[Numeric]?

Raises:



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/pdf/reader/object_hash.rb', line 152

def deref_array_of_numbers(key)
  arr = deref(key)

  return arr if arr.nil?

  raise MalformedPDFError, "expected object to be an Array" unless arr.is_a?(Array)

  arr.map { |item|
    if item.is_a?(Numeric)
      item
    elsif item.respond_to?(:to_f)
      item.to_f
    elsif item.respond_to?(:to_i)
      item.to_i
    else
      raise MalformedPDFError, "expected object to be a number"
    end
  }
end

#deref_hash(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return a Hash or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting a Hash and no other type will do.

Signature:

  • (untyped) -> Hash[Symbol, untyped]?



180
181
182
183
184
185
186
187
188
# File 'lib/pdf/reader/object_hash.rb', line 180

def deref_hash(key)
  obj = deref(key)

  return obj if obj.nil?

  obj.tap { |obj|
    raise MalformedPDFError, "expected object to be a Hash or nil" if !obj.is_a?(Hash)
  }
end

#deref_hash!(key) ⇒ Object

Signature:

  • (untyped) -> Hash[Symbol, untyped]?



369
370
371
372
373
374
375
# File 'lib/pdf/reader/object_hash.rb', line 369

def deref_hash!(key)
  deref!(key).tap { |obj|
    if !obj.nil? && !obj.is_a?(Hash)
      raise MalformedPDFError, "expected object (#{obj.inspect}) to be a Hash or nil"
    end
  }
end

#deref_integer(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return an Integer or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting an Integer and no other type will do.

Some effort to cast to an int is made when the reference points to a non-integer.

Signature:

  • (untyped) -> Integer?



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/pdf/reader/object_hash.rb', line 225

def deref_integer(key)
  obj = deref(key)

  return obj if obj.nil?

  if !obj.is_a?(Integer)
    if obj.respond_to?(:to_i)
      obj = obj.to_i
    else
      raise MalformedPDFError, "expected object to be an Integer"
    end
  end

  obj
end

#deref_name(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return a PDF name (Symbol) or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting a PDF Name and no other type will do.

Some effort to cast to a symbol is made when the reference points to a non-symbol.

Signature:

  • (untyped) -> Symbol?



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/pdf/reader/object_hash.rb', line 200

def deref_name(key)
  obj = deref(key)

  return obj if obj.nil?

  if !obj.is_a?(Symbol)
    if obj.respond_to?(:to_sym)
      obj = obj.to_sym
    else
      raise MalformedPDFError, "expected object to be a Name"
    end
  end

  obj
end

#deref_name_or_array(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return a PDF Name (symbol), Array or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting a Name or Array and no other type will do.

Signature:

  • (untyped) -> (Symbol | Array[untyped] | nil)



320
321
322
323
324
325
326
327
328
329
330
# File 'lib/pdf/reader/object_hash.rb', line 320

def deref_name_or_array(key)
  obj = deref(key)

  return obj if obj.nil?

  obj.tap { |obj|
    if !obj.is_a?(Symbol) && !obj.is_a?(Array)
      raise MalformedPDFError, "expected object to be an Array or Name"
    end
  }
end

#deref_number(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return a Numeric or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting a Number and no other type will do.

Some effort to cast to a number is made when the reference points to a non-number.

Signature:

  • (untyped) -> Numeric?



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/pdf/reader/object_hash.rb', line 250

def deref_number(key)
  obj = deref(key)

  return obj if obj.nil?

  if !obj.is_a?(Numeric)
    if obj.respond_to?(:to_f)
      obj = obj.to_f
    elsif obj.respond_to?(:to_i)
      obj.to_i
    else
      raise MalformedPDFError, "expected object to be a number"
    end
  end

  obj
end

#deref_stream(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return a PDF::Reader::Stream or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting a stream and no other type will do.

Signature:

  • (untyped) -> PDF::Reader::Stream?



276
277
278
279
280
281
282
283
284
285
286
# File 'lib/pdf/reader/object_hash.rb', line 276

def deref_stream(key)
  obj = deref(key)

  return obj if obj.nil?

  obj.tap { |obj|
    if !obj.is_a?(PDF::Reader::Stream)
      raise MalformedPDFError, "expected object to be a Stream or nil"
    end
  }
end

#deref_stream_or_array(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return a PDF::Reader::Stream, Array or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting a stream or Array and no other type will do.

Signature:

  • (untyped) -> (PDF::Reader::Stream | Array[untyped] | nil)



339
340
341
342
343
344
345
346
347
348
349
# File 'lib/pdf/reader/object_hash.rb', line 339

def deref_stream_or_array(key)
  obj = deref(key)

  return obj if obj.nil?

  obj.tap { |obj|
    if !obj.is_a?(PDF::Reader::Stream) && !obj.is_a?(Array)
      raise MalformedPDFError, "expected object to be an Array or Stream"
    end
  }
end

#deref_string(key) ⇒ Object

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Guaranteed to only return a String or nil. If the dereference results in any other type then a MalformedPDFError exception will raise. Useful when expecting a string and no other type will do.

Some effort to cast to a string is made when the reference points to a non-string.

Signature:

  • (untyped) -> String?



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/pdf/reader/object_hash.rb', line 297

def deref_string(key)
  obj = deref(key)

  return obj if obj.nil?

  if !obj.is_a?(String)
    if obj.respond_to?(:to_s)
      obj = obj.to_s
    else
      raise MalformedPDFError, "expected object to be a string"
    end
  end

  obj
end

#each(&block) ⇒ Object Also known as: each_pair

iterate over each key, value. Just like a ruby hash.

@override(allow_incompatible: true)

Signature:

  • () { (PDF::Reader::Reference, untyped) -> untyped } -> untyped



405
406
407
408
409
# File 'lib/pdf/reader/object_hash.rb', line 405

def each(&block)
  @xref.each do |ref|
    yield ref, self[ref]
  end
end

#each_key(&block) ⇒ Object

iterate over each key. Just like a ruby hash.

Signature:

  • { (PDF::Reader::Reference) -> untyped } -> untyped



415
416
417
418
419
# File 'lib/pdf/reader/object_hash.rb', line 415

def each_key(&block)
  each do |id, obj|
    yield id
  end
end

#each_value(&block) ⇒ Object

iterate over each value. Just like a ruby hash.

Signature:

  • { (untyped) -> untyped } -> untyped



424
425
426
427
428
# File 'lib/pdf/reader/object_hash.rb', line 424

def each_value(&block)
  each do |id, obj|
    yield obj
  end
end

#empty?Boolean

return true if there are no objects in this file

Signature:

  • () -> bool

Returns:

  • (Boolean)


441
442
443
# File 'lib/pdf/reader/object_hash.rb', line 441

def empty?
  size == 0 ? true : false
end

#encrypted?Boolean

Signature:

  • () -> bool

Returns:

  • (Boolean)


533
534
535
# File 'lib/pdf/reader/object_hash.rb', line 533

def encrypted?
  trailer.has_key?(:Encrypt)
end

#fetch(key, local_default = nil) ⇒ Object

Access an object from the PDF. key can be an int or a PDF::Reader::Reference object.

If an int is used, the object with that ID and a generation number of 0 will be returned.

If a PDF::Reader::Reference object is used the exact ID and generation number can be specified.

local_default is the object that will be returned if the requested key doesn't exist.

Signature:

  • (untyped, ?untyped) -> untyped



390
391
392
393
394
395
396
397
398
399
# File 'lib/pdf/reader/object_hash.rb', line 390

def fetch(key, local_default = nil)
  obj = self[key]
  if obj
    return obj
  elsif local_default
    return local_default
  else
    raise IndexError, "#{key} is invalid" if key.to_i <= 0
  end
end

#has_key?(check_key) ⇒ Boolean Also known as: include?, key?, member?, value?

return true if the specified key exists in the file. key can be an int or a PDF::Reader::Reference

Signature:

  • (untyped) -> bool

Returns:

  • (Boolean)


449
450
451
452
453
454
455
456
457
458
459
# File 'lib/pdf/reader/object_hash.rb', line 449

def has_key?(check_key)
  # TODO update from O(n) to O(1)
  each_key do |key|
    if check_key.kind_of?(PDF::Reader::Reference)
      return true if check_key == key
    else
      return true if check_key.to_i == key.id
    end
  end
  return false
end

#has_value?(value) ⇒ Boolean

return true if the specifiedvalue exists in the file

Signature:

  • (untyped) -> bool

Returns:

  • (Boolean)


467
468
469
470
471
472
473
# File 'lib/pdf/reader/object_hash.rb', line 467

def has_value?(value)
  # TODO update from O(n) to O(1)
  each_value do |obj|
    return true if obj == value
  end
  return false
end

#keysObject

return an array of all keys in the file

Signature:

  • () -> Array[PDF::Reader::Reference]



484
485
486
487
488
# File 'lib/pdf/reader/object_hash.rb', line 484

def keys
  ret = []
  each_key { |k| ret << k }
  ret
end

#obj_type(ref) ⇒ Object

returns the type of object a ref points to

Signature:

  • ((Integer | PDF::Reader::Reference)) -> Symbol?



81
82
83
84
85
# File 'lib/pdf/reader/object_hash.rb', line 81

def obj_type(ref)
  self[ref].class.to_s.to_sym
rescue
  nil
end

#object(key) ⇒ Object Also known as: deref

If key is a PDF::Reader::Reference object, lookup the corresponding object in the PDF and return it. Otherwise return key untouched.

Signature:

  • (untyped) -> untyped



119
120
121
# File 'lib/pdf/reader/object_hash.rb', line 119

def object(key)
  key.is_a?(PDF::Reader::Reference) ? self[key] : key
end

#page_referencesObject

returns an array of PDF::Reader::References. Each reference in the array points a Page object, one for each page in the PDF. The first reference is page 1, second reference is page 2, etc.

Useful for apps that want to extract data from specific pages.

Signature:

  • () -> Array[PDF::Reader::Reference | Hash[Symbol, untyped]]



524
525
526
527
528
529
530
# File 'lib/pdf/reader/object_hash.rb', line 524

def page_references
  root  = fetch(trailer[:Root])
  @page_references ||= begin
                         pages_root = deref_hash(root[:Pages]) || {}
                         get_page_objects(pages_root)
                       end
end

#sec_handler?Boolean

Signature:

  • () -> bool

Returns:

  • (Boolean)


538
539
540
# File 'lib/pdf/reader/object_hash.rb', line 538

def sec_handler?
  !!sec_handler
end

#sizeObject Also known as: length

return the number of objects in the file. An object with multiple generations is counted once.

Signature:

  • () -> Integer



433
434
435
# File 'lib/pdf/reader/object_hash.rb', line 433

def size
  xref.size
end

#stream?(ref) ⇒ Boolean

returns true if the supplied references points to an object with a stream

Signature:

  • ((Integer | PDF::Reader::Reference)) -> bool

Returns:

  • (Boolean)


89
90
91
# File 'lib/pdf/reader/object_hash.rb', line 89

def stream?(ref)
  self.has_key?(ref) && self[ref].is_a?(PDF::Reader::Stream)
end

#to_aObject

return an array of arrays. Each sub array contains a key/value pair.

Signature:

  • () -> untyped



509
510
511
512
513
514
515
# File 'lib/pdf/reader/object_hash.rb', line 509

def to_a
  ret = []
  each do |id, obj|
    ret << [id, obj]
  end
  ret
end

#to_sObject

Signature:

  • () -> String



477
478
479
# File 'lib/pdf/reader/object_hash.rb', line 477

def to_s
  "<PDF::Reader::ObjectHash size: #{self.size}>"
end

#valuesObject

return an array of all values in the file

Signature:

  • () -> untyped



493
494
495
496
497
# File 'lib/pdf/reader/object_hash.rb', line 493

def values
  ret = []
  each_value { |v| ret << v }
  ret
end

#values_at(*ids) ⇒ Object

return an array of all values from the specified keys

Signature:

  • (*untyped) -> untyped



502
503
504
# File 'lib/pdf/reader/object_hash.rb', line 502

def values_at(*ids)
  ids.map { |id| self[id] }
end