class HexaPDF::Type::OutlineItem
See: PDF2.0 s12.3.3
it is not recommended to manually do this but to rely on the provided convenience methods.
Since many dictionary keys need to be kept up-to-date when manipulating the outline item tree,
respectively the first and last descendant items.
and /Prev keys. Each item may have descendant items. If so, the /First and /Last keys point to
Outline item dictionaries are connected together in the form of a linked list using the /Next
sense to do this when the item has children.
If no destination/action is set, the item just acts as kind of a header. It usually only makes
items.
Additionally, items may have child items which makes it possible to create a hierarchy of
the text should appear bold and/or italic).
(either a simple destination or an explicit action object), the text color, and flags (whether
An item has a title and some optional attributes: the action that is activated when clicking
Represents an outline item dictionary.
def action(value = nil)
If an action is set, the destination has to be unset; and vice versa. So when setting an
the given value (needs to be a valid HexaPDF::Type::Action dictionary).
Returns the item's action if no argument is given. Otherwise sets the action to
item.action(value) -> action
item.action -> action
:call-seq:
def action(value = nil) if value delete(:Dest) self[:A] = value else self[:A] end end
def add_item(title, destination: nil, action: nil, position: :last, open: true,
item.add("First subitem", position: :first, destination: doc.pages[0])
item.add("Second subitem", destination: doc.pages[1]) # links to page 2
doc.destinations.add("Title") do |item| # no action, just container
Examples:
item text, see #flags for detail. Default is to use no variant.
An array of font variants (possible values are :bold and :italic) to set for the outline
flags::
#text_color for details). If not set, the text appears in black.
The text color of the outline item text which needs to be a valid RGB color (see
text_color::
or closed. Default: +true+.
Specifies whether the outline item should be open (i.e. one or more children are shown)
open::
zero-based index.
Integer:: When non-negative inserts before, otherwise after, the item at the given
+:last+:: Insert as last item (default)
+:first+:: Insert as first item
The position where the new child item should be inserted. Can either be:
position::
:action argument takes precedence.
HexaPDF::Type::Action for details. If the argument :destination is also specified, the
Specifies the action that should be taken when clicking on the outline item. See
action::
takes precedence if it is also specified,
See HexaPDF::Document::Destinations#use_or_create for details. The argument :action
Specifies the destination that should be activated when clicking on the outline item.
destination::
If a block is specified, the newly created item is yielded.
container.
This is only meaningful if the new item will have children as it then acts just as a
If neither :destination nor :action is specified, the outline item has no associated action.
appropriately.
/Next, /First, /Last, /Parent and /Count are deleted from the given item and set
title. If so, the only other argument that is used is +position+. Existing fields /Prev,
Alternatively, it is possible to provide an already initialized outline item instead of the
provided action on clicking. Returns the newly added item.
Adds, as child to this item, a new outline item with the given title that performs the
def add_item(title, destination: nil, action: nil, position: :last, open: true, text_color: nil, flags: nil) # :yield: item if title.kind_of?(HexaPDF::Object) && title.type == :XXOutlineItem item = title item.delete(:Prev) item.delete(:Next) item.delete(:First) item.delete(:Last) if item[:Count] && item[:Count] >= 0 item[:Count] = 0 else item.delete(:Count) end item[:Parent] = self else item = document.add({Parent: self}, type: :XXOutlineItem) item.title(title) if action item.action(action) else item.destination(destination) end item.text_color(text_color) if text_color item.flag(*flags) if flags item[:Count] = 0 if open # Count=0 means open if items are later added end unless position == :last || position == :first || position.kind_of?(Integer) raise ArgumentError, "position must be :first, :last, or an integer" end if self[:First] case position when :last, -1 item[:Prev] = self[:Last] self[:Last][:Next] = item self[:Last] = item when :first, 0 item[:Next] = self[:First] self[:First][:Prev] = item self[:First] = item when Integer temp, direction = if position > 0 [self[:First], :Next] else position = -position - 2 [self[:Last], :Prev] end position.times { temp &&= temp[direction] } raise ArgumentError, "position out of bounds" if temp.nil? item[:Prev] = temp[:Prev] item[:Next] = temp temp[:Prev] = item item[:Prev][:Next] = item end else self[:First] = self[:Last] = item end # Re-calculate /Count entries temp = self while temp if !temp.key?(:Count) || temp[:Count] < 0 temp[:Count] = (temp[:Count] || 0) - 1 break else temp[:Count] += 1 end temp = temp[:Parent] end yield(item) if block_given? item end
def destination(value = nil)
If an action is set, the destination has to be unset; and vice versa. So when setting a
values).
the given value (see HexaPDF::Document::Destinations#use_or_create for the posssible
Returns the item's destination if no argument is given. Otherwise sets the destination to
item.destination(value) -> destination
item.destination -> destination
:call-seq:
def destination(value = nil) if value delete(:A) self[:Dest] = document.destinations.use_or_create(value) else self[:Dest] end end
def destination_page
* If an action is set and it is a GoTo action, the associated page is returned.
* If a destination is set, the associated page is returned.
Returns the destination page if there is any.
def destination_page dest = self[:Dest] dest = action[:D] if !dest && (action = self[:A]) && action[:S] == :GoTo document.destinations.resolve(dest)&.page end
def each_item(&block)
The descendant items are yielded in-order, yielding first the item itself and then its
Iterates over all descendant items of this one.
item.each_item -> Enumerator
item.each_item {|descendant_item, level| block } -> item
:call-seq:
def each_item(&block) return to_enum(__method__) unless block_given? return self unless (item = self[:First]) level = self.level + 1 while item yield(item, level) item.each_item(&block) item = item[:Next] end self end
def level
|- Sub item 3 2
|- Sub sub item 1 3
|- Sub item 2 2
|- Sub item 1 2
Outline item 1 1
Outline dictionary 0
associated level:
Here is an illustrated example of items contained in a document outline with their
The level of the items in the main outline dictionary, the root level, is 1.
Returns the outline level this item is one.
def level count = 0 temp = self count += 1 while (temp = temp[:Parent]) count end
def must_be_indirect?
def must_be_indirect? true end
def open?
+false+:: If this item is closed, i.e. not showing its child items.
+true+:: If this item is open, i.e. showing its child items.
Returns the open state of the item.
def open? self[:First] && key?(:Count) && self[:Count] >= 0 end
def perform_validation # :nodoc:
def perform_validation # :nodoc: super first = self[:First] last = self[:Last] if (first && !last) || (!first && last) yield('Outline item 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 item dictionary key /Count set but no descendants exist', true) delete(:Count) end prev_item = self[:Prev] if prev_item && (prev_item_next = prev_item[:Next]) != self if prev_item_next yield('Outline item /Prev points to item whose /Next points somewhere else', false) else yield('Outline item /Prev points to item without /Next', true) prev_item[:Next] = self end end next_item = self[:Next] if next_item && (next_item_prev = next_item[:Prev]) != self if next_item_prev yield('Outline item /Next points to item whose /Prev points somewhere else', false) else yield('Outline item /Next points to item without /Prev', true) next_item[:Prev] = self end end end
def text_color(color = nil)
HexaPDF::Content::ColorSpace.device_color_from_specification for possible +color+ values.
argument is given. Otherwise sets the text color, see
Returns the item's text color as HexaPDF::Content::ColorSpace::DeviceRGB::Color object if no
item.text_color(color) -> color
item.text_color -> color
:call-seq:
def text_color(color = nil) if color color = HexaPDF::Content::ColorSpace.device_color_from_specification(color) unless color.color_space.family == :DeviceRGB raise ArgumentError, "The given argument is not a valid RGB color" end self[:C] = color.components else Content::ColorSpace.prenormalized_device_color(self[:C]) end end
def title(value = nil)
Returns the item's title if no argument is given. Otherwise sets the title to the given
item.title(value) -> title
item.title -> title
:call-seq:
def title(value = nil) if value self[:Title] = value else self[:Title] end end