# -*- encoding: utf-8; frozen_string_literal: true -*-
#
#--
# This file is part of HexaPDF.
#
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
# Copyright (C) 2014-2024 Thomas Leitner
#
# HexaPDF is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License version 3 as
# published by the Free Software Foundation with the addition of the
# following permission added to Section 15 as permitted in Section 7(a):
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
# INFRINGEMENT OF THIRD PARTY RIGHTS.
#
# HexaPDF is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with HexaPDF. If not, see .
#
# The interactive user interfaces in modified source and object code
# versions of HexaPDF must display Appropriate Legal Notices, as required
# under Section 5 of the GNU Affero General Public License version 3.
#
# In accordance with Section 7(b) of the GNU Affero General Public
# License, a covered work must retain the producer line in every PDF that
# is created or manipulated using HexaPDF.
#
# If the GNU Affero General Public License doesn't fit your need,
# commercial licenses are available at .
#++
require 'hexapdf/dictionary'
module HexaPDF
module Type
# Represents the root of the PDF's document outline containing a hierarchy of outline items
# (sometimes called bookmarks) in a linked list.
#
# The document outline usually contains items for the sections of the document, so that clicking
# on an item opens the page where the section starts (the section header is). Most PDF viewers
# are able to display the outline to aid in navigation, though not all apply the optional
# attributes like the text color.
#
# The outline dictionary is linked via the /Outlines entry from the Type::Catalog and can
# directly be accessed via HexaPDF::Document#outline.
#
# == Examples
#
# Here is an example for creating an outline:
#
# doc = HexaPDF::Document.new
# 5.times { doc.pages.add }
# doc.outline.add_item("Section 1", destination: 0) do |sec1|
# sec1.add_item("Page 2", destination: doc.pages[1])
# sec1.add_item("Page 3", destination: 2)
# sec1.add_item("Section 1.1", text_color: "red", flags: [:bold]) do |sec11|
# sec11.add_item("Page 4", destination: 3)
# end
# end
#
# Here is one for copying the complete outline from one PDF to another:
#
# doc = HexaPDF::Document.open(ARGV[0])
# target = HexaPDF::Document.new
# stack = [target.outline]
# doc.outline.each_item do |item, level|
# if stack.size < level
# stack << stack.last[:Last]
# elsif stack.size > level
# (stack.size - level).times { stack.pop }
# end
# stack.last.add_item(target.import(item))
# end
# # Copying all the pages so that the references work.
# doc.pages.each {|page| target.pages << target.import(page) }
#
# See: PDF2.0 s12.3.3
class Outline < Dictionary
define_type :Outlines
define_field :Type, type: Symbol, default: type
define_field :First, type: :XXOutlineItem, indirect: true
define_field :Last, type: :XXOutlineItem, indirect: true
define_field :Count, type: Integer
# Adds a new top-level outline item.
#
# See OutlineItem#add_item for details on the available options since this method just passes
# all arguments through to it.
def add_item(title, **options, &block)
self[:Count] ||= 0
self_as_item.add_item(title, **options, &block)
end
# :call-seq:
# outline.each_item {|item| block } -> item
# outline.each_item -> Enumerator
#
# Iterates over all items of the outline.
#
# The items are yielded in-order, yielding first the item itself and then its descendants.
def each_item(&block)
self_as_item.each_item(&block)
end
private
# Represents the outline dictionary as an outline item dictionary to make use of some of its
# methods.
def self_as_item
@self_as_item ||= document.wrap(self, type: :XXOutlineItem)
end
# Makes sure the required values are set.
def perform_validation
super
first = self[:First]
last = self[:Last]
if (first && !last) || (!first && last)
yield('Outline dictionary is missing an endpoint reference', true)
node, dir = first ? [first, :Next] : [last, :Prev]
node = node[dir] while node[dir]
self[dir == :Next ? :Last : :First] = node
elsif !first && !last && self[:Count] && self[:Count] != 0
yield('Outline dictionary key /Count set but no items exist', true)
delete(:Count)
end
end
end
end
end