Class: HexaPDF::Type::OutlineItem

Inherits:
Dictionary show all
Extended by:
Utils::BitField
Defined in:
lib/hexapdf/type/outline_item.rb

Overview

Represents an outline item dictionary.

An item has a title and some optional attributes: the action that is activated when clicking (either a simple destination or an explicit action object), the text color, and flags (whether the text should appear bold and/or italic).

Additionally, items may have child items which makes it possible to create a hierarchy of items.

If no destination/action is set, the item just acts as kind of a header. It usually only makes sense to do this when the item has children.

Outline item dictionaries are connected together in the form of a linked list using the /Next and /Prev keys. Each item may have descendant items. If so, the /First and /Last keys point to respectively the first and last descendant items.

Since many dictionary keys need to be kept up-to-date when manipulating the outline item tree, it is not recommended to manually do this but to rely on the provided convenience methods.

See: PDF2.0 s12.3.3

Constant Summary

Constants included from DictionaryFields

DictionaryFields::Boolean, DictionaryFields::PDFByteString, DictionaryFields::PDFDate

Instance Attribute Summary

Attributes inherited from Object

#data, #document, #must_be_indirect

Instance Method Summary collapse

Methods included from Utils::BitField

bit_field

Methods inherited from Dictionary

#[], #[]=, define_field, define_type, #delete, #each, each_field, #empty?, field, #key?, #to_hash, type, #type

Methods inherited from Object

#<=>, #==, #cache, #cached?, #clear_cache, deep_copy, #deep_copy, #document?, #eql?, field, #gen, #gen=, #hash, #indirect?, #initialize, #inspect, make_direct, #null?, #oid, #oid=, #type, #validate, #value, #value=

Constructor Details

This class inherits a constructor from HexaPDF::Object

Instance Method Details

#action(value = nil) ⇒ Object

:call-seq:

item.action             -> action
item.action(value)      -> action

Returns the item’s action if no argument is given. Otherwise sets the action to the given value (needs to be a valid HexaPDF::Type::Action dictionary).

If an action is set, the destination has to be unset; and vice versa. So when setting an action value, the destination is automatically deleted.



197
198
199
200
201
202
203
204
# File 'lib/hexapdf/type/outline_item.rb', line 197

def action(value = nil)
  if value
    delete(:Dest)
    self[:A] = value
  else
    self[:A]
  end
end

#add_item(title, destination: nil, action: nil, position: :last, open: true, text_color: nil, flags: nil) {|item| ... } ⇒ Object

Adds, as child to this item, a new outline item with the given title that performs the provided action on clicking. Returns the newly added item.

Alternatively, it is possible to provide an already initialized outline item instead of the title. If so, the only other argument that is used is position. Existing fields /Prev, /Next, /First, /Last, /Parent and /Count are deleted from the given item and set appropriately.

If neither :destination nor :action is specified, the outline item has no associated action. This is only meaningful if the new item will have children as it then acts just as a container.

If a block is specified, the newly created item is yielded.

destination

Specifies the destination that should be activated when clicking on the outline item. See HexaPDF::Document::Destinations#use_or_create for details. The argument :action takes precedence if it is also specified,

action

Specifies the action that should be taken when clicking on the outline item. See HexaPDF::Type::Action for details. If the argument :destination is also specified, the :action argument takes precedence.

position

The position where the new child item should be inserted. Can either be:

:first

Insert as first item

:last

Insert as last item (default)

Integer

When non-negative inserts before, otherwise after, the item at the given zero-based index.

open

Specifies whether the outline item should be open (i.e. one or more children are shown) or closed. Default: true.

text_color

