/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.pdfbox.pdmodel.interactive.documentnavigation.outline; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.pdmodel.common.COSObjectable; /** * This represents an node in an outline in a pdf document. * * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> * @version $Revision: 1.3 $ */ public class PDOutlineNode implements COSObjectable { /** * The dictionary for this node. */ protected COSDictionary node; /** * Default Constructor. */ public PDOutlineNode() { node = new COSDictionary(); } /** * Default Constructor. * * @param dict The dictionary storage. */ public PDOutlineNode( COSDictionary dict) { node = dict; } /** * Convert this standard java object to a COS object. * * @return The cos object that matches this Java object. */ public COSBase getCOSObject() { return node; } /** * Convert this standard java object to a COS object. * * @return The cos object that matches this Java object. */ public COSDictionary getCOSDictionary() { return node; } /** * Get the parent of this object. This will either be a DocumentOutline or an OutlineItem. * * @return The parent of this object, or null if this is the document outline and there * is no parent. */ protected PDOutlineNode getParent() { PDOutlineNode retval = null; COSDictionary parent = (COSDictionary)node.getDictionaryObject( "Parent", "P" ); if( parent != null ) { if( parent.getDictionaryObject( "Parent", "P" ) == null ) { retval = new PDDocumentOutline( parent ); } else { retval = new PDOutlineItem( parent ); } } return retval; } /** * Set the parent of this object, this is maintained by these objects and should not * be called by any clients of PDFBox code. * * @param parent The parent of this object. */ protected void setParent( PDOutlineNode parent ) { node.setItem( "Parent", parent ); } /** * append a child node to this node. * * @param outlineNode The node to add. */ public void appendChild( PDOutlineItem outlineNode ) { outlineNode.setParent( this ); if( getFirstChild() == null ) { int currentOpenCount = getOpenCount(); setFirstChild( outlineNode ); //1 for the the item we are adding; int numberOfOpenNodesWeAreAdding = 1; if( outlineNode.isNodeOpen() ) { numberOfOpenNodesWeAreAdding += outlineNode.getOpenCount(); } if( isNodeOpen() ) { setOpenCount( currentOpenCount + numberOfOpenNodesWeAreAdding ); } else { setOpenCount( currentOpenCount - numberOfOpenNodesWeAreAdding ); } updateParentOpenCount( numberOfOpenNodesWeAreAdding ); } else { PDOutlineItem previousLastChild = getLastChild(); previousLastChild.insertSiblingAfter( outlineNode ); } PDOutlineItem lastNode = outlineNode; while(lastNode.getNextSibling() != null) { lastNode = lastNode.getNextSibling(); } setLastChild( lastNode ); } /** * Return the first child or null if there is no child. * * @return The first child. */ public PDOutlineItem getFirstChild() { PDOutlineItem last = null; COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( "First" ); if( lastDic != null ) { last = new PDOutlineItem( lastDic ); } return last; } /** * Set the first child, this will be maintained by this class. * * @param outlineNode The new first child. */ protected void setFirstChild( PDOutlineNode outlineNode ) { node.setItem( "First", outlineNode ); } /** * Return the last child or null if there is no child. * * @return The last child. */ public PDOutlineItem getLastChild() { PDOutlineItem last = null; COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( "Last" ); if( lastDic != null ) { last = new PDOutlineItem( lastDic ); } return last; } /** * Set the last child, this will be maintained by this class. * * @param outlineNode The new last child. */ protected void setLastChild( PDOutlineNode outlineNode ) { node.setItem( "Last", outlineNode ); } /** * Get the number of open nodes. Or a negative number if this node * is closed. See PDF Reference for more details. This value * is updated as you append children and siblings. * * @return The Count attribute of the outline dictionary. */ public int getOpenCount() { return node.getInt( "Count", 0 ); } /** * Set the open count. This number is automatically managed for you * when you add items to the outline. * * @param openCount The new open cound. */ protected void setOpenCount( int openCount ) { node.setInt( "Count", openCount ); } /** * This will set this node to be open when it is shown in the viewer. By default, when * a new node is created it will be closed. * This will do nothing if the node is already open. */ public void openNode() { //if the node is already open then do nothing. if( !isNodeOpen() ) { int openChildrenCount = 0; PDOutlineItem currentChild = getFirstChild(); while( currentChild != null ) { //first increase by one for the current child openChildrenCount++; //then increase by the number of open nodes the child has if( currentChild.isNodeOpen() ) { openChildrenCount += currentChild.getOpenCount(); } currentChild = currentChild.getNextSibling(); } setOpenCount( openChildrenCount ); updateParentOpenCount( openChildrenCount ); } } /** * Close this node. * */ public void closeNode() { //if the node is already closed then do nothing. if( isNodeOpen() ) { int openCount = getOpenCount(); updateParentOpenCount( -openCount ); setOpenCount( -openCount ); } } /** * Node is open if the open count is greater than zero. * @return true if this node is open. */ public boolean isNodeOpen() { return getOpenCount() > 0; } /** * The count parameter needs to be updated when you add or remove elements to * the outline. When you add an element at a lower level then you need to * increase all of the parents. * * @param amount The amount to update by. */ protected void updateParentOpenCount( int amount ) { PDOutlineNode parent = getParent(); if( parent != null ) { int currentCount = parent.getOpenCount(); //if the currentCount is negative or it is absent then //we will treat it as negative. The default is to be negative. boolean negative = currentCount < 0 || parent.getCOSDictionary().getDictionaryObject( "Count" ) == null; currentCount = Math.abs( currentCount ); currentCount += amount; if( negative ) { currentCount = -currentCount; } parent.setOpenCount( currentCount ); //recursively call parent to update count, but the parents count is only //updated if this is an open node if( !negative ) { parent.updateParentOpenCount( amount ); } } } }