class HexaPDF::Type::PageTreeNode

Represents a node in the page tree of the PDF's document.

The page tree is a tree structure containing page tree nodes for the root and intermediate nodes and page objects for the leaf nodes (see Page). The root node of the page tree is linked via the /Pages entry in the Catalog.

All operations except add_page on the page tree are rather expensive because page tree nodes and page objects can be mixed. This means that for finding a page at a specific index we have to go through all objects that come before it.

Page indices are zero-based, not one-based. Therefore the first page has an index of 0!

Since the page tree needs a certain structure it is not advised to directly modify page tree nodes. The validation feature can correct most problems but until the page tree is in order the methods may not work correctly!

Newly created pages use the 'page.default_media_box' configuration option for the /MediaBox value. If an inherited /Resources dictionary does not exist, an empty one is created for the page.

See: PDF1.7 s7.7.3.2, Page

Public Instance Methods

add_page(page = nil) click to toggle source

Adds the page or a new empty page at the end and returns it.

See: insert_page

# File lib/hexapdf/type/page_tree_node.rb, line 157
def add_page(page = nil)
  insert_page(-1, page)
end
delete_page(page) click to toggle source
delete_page(index)

Deletes the given page or the page at the position specified by the zero-based index from the page tree and the document.

Negative indices count backwards from the end, i.e. -1 is the last page.

# File lib/hexapdf/type/page_tree_node.rb, line 169
def delete_page(page)
  page = self.page(page) if page.kind_of?(Integer)
  return unless page && !page.null? && page[:Parent]

  parent = page[:Parent]
  index = parent[:Kids].index(page)

  if index
    ancestors = page.ancestor_nodes
    return nil unless ancestors.include?(self)

    page[:Parent][:Kids].delete_at(index)
    page.delete(:Parent)
    document.delete(page)
    ancestors.each {|node| node[:Count] -= 1 }
  else
    raise HexaPDF::Error, "Given page not found in page tree"
  end
end
each_page {|page| block } → pages click to toggle source
each_page → Enumerator

Iterates over all pages that are beneath this page tree node, from the first to the last page.

# File lib/hexapdf/type/page_tree_node.rb, line 220
def each_page(&block)
  return to_enum(__method__) unless block_given?

  self[:Kids].each do |kid|
    if kid.type == :Page
      yield(kid)
    else
      kid.each_page(&block)
    end
  end

  self
end
insert_page(index, page = nil) click to toggle source

Inserts the page or a new empty page at the zero-based index and returns it.

Negative indices count backwards from the end, i.e. -1 is the last page. When using negative indices, the page will be inserted after that element. So using an index of -1 will insert the page after the last page.

Must be called on the root of the page tree, otherwise the /Count entries are not correctly updated!

If an existing page is inserted, it may be necessary to use Page#copy_inherited_values before insertion so that the page dictionary contains all necessary information.

# File lib/hexapdf/type/page_tree_node.rb, line 124
def insert_page(index, page = nil)
  page ||= new_page
  index = self[:Count] + index + 1 if index < 0

  if index >= self[:Count]
    self[:Kids] << page
    page[:Parent] = self
    page[:Resources] ||= {}
  else
    self[:Kids].each_with_index do |kid, kid_index|
      if index == 0
        self[:Kids].insert(kid_index, page)
        page[:Parent] = self
        break
      elsif kid.type == :Page
        index -= 1
      elsif index <= kid[:Count]
        kid.insert_page(index, page)
        break
      else
        index -= kid[:Count]
      end
    end
  end

  self[:Count] += 1

  page
end
move_page(page, to_index) click to toggle source
move_page(index, to_index)

Moves the given page or the page at the position specified by the zero-based index to the to_index position.

If the page that should be moved, doesn't exist or is invalid, an error is raised.

Negative indices count backwards from the end, i.e. -1 is the last page. When using a negative index, the page will be moved after that element. So using an index of -1 will move the page after the last page.

# File lib/hexapdf/type/page_tree_node.rb, line 201
def move_page(page, to_index)
  page = self.page(page) if page.kind_of?(Integer)
  if page.nil? || page.null? || !page[:Parent] ||
      !(ancestors = page.ancestor_nodes).include?(self)
    raise HexaPDF::Error, "The page to be moved doesn't exist in this page tree"
  end

  parent = page[:Parent]
  insert_page(to_index, page)
  ancestors.each {|node| node[:Count] -= 1 }
  parent[:Kids].delete(page)
end
must_be_indirect?() click to toggle source

Returns true since page tree objects must always be indirect.

# File lib/hexapdf/type/page_tree_node.rb, line 79
def must_be_indirect?
  true
end
page(index) click to toggle source

Returns the page for the zero-based index or nil if no such page exists.

Negative indices count backwards from the end, i.e. -1 is the last page.

# File lib/hexapdf/type/page_tree_node.rb, line 94
def page(index)
  index = self[:Count] + index if index < 0
  return nil if index < 0 || index >= self[:Count]

  self[:Kids].each do |kid|
    if kid.type == :Page
      if index == 0
        return kid
      else
        index -= 1
      end
    elsif index < kid[:Count]
      return kid.page(index)
    else
      index -= kid[:Count]
    end
  end
end
page_count() click to toggle source

Returns the number of pages under this page tree.

Note: If this methods is not called on the root object of the page tree, the returned number is not the total number of pages in the document!

# File lib/hexapdf/type/page_tree_node.rb, line 87
def page_count
  self[:Count]
end

Private Instance Methods

new_page() click to toggle source

Returns a new page object, correctly initialized using the document's configuration options.

# File lib/hexapdf/type/page_tree_node.rb, line 237
def new_page
  box = config['page.default_media_box']
  if box.kind_of?(Symbol)
    box = Page.media_box(box, orientation: config['page.default_media_orientation'])
  end
  document.add({Type: :Page, MediaBox: box})
end
perform_validation() { |"Invalid object in page tree node", true| ... } click to toggle source

Ensures that the /Count and /Parent fields of the whole page tree are set up correctly and that there is at least one page node. This is therefore only done for the root node of the page tree!

Calls superclass method HexaPDF::Dictionary#perform_validation
# File lib/hexapdf/type/page_tree_node.rb, line 248
def perform_validation(&block)
  super
  return if key?(:Parent)

  validate_node = lambda do |node|
    count = 0
    node[:Kids].reject! do |kid|
      if !kid.kind_of?(HexaPDF::Object) || kid.null? ||
          (kid.type != :Page && kid.type != :Pages)
        yield("Invalid object in page tree node", true)
        next true
      elsif kid.type == :Page
        count += 1
      else
        count += validate_node.call(kid)
      end
      if kid[:Parent] != node
        yield("Field Parent of page tree node (#{kid.oid},#{kid.gen}) is invalid", true)
        kid[:Parent] = node
      end
      false
    end
    if node[:Count] != count
      yield("Field Count of page tree node (#{node.oid},#{node.gen}) is invalid", true)
      node[:Count] = count
    end
    count
  end

  validate_node.call(self)

  if self[:Count] == 0
    yield("A PDF document needs at least one page", true)
    add_page.validate(&block)
  end
end