You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2015/10/30 22:25:48 UTC
svn commit: r1711554 -
/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java
Author: tilman
Date: Fri Oct 30 21:25:48 2015
New Revision: 1711554
URL: http://svn.apache.org/viewvc?rev=1711554&view=rev
Log:
PDFBOX-3072: add missing /Type /Page when missing in page, as suggested by Andrea Vacondio
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java?rev=1711554&r1=1711553&r2=1711554&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java Fri Oct 30 21:25:48 2015
@@ -1,518 +1,524 @@
-/*
- * 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;
-
-import java.util.ArrayDeque;
-import java.util.Iterator;
-import java.util.Queue;
-import org.apache.pdfbox.cos.COSArray;
-import org.apache.pdfbox.cos.COSBase;
-import org.apache.pdfbox.cos.COSDictionary;
-import org.apache.pdfbox.cos.COSInteger;
-import org.apache.pdfbox.cos.COSName;
-
-import org.apache.pdfbox.pdmodel.common.COSObjectable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * The page tree, which defines the ordering of pages in the document in an efficient manner.
- *
- * @author John Hewson
- */
-public class PDPageTree implements COSObjectable, Iterable<PDPage>
-{
- private final COSDictionary root;
- private final PDDocument document; // optional
-
- /**
- * Constructor for embedding.
- */
- public PDPageTree()
- {
- root = new COSDictionary();
- root.setItem(COSName.TYPE, COSName.PAGES);
- root.setItem(COSName.KIDS, new COSArray());
- root.setItem(COSName.COUNT, COSInteger.ZERO);
- document = null;
- }
-
- /**
- * Constructor for reading.
- *
- * @param root A page tree root.
- */
- public PDPageTree(COSDictionary root)
- {
- if (root == null)
- {
- throw new IllegalArgumentException("root cannot be null");
- }
- this.root = root;
- document = null;
- }
-
- /**
- * Constructor for reading.
- *
- * @param root A page tree root.
- * @param document The document which contains "root".
- */
- PDPageTree(COSDictionary root, PDDocument document)
- {
- if (root == null)
- {
- throw new IllegalArgumentException("root cannot be null");
- }
- this.root = root;
- this.document = document;
- }
-
- /**
- * Returns the given attribute, inheriting from parent tree nodes if necessary.
- *
- * @param node page object
- * @param key the key to look up
- * @return COS value for the given key
- */
- public static COSBase getInheritableAttribute(COSDictionary node, COSName key)
- {
- COSBase value = node.getDictionaryObject(key);
- if (value != null)
- {
- return value;
- }
-
- COSDictionary parent = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P);
- if (parent != null)
- {
- return getInheritableAttribute(parent, key);
- }
-
- return null;
- }
-
- /**
- * Returns an iterator which walks all pages in the tree, in order.
- */
- @Override
- public Iterator<PDPage> iterator()
- {
- return new PageIterator(root);
- }
-
- /**
- * Helper to get kids from malformed PDFs.
- * @param node page tree node
- * @return list of kids
- */
- private List<COSDictionary> getKids(COSDictionary node)
- {
- List<COSDictionary> result = new ArrayList<COSDictionary>();
-
- COSArray kids = (COSArray)node.getDictionaryObject(COSName.KIDS);
- if (kids == null)
- {
- // probably a malformed PDF
- return result;
- }
-
- for (int i = 0, size = kids.size(); i < size; i++)
- {
- result.add((COSDictionary)kids.getObject(i));
- }
-
- return result;
- }
-
- /**
- * Iterator which walks all pages in the tree, in order.
- */
- private final class PageIterator implements Iterator<PDPage>
- {
- private final Queue<COSDictionary> queue = new ArrayDeque<COSDictionary>();
-
- private PageIterator(COSDictionary node)
- {
- enqueueKids(node);
- }
-
- private void enqueueKids(COSDictionary node)
- {
- if (isPageTreeNode(node))
- {
- List<COSDictionary> kids = getKids(node);
- for (COSDictionary kid : kids)
- {
- enqueueKids(kid);
- }
- }
- else
- {
- queue.add(node);
- }
- }
-
- @Override
- public boolean hasNext()
- {
- return !queue.isEmpty();
- }
-
- @Override
- public PDPage next()
- {
- COSDictionary next = queue.poll();
-
- // sanity check
- if (next.getCOSName(COSName.TYPE) != COSName.PAGE)
- {
- throw new IllegalStateException("Expected Page but got " + next);
- }
-
- ResourceCache resourceCache = document != null ? document.getResourceCache() : null;
- return new PDPage(next, resourceCache);
- }
-
- @Override
- public void remove()
- {
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Returns the page at the given index.
- *
- * @param index zero-based index
- */
- public PDPage get(int index)
- {
- COSDictionary dict = get(index + 1, root, 0);
-
- // sanity check
- if (dict.getCOSName(COSName.TYPE) != COSName.PAGE)
- {
- throw new IllegalStateException("Expected Page but got " + dict);
- }
-
- ResourceCache resourceCache = document != null ? document.getResourceCache() : null;
- return new PDPage(dict, resourceCache);
- }
-
- /**
- * Returns the given COS page using a depth-first search.
- *
- * @param pageNum 1-based page number
- * @param node page tree node to search
- * @param encountered number of pages encountered so far
- * @return COS dictionary of the Page object
- */
- private COSDictionary get(int pageNum, COSDictionary node, int encountered)
- {
- if (pageNum < 0)
- {
- throw new IndexOutOfBoundsException("Index out of bounds: " + pageNum);
- }
-
- if (isPageTreeNode(node))
- {
- int count = node.getInt(COSName.COUNT, 0);
- if (pageNum <= encountered + count)
- {
- // it's a kid of this node
- for (COSDictionary kid : getKids(node))
- {
- // which kid?
- if (isPageTreeNode(kid))
- {
- int kidCount = kid.getInt(COSName.COUNT, 0);
- if (pageNum <= encountered + kidCount)
- {
- // it's this kid
- return get(pageNum, kid, encountered);
- }
- else
- {
- encountered += kidCount;
- }
- }
- else
- {
- // single page
- encountered++;
- if (pageNum == encountered)
- {
- // it's this page
- return get(pageNum, kid, encountered);
- }
- }
- }
-
- throw new IllegalStateException();
- }
- else
- {
- throw new IndexOutOfBoundsException("Index out of bounds: " + pageNum);
- }
- }
- else
- {
- if (encountered == pageNum)
- {
- return node;
- }
- else
- {
- throw new IllegalStateException();
- }
- }
- }
-
- /**
- * Returns true if the node is a page tree node (i.e. and intermediate).
- */
- private boolean isPageTreeNode(COSDictionary node )
- {
- // some files such as PDFBOX-2250-229205.pdf don't have Pages set as the Type, so we have
- // to check for the presence of Kids too
- return node.getCOSName(COSName.TYPE) == COSName.PAGES ||
- node.containsKey(COSName.KIDS);
- }
-
- /**
- * Returns the index of the given page, or -1 if it does not exist.
- *
- * @param page The page to search for.
- * @return the zero-based index of the given page, or -1 if the page is not found.
- */
- public int indexOf(PDPage page)
- {
- SearchContext context = new SearchContext(page);
- if (findPage(context, root))
- {
- return context.index;
- }
- return -1;
- }
-
- private boolean findPage(SearchContext context, COSDictionary node)
- {
- for (COSDictionary kid : getKids(node))
- {
- if (context.found)
- {
- break;
- }
- if (isPageTreeNode(kid))
- {
- findPage(context, kid);
- }
- else
- {
- context.visitPage(kid);
- }
- }
- return context.found;
- }
-
- private static final class SearchContext
- {
- private final COSDictionary searched;
- private int index = -1;
- private boolean found;
-
- private SearchContext(PDPage page)
- {
- this.searched = page.getCOSObject();
- }
-
- private void visitPage(COSDictionary current)
- {
- index++;
- found = searched.equals(current);
- }
- }
-
- /**
- * Returns the number of leaf nodes (page objects) that are descendants of this root within the
- * page tree.
- */
- public int getCount()
- {
- return root.getInt(COSName.COUNT, 0);
- }
-
- @Override
- public COSDictionary getCOSObject()
- {
- return root;
- }
-
- /**
- * Removes the page with the given index from the page tree.
- * @param index zero-based page index
- */
- public void remove(int index)
- {
- COSDictionary node = get(index + 1, root, 0);
- remove(node);
- }
-
- /**
- * Removes the given page from the page tree.
- *
- * @param page The page to remove.
- */
- public void remove(PDPage page)
- {
- remove(page.getCOSObject());
- }
-
- /**
- * Removes the given COS page.
- */
- private void remove(COSDictionary node)
- {
- // remove from parent's kids
- COSDictionary parent = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P);
- COSArray kids = (COSArray)parent.getDictionaryObject(COSName.KIDS);
- if (kids.removeObject(node))
- {
- // update ancestor counts
- do
- {
- node = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P);
- if (node != null)
- {
- node.setInt(COSName.COUNT, node.getInt(COSName.COUNT) - 1);
- }
- }
- while (node != null);
- }
- }
-
- /**
- * Adds the given page to this page tree.
- *
- * @param page The page to add.
- */
- public void add(PDPage page)
- {
- // set parent
- COSDictionary node = page.getCOSObject();
- node.setItem(COSName.PARENT, root);
-
- // todo: re-balance tree? (or at least group new pages into tree nodes of e.g. 20)
-
- // add to parent's kids
- COSArray kids = (COSArray)root.getDictionaryObject(COSName.KIDS);
- kids.add(node);
-
- // update ancestor counts
- do
- {
- node = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P);
- if (node != null)
- {
- node.setInt(COSName.COUNT, node.getInt(COSName.COUNT) + 1);
- }
- }
- while (node != null);
- }
-
- /**
- * Insert a page before another page within a page tree.
- *
- * @param newPage the page to be inserted.
- * @param nextPage the page that is to be after the new page.
- * @throws IllegalArgumentException if one attempts to insert a page that isn't part of a page
- * tree.
- */
- public void insertBefore(PDPage newPage, PDPage nextPage)
- {
- COSDictionary nextPageDict = nextPage.getCOSObject();
- COSDictionary parentDict = (COSDictionary) nextPageDict.getDictionaryObject(COSName.PARENT);
- COSArray kids = (COSArray) parentDict.getDictionaryObject(COSName.KIDS);
- boolean found = false;
- for (int i = 0; i < kids.size(); ++i)
- {
- COSDictionary pageDict = (COSDictionary) kids.getObject(i);
- if (pageDict.equals(nextPage.getCOSObject()))
- {
- kids.add(i, newPage.getCOSObject());
- newPage.getCOSObject().setItem(COSName.PARENT, parentDict);
- found = true;
- break;
- }
- }
- if (!found)
- {
- throw new IllegalArgumentException("attempted to insert before orphan page");
- }
-
- // now increase every parent
- do
- {
- int cnt = parentDict.getInt(COSName.COUNT);
- parentDict.setInt(COSName.COUNT, cnt + 1);
- parentDict = (COSDictionary) parentDict.getDictionaryObject(COSName.PARENT);
- }
- while (parentDict != null);
- }
-
- /**
- * Insert a page after another page within a page tree.
- *
- * @param newPage the page to be inserted.
- * @param prevPage the page that is to be before the new page.
- * @throws IllegalArgumentException if one attempts to insert a page that isn't part of a page
- * tree.
- */
- public void insertAfter(PDPage newPage, PDPage prevPage)
- {
- COSDictionary prevPageDict = prevPage.getCOSObject();
- COSDictionary parentDict = (COSDictionary) prevPageDict.getDictionaryObject(COSName.PARENT);
- COSArray kids = (COSArray) parentDict.getDictionaryObject(COSName.KIDS);
- boolean found = false;
- for (int i = 0; i < kids.size(); ++i)
- {
- COSDictionary pageDict = (COSDictionary) kids.getObject(i);
- if (pageDict.equals(prevPage.getCOSObject()))
- {
- kids.add(i + 1, newPage.getCOSObject());
- newPage.getCOSObject().setItem(COSName.PARENT, parentDict);
- found = true;
- break;
- }
- }
- if (!found)
- {
- throw new IllegalArgumentException("attempted to insert before orphan page");
- }
-
- // now increase every parent
- do
- {
- int cnt = parentDict.getInt(COSName.COUNT);
- parentDict.setInt(COSName.COUNT, cnt + 1);
- parentDict = (COSDictionary) parentDict.getDictionaryObject(COSName.PARENT);
- }
- while (parentDict != null);
- }
-}
+/*
+ * 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;
+
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.Queue;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The page tree, which defines the ordering of pages in the document in an efficient manner.
+ *
+ * @author John Hewson
+ */
+public class PDPageTree implements COSObjectable, Iterable<PDPage>
+{
+ private final COSDictionary root;
+ private final PDDocument document; // optional
+
+ /**
+ * Constructor for embedding.
+ */
+ public PDPageTree()
+ {
+ root = new COSDictionary();
+ root.setItem(COSName.TYPE, COSName.PAGES);
+ root.setItem(COSName.KIDS, new COSArray());
+ root.setItem(COSName.COUNT, COSInteger.ZERO);
+ document = null;
+ }
+
+ /**
+ * Constructor for reading.
+ *
+ * @param root A page tree root.
+ */
+ public PDPageTree(COSDictionary root)
+ {
+ if (root == null)
+ {
+ throw new IllegalArgumentException("root cannot be null");
+ }
+ this.root = root;
+ document = null;
+ }
+
+ /**
+ * Constructor for reading.
+ *
+ * @param root A page tree root.
+ * @param document The document which contains "root".
+ */
+ PDPageTree(COSDictionary root, PDDocument document)
+ {
+ if (root == null)
+ {
+ throw new IllegalArgumentException("root cannot be null");
+ }
+ this.root = root;
+ this.document = document;
+ }
+
+ /**
+ * Returns the given attribute, inheriting from parent tree nodes if necessary.
+ *
+ * @param node page object
+ * @param key the key to look up
+ * @return COS value for the given key
+ */
+ public static COSBase getInheritableAttribute(COSDictionary node, COSName key)
+ {
+ COSBase value = node.getDictionaryObject(key);
+ if (value != null)
+ {
+ return value;
+ }
+
+ COSDictionary parent = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P);
+ if (parent != null)
+ {
+ return getInheritableAttribute(parent, key);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an iterator which walks all pages in the tree, in order.
+ */
+ @Override
+ public Iterator<PDPage> iterator()
+ {
+ return new PageIterator(root);
+ }
+
+ /**
+ * Helper to get kids from malformed PDFs.
+ * @param node page tree node
+ * @return list of kids
+ */
+ private List<COSDictionary> getKids(COSDictionary node)
+ {
+ List<COSDictionary> result = new ArrayList<COSDictionary>();
+
+ COSArray kids = (COSArray)node.getDictionaryObject(COSName.KIDS);
+ if (kids == null)
+ {
+ // probably a malformed PDF
+ return result;
+ }
+
+ for (int i = 0, size = kids.size(); i < size; i++)
+ {
+ result.add((COSDictionary)kids.getObject(i));
+ }
+
+ return result;
+ }
+
+ /**
+ * Iterator which walks all pages in the tree, in order.
+ */
+ private final class PageIterator implements Iterator<PDPage>
+ {
+ private final Queue<COSDictionary> queue = new ArrayDeque<COSDictionary>();
+
+ private PageIterator(COSDictionary node)
+ {
+ enqueueKids(node);
+ }
+
+ private void enqueueKids(COSDictionary node)
+ {
+ if (isPageTreeNode(node))
+ {
+ List<COSDictionary> kids = getKids(node);
+ for (COSDictionary kid : kids)
+ {
+ enqueueKids(kid);
+ }
+ }
+ else
+ {
+ queue.add(node);
+ }
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return !queue.isEmpty();
+ }
+
+ @Override
+ public PDPage next()
+ {
+ COSDictionary next = queue.poll();
+
+ sanitizeType(next);
+
+ ResourceCache resourceCache = document != null ? document.getResourceCache() : null;
+ return new PDPage(next, resourceCache);
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Returns the page at the given index.
+ *
+ * @param index zero-based index
+ */
+ public PDPage get(int index)
+ {
+ COSDictionary dict = get(index + 1, root, 0);
+
+ sanitizeType(dict);
+
+ ResourceCache resourceCache = document != null ? document.getResourceCache() : null;
+ return new PDPage(dict, resourceCache);
+ }
+
+ private static void sanitizeType(COSDictionary dictionary)
+ {
+ COSName type = dictionary.getCOSName(COSName.TYPE);
+ if (type == null)
+ {
+ dictionary.setItem(COSName.TYPE, COSName.PAGE);
+ return;
+ }
+ if (!COSName.PAGE.equals(type))
+ {
+ throw new IllegalStateException("Expected 'Page' but found " + type);
+ }
+ }
+
+ /**
+ * Returns the given COS page using a depth-first search.
+ *
+ * @param pageNum 1-based page number
+ * @param node page tree node to search
+ * @param encountered number of pages encountered so far
+ * @return COS dictionary of the Page object
+ */
+ private COSDictionary get(int pageNum, COSDictionary node, int encountered)
+ {
+ if (pageNum < 0)
+ {
+ throw new IndexOutOfBoundsException("Index out of bounds: " + pageNum);
+ }
+
+ if (isPageTreeNode(node))
+ {
+ int count = node.getInt(COSName.COUNT, 0);
+ if (pageNum <= encountered + count)
+ {
+ // it's a kid of this node
+ for (COSDictionary kid : getKids(node))
+ {
+ // which kid?
+ if (isPageTreeNode(kid))
+ {
+ int kidCount = kid.getInt(COSName.COUNT, 0);
+ if (pageNum <= encountered + kidCount)
+ {
+ // it's this kid
+ return get(pageNum, kid, encountered);
+ }
+ else
+ {
+ encountered += kidCount;
+ }
+ }
+ else
+ {
+ // single page
+ encountered++;
+ if (pageNum == encountered)
+ {
+ // it's this page
+ return get(pageNum, kid, encountered);
+ }
+ }
+ }
+
+ throw new IllegalStateException();
+ }
+ else
+ {
+ throw new IndexOutOfBoundsException("Index out of bounds: " + pageNum);
+ }
+ }
+ else
+ {
+ if (encountered == pageNum)
+ {
+ return node;
+ }
+ else
+ {
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the node is a page tree node (i.e. and intermediate).
+ */
+ private boolean isPageTreeNode(COSDictionary node )
+ {
+ // some files such as PDFBOX-2250-229205.pdf don't have Pages set as the Type, so we have
+ // to check for the presence of Kids too
+ return node.getCOSName(COSName.TYPE) == COSName.PAGES ||
+ node.containsKey(COSName.KIDS);
+ }
+
+ /**
+ * Returns the index of the given page, or -1 if it does not exist.
+ *
+ * @param page The page to search for.
+ * @return the zero-based index of the given page, or -1 if the page is not found.
+ */
+ public int indexOf(PDPage page)
+ {
+ SearchContext context = new SearchContext(page);
+ if (findPage(context, root))
+ {
+ return context.index;
+ }
+ return -1;
+ }
+
+ private boolean findPage(SearchContext context, COSDictionary node)
+ {
+ for (COSDictionary kid : getKids(node))
+ {
+ if (context.found)
+ {
+ break;
+ }
+ if (isPageTreeNode(kid))
+ {
+ findPage(context, kid);
+ }
+ else
+ {
+ context.visitPage(kid);
+ }
+ }
+ return context.found;
+ }
+
+ private static final class SearchContext
+ {
+ private final COSDictionary searched;
+ private int index = -1;
+ private boolean found;
+
+ private SearchContext(PDPage page)
+ {
+ this.searched = page.getCOSObject();
+ }
+
+ private void visitPage(COSDictionary current)
+ {
+ index++;
+ found = searched.equals(current);
+ }
+ }
+
+ /**
+ * Returns the number of leaf nodes (page objects) that are descendants of this root within the
+ * page tree.
+ */
+ public int getCount()
+ {
+ return root.getInt(COSName.COUNT, 0);
+ }
+
+ @Override
+ public COSDictionary getCOSObject()
+ {
+ return root;
+ }
+
+ /**
+ * Removes the page with the given index from the page tree.
+ * @param index zero-based page index
+ */
+ public void remove(int index)
+ {
+ COSDictionary node = get(index + 1, root, 0);
+ remove(node);
+ }
+
+ /**
+ * Removes the given page from the page tree.
+ *
+ * @param page The page to remove.
+ */
+ public void remove(PDPage page)
+ {
+ remove(page.getCOSObject());
+ }
+
+ /**
+ * Removes the given COS page.
+ */
+ private void remove(COSDictionary node)
+ {
+ // remove from parent's kids
+ COSDictionary parent = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P);
+ COSArray kids = (COSArray)parent.getDictionaryObject(COSName.KIDS);
+ if (kids.removeObject(node))
+ {
+ // update ancestor counts
+ do
+ {
+ node = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P);
+ if (node != null)
+ {
+ node.setInt(COSName.COUNT, node.getInt(COSName.COUNT) - 1);
+ }
+ }
+ while (node != null);
+ }
+ }
+
+ /**
+ * Adds the given page to this page tree.
+ *
+ * @param page The page to add.
+ */
+ public void add(PDPage page)
+ {
+ // set parent
+ COSDictionary node = page.getCOSObject();
+ node.setItem(COSName.PARENT, root);
+
+ // todo: re-balance tree? (or at least group new pages into tree nodes of e.g. 20)
+
+ // add to parent's kids
+ COSArray kids = (COSArray)root.getDictionaryObject(COSName.KIDS);
+ kids.add(node);
+
+ // update ancestor counts
+ do
+ {
+ node = (COSDictionary) node.getDictionaryObject(COSName.PARENT, COSName.P);
+ if (node != null)
+ {
+ node.setInt(COSName.COUNT, node.getInt(COSName.COUNT) + 1);
+ }
+ }
+ while (node != null);
+ }
+
+ /**
+ * Insert a page before another page within a page tree.
+ *
+ * @param newPage the page to be inserted.
+ * @param nextPage the page that is to be after the new page.
+ * @throws IllegalArgumentException if one attempts to insert a page that isn't part of a page
+ * tree.
+ */
+ public void insertBefore(PDPage newPage, PDPage nextPage)
+ {
+ COSDictionary nextPageDict = nextPage.getCOSObject();
+ COSDictionary parentDict = (COSDictionary) nextPageDict.getDictionaryObject(COSName.PARENT);
+ COSArray kids = (COSArray) parentDict.getDictionaryObject(COSName.KIDS);
+ boolean found = false;
+ for (int i = 0; i < kids.size(); ++i)
+ {
+ COSDictionary pageDict = (COSDictionary) kids.getObject(i);
+ if (pageDict.equals(nextPage.getCOSObject()))
+ {
+ kids.add(i, newPage.getCOSObject());
+ newPage.getCOSObject().setItem(COSName.PARENT, parentDict);
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ throw new IllegalArgumentException("attempted to insert before orphan page");
+ }
+
+ // now increase every parent
+ do
+ {
+ int cnt = parentDict.getInt(COSName.COUNT);
+ parentDict.setInt(COSName.COUNT, cnt + 1);
+ parentDict = (COSDictionary) parentDict.getDictionaryObject(COSName.PARENT);
+ }
+ while (parentDict != null);
+ }
+
+ /**
+ * Insert a page after another page within a page tree.
+ *
+ * @param newPage the page to be inserted.
+ * @param prevPage the page that is to be before the new page.
+ * @throws IllegalArgumentException if one attempts to insert a page that isn't part of a page
+ * tree.
+ */
+ public void insertAfter(PDPage newPage, PDPage prevPage)
+ {
+ COSDictionary prevPageDict = prevPage.getCOSObject();
+ COSDictionary parentDict = (COSDictionary) prevPageDict.getDictionaryObject(COSName.PARENT);
+ COSArray kids = (COSArray) parentDict.getDictionaryObject(COSName.KIDS);
+ boolean found = false;
+ for (int i = 0; i < kids.size(); ++i)
+ {
+ COSDictionary pageDict = (COSDictionary) kids.getObject(i);
+ if (pageDict.equals(prevPage.getCOSObject()))
+ {
+ kids.add(i + 1, newPage.getCOSObject());
+ newPage.getCOSObject().setItem(COSName.PARENT, parentDict);
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ throw new IllegalArgumentException("attempted to insert before orphan page");
+ }
+
+ // now increase every parent
+ do
+ {
+ int cnt = parentDict.getInt(COSName.COUNT);
+ parentDict.setInt(COSName.COUNT, cnt + 1);
+ parentDict = (COSDictionary) parentDict.getDictionaryObject(COSName.PARENT);
+ }
+ while (parentDict != null);
+ }
+}