/*
* Copyright 2006-2012 ICEsoft Technologies Inc.
*
* 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.pobjects.fonts.ofont.Encoding;
import org.icepdf.core.util.Library;
import java.util.Hashtable;
import java.util.Vector;
/**
* <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>
* <p/>
* <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>
*
* @see org.icepdf.ri.common.OutlineItemTreeNode
* @see org.icepdf.core.pobjects.Outlines
* @since 2.0
*/
public class OutlineItem extends Dictionary {
// 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 Vector<OutlineItem> subItems;
/**
* Creates a new instance of an OutlineItem.
*
* @param l document library.
* @param h OutlineItem dictionary entries.
*/
public OutlineItem(Library l, Hashtable h) {
super(l, h);
loadedSubItems = false;
subItems = new Vector<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");
if (obj instanceof Hashtable) {
action = new org.icepdf.core.pobjects.actions.Action(library, (Hashtable) 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");
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");
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");
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");
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");
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");
}
return count;
}
/**
* Gets the text to be displayed on the screen for this item.
*
* @return text to be displayed
*/
public String getTitle() {
/*
* Some bizarre code that we have no idea what is for
* Written by those crazy Italians
if (title != null && title.indexOf(13) != -1) {
StringBuffer sb = new StringBuffer();
sb.append("<html>");
StringTokenizer stk = new StringTokenizer(title, "\n\r");
while (stk.hasMoreTokens()) {
String s = stk.nextToken();
sb.append("<p>" + s + "</p>");
}
sb.append("</html>");
title = sb.toString();
}
*/
if (title == null) {
// get title String for outline entry
Object obj = library.getObject(entries, "Title");
if (obj instanceof StringObject) {
StringObject outlineText = (StringObject) obj;
String titleText = outlineText.getDecryptedLiteralString(library.securityManager);
// If the title begins with 254 and 255 we are working with
// Octal encoded strings. Check first to make sure that the
// title string is not null, or is at least of length 2.
if (titleText != null && titleText.length() >= 2 &&
((int) titleText.charAt(0)) == 254 &&
((int) titleText.charAt(1)) == 255) {
StringBuilder sb1 = new StringBuilder();
// convert teh unicode to characters.
for (int i = 2; i < titleText.length(); i += 2) {
try {
int b1 = ((int) titleText.charAt(i)) & 0xFF;
int b2 = ((int) titleText.charAt(i + 1)) & 0xFF;
//System.err.println(b1 + " " + b2);
sb1.append((char) (b1 * 256 + b2));
} catch (Exception ex) {
// intentionally left blank.
}
}
title = sb1.toString();
} else if (titleText != null) {
StringBuilder sb = new StringBuilder();
Encoding enc = Encoding.getPDFDoc();
for (int i = 0; i < titleText.length(); i++) {
sb.append(enc.get(titleText.charAt(i)));
}
title = sb.toString();
}
}
}
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");
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;
Hashtable dictionary;
// iterate through children and see if then have children.
while (nextReference != null) {
// result the outline dictionary
dictionary = (Hashtable) library.getObject(nextReference);
if (dictionary == null) {
break;
}
// 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;
}
}
}
}
}