module Prawn::Templates::ObjectStoreExtensions

Public Instance Methods

import_page(input, page_num) click to toggle source

imports all objects required to render a page from another PDF. The objects are added to the current object store, but NOT linked anywhere.

The object ID of the root Page object is returned, it’s up to the calling code to link that into the document structure somewhere. If this isn’t done the imported objects will just be removed when the store is compacted.

Imports nothing and returns nil if the requested page number doesn’t exist. page_num is 1 indexed, so 1 indicates the first page.

# File lib/prawn/templates.rb, line 108
def import_page(input, page_num)
  @loaded_objects = {}
  template_id = indexed_template(input, page_num)
  return template_id if template_id

  io = if input.respond_to?(:seek) && input.respond_to?(:read)
         input
       elsif File.file?(input.to_s)
         StringIO.new(File.binread(input.to_s))
       else
         raise ArgumentError, 'input must be an IO-like object or a ' \
         'filename'
       end

  hash = indexed_hash(input, io)
  ref = hash.page_references[page_num - 1]

  if ref.nil?
    nil
  else
    index_template(
      input, page_num,
      load_object_graph(hash, ref).identifier
    )
  end
rescue PDF::Reader::MalformedPDFError,
       PDF::Reader::InvalidObjectError => e
  msg = 'Error reading template file. If you are sure it\'s a valid PDF,'\
        " it may be a bug.\n#{e.message}"
  raise PDF::Core::Errors::TemplateError, msg
rescue PDF::Reader::UnsupportedFeatureError
  msg = 'Template file contains unsupported PDF features'
  raise PDF::Core::Errors::TemplateError, msg
end

Private Instance Methods

get_page_objects(obj) click to toggle source

returns a nested array of object IDs for all pages in this object store.

# File lib/prawn/templates.rb, line 187
def get_page_objects(obj)
  if obj.data[:Type] == :Page
    obj.identifier
  elsif obj.data[:Type] == :Pages
    obj.data[:Kids].map { |kid| get_page_objects(kid) }
  end
end
hash_index() click to toggle source

An index for the read object hash of a pdf template so that the object hash does not need to be parsed multiple times when using different pages of the pdf as page templates

# File lib/prawn/templates.rb, line 166
def hash_index
  @hash_index ||= {}
end
index_template(input, page_number, id) click to toggle source

indexes the identifier for a page from a template

# File lib/prawn/templates.rb, line 159
def index_template(input, page_number, id)
  (template_index[indexing_key(input)] ||= {})[page_number] ||= id
end
indexed_hash(input, io) click to toggle source

reads and indexes a new IO for a template if the IO has been indexed already then the parsed object hash is returned directly

# File lib/prawn/templates.rb, line 173
def indexed_hash(input, io)
  hash_index[indexing_key(input)] ||= PDF::Reader::ObjectHash.new(io)
end
indexed_template(input, page_number) click to toggle source

returns the indexed object graph identifier for a template page if it exists

# File lib/prawn/templates.rb, line 153
def indexed_template(input, page_number)
  key = indexing_key(input)
  template_index[key] && template_index[key][page_number]
end
indexing_key(input) click to toggle source

the index key for the input. uses object_id so that both a string filename or an IO stream can be indexed and reused provided the same object gets used in multiple page template calls.

# File lib/prawn/templates.rb, line 181
def indexing_key(input)
  input.object_id
end
load_file(template) click to toggle source

takes a source PDF and uses it as a template for this document.

# File lib/prawn/templates.rb, line 197
def load_file(template)
  unless (template.respond_to?(:seek) && template.respond_to?(:read)) ||
      File.file?(template)
    raise ArgumentError, "#{template} does not exist"
  end

  hash = PDF::Reader::ObjectHash.new(template)
  src_info = hash.trailer[:Info]
  src_root = hash.trailer[:Root]
  @min_version = hash.pdf_version.to_f

  if hash.trailer[:Encrypt]
    msg = 'Template file is an encrypted PDF, it can\'t be used as a '\
        'template'
    raise PDF::Core::Errors::TemplateError, msg
  end

  if src_info
    @info = load_object_graph(hash, src_info).identifier
  end

  if src_root
    @root = load_object_graph(hash, src_root).identifier
  end
rescue PDF::Reader::MalformedPDFError,
       PDF::Reader::InvalidObjectError => e
  msg = 'Error reading template file. If you are sure it\'s a valid PDF,'\
        " it may be a bug.\n#{e.message}"
  raise PDF::Core::Errors::TemplateError, msg
rescue PDF::Reader::UnsupportedFeatureError
  msg = 'Template file contains unsupported PDF features'
  raise PDF::Core::Errors::TemplateError, msg
end
load_object_graph(hash, object) click to toggle source

recurse down an object graph from a source PDF, importing all the indirect objects we find.

hash is the PDF::Reader::ObjectHash to extract objects from, object is the object to extract.

# File lib/prawn/templates.rb, line 237
def load_object_graph(hash, object)
  @loaded_objects ||= {}
  case object
  when ::Hash then
    object.each do |key, value|
      object[key] = load_object_graph(hash, value)
    end
    object
  when Array then
    object.map { |item| load_object_graph(hash, item) }
  when PDF::Reader::Reference then
    unless @loaded_objects.key?(object.id)
      @loaded_objects[object.id] = ref(nil)
      new_obj = load_object_graph(hash, hash[object])
      if new_obj.is_a?(PDF::Reader::Stream)
        stream_dict = load_object_graph(hash, new_obj.hash)
        @loaded_objects[object.id].data = stream_dict
        @loaded_objects[object.id] << new_obj.data
      else
        @loaded_objects[object.id].data = new_obj
      end
    end
    @loaded_objects[object.id]
  when PDF::Reader::Stream
    # Stream is a subclass of string, so this is here to prevent the
    # stream being wrapped in a LiteralString
    object
  when String
    utf8?(object) ? object : PDF::Core::ByteString.new(object)
  else
    object
  end
end
template_index() click to toggle source

An index for page templates so that their loaded object graph can be reused without multiple loading

# File lib/prawn/templates.rb, line 147
def template_index
  @template_index ||= {}
end