/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.icepdf.core.pobjects; import org.icepdf.core.pobjects.actions.Action; import org.icepdf.core.util.Library; import org.icepdf.core.util.Utils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * <p>The <code>OutlineItem</code> represents the individual outline item within * the hierarchy defined by an <code>Outlines</code> class. The outlines items * are chained together through their <b>Prev</b> and <b>Next</b> entries. Each * outline item has a title and a destination which can be accessed by the * visual representation to create a function document outlines (sometimes * called bookmarks).</p> * <br> * <p>This class is used mainly by the Outlines class to build the outline * hierarchy. The root node of the outline hierarchy can be accessed through * the Outlines class. </p> * * {@link org.icepdf.core.pobjects.Outlines} * @since 2.0 */ public class OutlineItem extends Dictionary { public static final Name A_KEY = new Name("A"); public static final Name COUNT_KEY = new Name("Count"); public static final Name TITLE_KEY = new Name("Title"); public static final Name DEST_KEY = new Name("Dest"); public static final Name FIRST_KEY = new Name("First"); public static final Name LAST_KEY = new Name("Last"); public static final Name NEXT_KEY = new Name("Next"); public static final Name PREV_KEY = new Name("Prev"); public static final Name PARENT_KEY = new Name("Parent"); // The text to be displayed on the screen for this item. private String title; // The destination to be displayed when this item is activated private Destination dest; // The action to be performed when this item is activated private Action action; // The parent of this item in the outline hierarchy. The parent of a // top-level item is the outline dictionary itself. private Reference parent; // The previous item at this outline level. private Reference prev; // The next item at this outline level. private Reference next; // An outline item dictionary representing the first top-level item // in the outline. private Reference first; // An outline item dictionary representing the last top-level item // in the outline. private Reference last; // The total number of open items at all levels of the outline. This // entry should be omitted if there are no open outline items. private int count = -1; private boolean loadedSubItems; private List<OutlineItem> subItems; /** * Creates a new instance of an OutlineItem. * * @param l document library. * @param h OutlineItem dictionary entries. */ public OutlineItem(Library l, HashMap h) { super(l, h); loadedSubItems = false; subItems = new ArrayList<OutlineItem>(Math.max(Math.abs(getCount()), 16)); } /** * Indicates if the Outline item is empty. An outline item is empty if * it has no title, destination and action dictionary entries. * * @return true, if the outline entry is empty; false, otherwise. */ public boolean isEmpty() { return getTitle() == null && getDest() == null && getAction() == null; } /** * Gets the number of descendants that would appear under this outline item. * * @return descendant count. */ public int getSubItemCount() { ensureSubItemsLoaded(); if (subItems != null) return subItems.size(); else return 0; } /** * Gets the child outline item specified by the index. All children of the * outline items are ordered and numbered. * * @param index child index number of desired outline item. * @return outline specified by index. */ public OutlineItem getSubItem(int index) { ensureSubItemsLoaded(); return subItems.get(index); } /** * Gets the action associated with this OutlineItem. * * @return the associated action; null, if there is no action. */ public Action getAction() { // grab the action attribute if (action == null) { Object obj = library.getObject(entries, A_KEY); if (obj instanceof HashMap) { action = new org.icepdf.core.pobjects.actions.Action(library, (HashMap) obj); } } return action; } /** * Gets a reference to an outline item dictionary representing the first * top-level item in the outline. * * @return reference to first top-level item */ public Reference getFirst() { if (first == null) { Object attribute = entries.get(FIRST_KEY); if (attribute instanceof Reference) { first = (Reference) attribute; } } return first; } /** * Gets a reference to an outline item dictionary representing the last * top-level item in the outline. * * @return reference to last top-level item */ public Reference getLast() { if (last == null) { Object attribute = entries.get(LAST_KEY); if (attribute instanceof Reference) { last = (Reference) attribute; } } return last; } /** * Gets the next outline item at this outline level. * * @return next item at this outline level. */ public Reference getNext() { if (next == null) { Object attribute = entries.get(NEXT_KEY); if (attribute instanceof Reference) { next = (Reference) attribute; } } return next; } /** * Gets the previous outline item at this outline level. * * @return previous item at this outline level. */ public Reference getPrev() { if (prev == null) { Object attribute = entries.get(PREV_KEY); if (attribute instanceof Reference) { prev = (Reference) attribute; } } return prev; } /** * Gets the parent of this outline item in the outline hierarchy. The * parent of a top-level item is the outline dictionary itself. * * @return parent of this item. */ public Reference getParent() { if (parent == null) { Object attribute = entries.get(PARENT_KEY); if (attribute instanceof Reference) { parent = (Reference) attribute; } } return parent; } /** * Gets the number of descendants that would appear under this outline item. * * @return descendant count. */ private int getCount() { if (count < 0) { // grab the count attribute count = library.getInt(entries, COUNT_KEY); } return count; } /** * Gets the text to be displayed on the screen for this item. * * @return text to be displayed */ public String getTitle() { if (title == null) { // get title String for outline entry Object obj = library.getObject(entries, TITLE_KEY); if (obj instanceof StringObject) { title = Utils.convertStringObject(library, (StringObject) obj); } } return title; } /** * Gets the destination to be displayed when this item is activated. * * @return destination to be displayed. */ public Destination getDest() { if (dest == null) { // grab the Destination attribute Object obj = library.getObject(entries, DEST_KEY); if (obj != null) { dest = new Destination(library, obj); } } return dest; } /** * Utility method for loading all children of this outline. The main purpose * of this is to make sure the count is accurate. */ private void ensureSubItemsLoaded() { if (loadedSubItems) return; loadedSubItems = true; if (getFirst() != null) { // get first child Reference nextReference = getFirst(); Reference oldNextReference; OutlineItem outLineItem; HashMap dictionary; Object tmp; // iterate through children and see if then have children. while (nextReference != null) { // result the outline dictionary tmp = library.getObject(nextReference); if (tmp == null || !(tmp instanceof HashMap)) { break; } else { dictionary = (HashMap) tmp; } // create the new outline outLineItem = new OutlineItem(library, dictionary); // add the new item to the list of children subItems.add(outLineItem); // old reference is kept to make sure we don't iterate out // of control on a circular reference. oldNextReference = nextReference; nextReference = outLineItem.getNext(); // make sure we haven't already added this node, some // Outlines terminate with next being a pointer to itself. if (oldNextReference.equals(nextReference)) { break; } } } } }