001: /**
002: * Copyright (c) 2005, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox.pdmodel.interactive.documentnavigation.outline;
031:
032: import org.pdfbox.cos.COSBase;
033: import org.pdfbox.cos.COSDictionary;
034:
035: import org.pdfbox.pdmodel.common.COSObjectable;
036:
037: /**
038: * This represents an node in an outline in a pdf document.
039: *
040: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
041: * @version $Revision: 1.3 $
042: */
043: public class PDOutlineNode implements COSObjectable {
044: /**
045: * The dictionary for this node.
046: */
047: protected COSDictionary node;
048:
049: /**
050: * Default Constructor.
051: */
052: public PDOutlineNode() {
053: node = new COSDictionary();
054: }
055:
056: /**
057: * Default Constructor.
058: *
059: * @param dict The dictionary storage.
060: */
061: public PDOutlineNode(COSDictionary dict) {
062: node = dict;
063: }
064:
065: /**
066: * Convert this standard java object to a COS object.
067: *
068: * @return The cos object that matches this Java object.
069: */
070: public COSBase getCOSObject() {
071: return node;
072: }
073:
074: /**
075: * Convert this standard java object to a COS object.
076: *
077: * @return The cos object that matches this Java object.
078: */
079: public COSDictionary getCOSDictionary() {
080: return node;
081: }
082:
083: /**
084: * Get the parent of this object. This will either be a DocumentOutline or an OutlineItem.
085: *
086: * @return The parent of this object, or null if this is the document outline and there
087: * is no parent.
088: */
089: public PDOutlineNode getParent() {
090: PDOutlineNode retval = null;
091: COSDictionary parent = (COSDictionary) node
092: .getDictionaryObject("Parent", "P");
093: if (parent != null) {
094: if (parent.getDictionaryObject("Parent", "P") == null) {
095: retval = new PDDocumentOutline(parent);
096: } else {
097: retval = new PDOutlineItem(parent);
098: }
099: }
100:
101: return retval;
102: }
103:
104: /**
105: * Set the parent of this object, this is maintained by these objects and should not
106: * be called by any clients of PDFBox code.
107: *
108: * @param parent The parent of this object.
109: */
110: protected void setParent(PDOutlineNode parent) {
111: node.setItem("Parent", parent);
112: }
113:
114: /**
115: * append a child node to this node.
116: *
117: * @param outlineNode The node to add.
118: */
119: public void appendChild(PDOutlineItem outlineNode) {
120: outlineNode.setParent(this );
121: if (getFirstChild() == null) {
122: int currentOpenCount = getOpenCount();
123: setFirstChild(outlineNode);
124: //1 for the the item we are adding;
125: int numberOfOpenNodesWeAreAdding = 1;
126: if (outlineNode.isNodeOpen()) {
127: numberOfOpenNodesWeAreAdding += outlineNode
128: .getOpenCount();
129: }
130: if (isNodeOpen()) {
131: setOpenCount(currentOpenCount
132: + numberOfOpenNodesWeAreAdding);
133: } else {
134: setOpenCount(currentOpenCount
135: - numberOfOpenNodesWeAreAdding);
136: }
137: updateParentOpenCount(numberOfOpenNodesWeAreAdding);
138: } else {
139: PDOutlineItem previousLastChild = getLastChild();
140: previousLastChild.insertSiblingAfter(outlineNode);
141: }
142: setLastChild(outlineNode);
143: }
144:
145: /**
146: * Return the first child or null if there is no child.
147: *
148: * @return The first child.
149: */
150: public PDOutlineItem getFirstChild() {
151: PDOutlineItem last = null;
152: COSDictionary lastDic = (COSDictionary) node
153: .getDictionaryObject("First");
154: if (lastDic != null) {
155: last = new PDOutlineItem(lastDic);
156: }
157: return last;
158: }
159:
160: /**
161: * Set the first child, this will be maintained by this class.
162: *
163: * @param outlineNode The new first child.
164: */
165: protected void setFirstChild(PDOutlineNode outlineNode) {
166: node.setItem("First", outlineNode);
167: }
168:
169: /**
170: * Return the last child or null if there is no child.
171: *
172: * @return The last child.
173: */
174: public PDOutlineItem getLastChild() {
175: PDOutlineItem last = null;
176: COSDictionary lastDic = (COSDictionary) node
177: .getDictionaryObject("Last");
178: if (lastDic != null) {
179: last = new PDOutlineItem(lastDic);
180: }
181: return last;
182: }
183:
184: /**
185: * Set the last child, this will be maintained by this class.
186: *
187: * @param outlineNode The new last child.
188: */
189: protected void setLastChild(PDOutlineNode outlineNode) {
190: node.setItem("Last", outlineNode);
191: }
192:
193: /**
194: * Get the number of open nodes. Or a negative number if this node
195: * is closed. See PDF Reference for more details. This value
196: * is updated as you append children and siblings.
197: *
198: * @return The Count attribute of the outline dictionary.
199: */
200: public int getOpenCount() {
201: return node.getInt("Count", 0);
202: }
203:
204: /**
205: * Set the open count. This number is automatically managed for you
206: * when you add items to the outline.
207: *
208: * @param openCount The new open cound.
209: */
210: protected void setOpenCount(int openCount) {
211: node.setInt("Count", openCount);
212: }
213:
214: /**
215: * This will set this node to be open when it is shown in the viewer. By default, when
216: * a new node is created it will be closed.
217: * This will do nothing if the node is already open.
218: */
219: public void openNode() {
220: //if the node is already open then do nothing.
221: if (!isNodeOpen()) {
222: int openChildrenCount = 0;
223: PDOutlineItem currentChild = getFirstChild();
224: while (currentChild != null) {
225: //first increase by one for the current child
226: openChildrenCount++;
227: //then increase by the number of open nodes the child has
228: if (currentChild.isNodeOpen()) {
229: openChildrenCount += currentChild.getOpenCount();
230: }
231: currentChild = currentChild.getNextSibling();
232: }
233: setOpenCount(openChildrenCount);
234: updateParentOpenCount(openChildrenCount);
235: }
236: }
237:
238: /**
239: * Close this node.
240: *
241: */
242: public void closeNode() {
243: //if the node is already closed then do nothing.
244: if (isNodeOpen()) {
245: int openCount = getOpenCount();
246: updateParentOpenCount(-openCount);
247: setOpenCount(-openCount);
248: }
249: }
250:
251: /**
252: * Node is open if the open count is greater than zero.
253: * @return true if this node is open.
254: */
255: public boolean isNodeOpen() {
256: return getOpenCount() > 0;
257: }
258:
259: /**
260: * The count parameter needs to be updated when you add or remove elements to
261: * the outline. When you add an element at a lower level then you need to
262: * increase all of the parents.
263: *
264: * @param amount The amount to update by.
265: */
266: protected void updateParentOpenCount(int amount) {
267: PDOutlineNode parent = getParent();
268: if (parent != null) {
269: int currentCount = parent.getOpenCount();
270: //if the currentCount is negative or it is absent then
271: //we will treat it as negative. The default is to be negative.
272: boolean negative = currentCount < 0
273: || parent.getCOSDictionary().getDictionaryObject(
274: "Count") == null;
275: currentCount = Math.abs(currentCount);
276: currentCount += amount;
277: if (negative) {
278: currentCount = -currentCount;
279: }
280: parent.setOpenCount(currentCount);
281: //recursively call parent to update count, but the parents count is only
282: //updated if this is an open node
283: if (!negative) {
284: parent.updateParentOpenCount(amount);
285: }
286: }
287: }
288: }
|