The text color of the outline item text which needs to be a valid RGB color (see #text_color for details). If not set, the text appears in black.

flags

An array of font variants (possible values are :bold and :italic) to set for the outline item text, see #flags for detail. Default is to use no variant.

Examples:

doc.destinations.add("Title") do |item|                  # no action, just container
  item.add("Second subitem", destination: doc.pages[1])  # links to page 2
  item.add("First subitem", position: :first, destination: doc.pages[0])
end

Yields:

  • (item)


303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/hexapdf/type/outline_item.rb', line 303

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

#destination(value = nil) ⇒ Object

:call-seq:

item.destination             -> destination
item.destination(value)      -> destination

Returns the item’s destination if no argument is given. Otherwise sets the destination to the given value (see HexaPDF::Document::Destinations#use_or_create for the posssible values).

If an action is set, the destination has to be unset; and vice versa. So when setting a destination value, the action is automatically deleted.



179
180
181
182
183
184
185
186
# File 'lib/hexapdf/type/outline_item.rb', line 179

def destination(value = nil)
  if value
    delete(:A)
    self[:Dest] = document.destinations.use_or_create(value)
  else
    self[:Dest]
  end
end

#destination_pageObject

Returns the destination page if there is any.

  • If a destination is set, the associated page is returned.

  • If an action is set and it is a GoTo action, the associated page is returned.

  • Otherwise nil is returned.



241
242
243
244
245
# File 'lib/hexapdf/type/outline_item.rb', line 241

def destination_page
  dest = self[:Dest]
  dest = action[:D] if !dest && (action = self[:A]) && action[:S] == :GoTo
  document.destinations.resolve(dest)&.page
end

#each_item(&block) ⇒ Object

:call-seq:

item.each_item {|descendant_item, level| block }   -> item
item.each_item                                     -> Enumerator

Iterates over all descendant items of this one.

The descendant items are yielded in-order, yielding first the item itself and then its descendants.



386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/hexapdf/type/outline_item.rb', line 386

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

#flagsObject

:method: unflag :call-seq:

flag(*flags)

Clears the given flags from /F, given as flag names or bit indices.

See #flags for the list of available flags.



125
126
127
# File 'lib/hexapdf/type/outline_item.rb', line 125

bit_field(:flags, {italic: 0, bold: 1},
lister: "flags", getter: "flagged?", setter: "flag", unsetter: "unflag",
value_getter: "self[:F]", value_setter: "self[:F]")

#levelObject

Returns the outline level this item is one.

The level of the items in the main outline dictionary, the root level, is 1.

Here is an illustrated example of items contained in a document outline with their associated level:

Outline dictionary          0
  Outline item 1            1
  |- Sub item 1             2
  |- Sub item 2             2
     |- Sub sub item 1      3
  |- Sub item 3             2
  Outline item 2            1


220
221
222
223
224
225
# File 'lib/hexapdf/type/outline_item.rb', line 220

def level
  count = 0
  temp = self
  count += 1 while (temp = temp[:Parent])
  count
end

#must_be_indirect?Boolean

Returns true since outline items must always be indirect objects.

Returns:



130
131
132
# File 'lib/hexapdf/type/outline_item.rb', line 130

def must_be_indirect?
  true
end

#open?Boolean

Returns the open state of the item.

true

If this item is open, i.e. showing its child items.

false

If this item is closed, i.e. not showing its child items.

nil

If this item doesn’t (yet) have any child items.

Returns:



232
233
234
# File 'lib/hexapdf/type/outline_item.rb', line 232

def open?
  self[:First] && key?(:Count) && self[:Count] >= 0
end

#text_color(color = nil) ⇒ Object

:call-seq:

item.text_color               -> color
item.text_color(color)        -> color

Returns the item’s text color as HexaPDF::Content::ColorSpace::DeviceRGB::Color object if no argument is given. Otherwise sets the text color, see HexaPDF::Content::ColorSpace.device_color_from_specification for possible color values.

Note: The color has to be an RGB color.



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/hexapdf/type/outline_item.rb', line 157

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

#title(value = nil) ⇒ Object

:call-seq:

item.title          -> title
item.title(value)   -> title

Returns the item’s title if no argument is given. Otherwise sets the title to the given value.



140
141
142
143
144
145
146
# File 'lib/hexapdf/type/outline_item.rb', line 140

def title(value = nil)
  if value
    self[:Title] = value
  else
    self[:Title]
  end
end