/* * 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.util.Library; import java.util.Hashtable; import java.util.Vector; /** * <p>This class represents a document's page tree which defines the ordering * of pages in the document. The tree structure allows a user to quickly * open a document containing thousands of pages. The tree contains nodes of * two types, page tree nodes and page nodes, where page tree nodes are * intermediate nodes and pages are leaves. A simple example of this tree structure * is a single page tree node that references all of the document's page * objects directly.</p> * <p/> * <p>The page tree is accessible via the document catalog and can be traversed * to display a desired page or extracts its content.<p> * * @see org.icepdf.core.pobjects.Page * @see org.icepdf.core.pobjects.Catalog * @since 2.0 */ public class PageTree extends Dictionary { // Number of leaf nodes private int kidsCount = 0; // vector of references to leafs private Vector kidsReferences; // vector of the pages associated with tree private Vector kidsPageAndPages; // pointer to parent page tree private PageTree parent; // initiated flag private boolean inited; // inheritable page boundary data. private PRectangle mediaBox; private PRectangle cropBox; // inheritable Resources private Resources resources; // loaded resource flag, we can't use null check as some trees don't have // resources. private boolean loadedResources; /** * Inheritable rotation factor by child pages. */ protected float rotationFactor = 0; /** * Indicates that the PageTree has a rotation factor which should be respected. */ protected boolean isRotationFactor = false; /** * Creates a new instance of a PageTree. * * @param l document library. * @param h PageTree dictionary entries. */ public PageTree(Library l, Hashtable h) { super(l, h); } /** * Dispose the PageTree. */ protected synchronized void dispose(boolean cache) { if (kidsReferences != null) { if (!cache) { kidsReferences.clear(); kidsReferences.trimToSize(); } } if (kidsPageAndPages != null) { for (Object pageOrPages : kidsPageAndPages) { if (pageOrPages instanceof Page) ((Page) pageOrPages).dispose(cache); else if (pageOrPages instanceof PageTree) ((PageTree) pageOrPages).dispose(cache); } if (!cache) { kidsPageAndPages.clear(); kidsPageAndPages.trimToSize(); } } /* * If resources is non-null, then at least one page has got a reference * to it. At which point we'll wait for the last page to reference it * do make the call to dispose. * * It is also possible for type3 fonts to use a resource, so we better * go through the motions to remove the resource, as we don't dispose * of fonts. */ if (resources != null) { boolean disposeSuccess = resources.dispose(cache, this); if (disposeSuccess) { loadedResources = false; } } } /** * Initiate the PageTree. */ public synchronized void init() { if (inited) { return; } Object parentTree = library.getObject(entries, "Parent"); if (parentTree instanceof PageTree) { parent = (PageTree) library.getObject(entries, "Parent"); } kidsCount = library.getNumber(entries, "Count").intValue(); Vector boxDimensions = (Vector) (library.getObject(entries, "MediaBox")); if (boxDimensions != null) { mediaBox = new PRectangle(boxDimensions); // System.out.println("PageTree - MediaBox " + mediaBox); } boxDimensions = (Vector) (library.getObject(entries, "CropBox")); if (boxDimensions != null) { cropBox = new PRectangle(boxDimensions); // System.out.println("PageTree - CropBox " + cropBox); } kidsReferences = (Vector) library.getObject(entries, "Kids"); kidsPageAndPages = new Vector(kidsReferences.size()); kidsPageAndPages.setSize(kidsReferences.size()); // Rotation is only respected if child pages do not have their own // rotation value. Object tmpRotation = library.getObject(entries, "Rotate"); if (tmpRotation != null) { rotationFactor = ((Number) tmpRotation).floatValue(); // mark that we have an inheritable value isRotationFactor = true; } inited = true; } // todo: clean up method void initRootPageTree() { } /** * Gets the media box boundary defined by this page tree. The media box is a * required page entry and can be inherited from its parent page tree. * * @return media box boundary in user space units. */ public PRectangle getMediaBox() { return mediaBox; } /** * Gets the crop box boundary defined by this page tree. The media box is a * required page entry and can be inherited from its parent page tree. * * @return crop box boundary in user space units. */ public PRectangle getCropBox() { return cropBox; } /** * Gets the Resources defined by this PageTree. The Resources entry can * be inherited by the child Page objects. * <p/> * The caller is responsible for disposing of the returned Resources object. * * @return Resources associates with the PageTree */ public Resources getResources() { if (!loadedResources) { loadedResources = true; resources = library.getResources(entries, "Resources"); } return resources; } /** * Gets the page tree node that is the immediate parent of this one. * * @return parent page tree; null, if this is the root page tree. */ public PageTree getParent() { return parent; } /** * Gets the page number of the page specifed by a reference. * * @param r reference to a page in the page tree. * @return page number of the specified reference. If no page is found, -1 * is returned. */ public int getPageNumber(Reference r) { Page pg = (Page) library.getObject(r); if (pg == null) return -1; // pg.init(); int globalIndex = 0; Reference currChildRef = r; Reference currParentRef = pg.getParentReference(); PageTree currParent = pg.getParent(); while (currParentRef != null && currParent != null) { currParent.init(); int refIndex = currParent.indexOfKidReference(currChildRef); if (refIndex < 0) return -1; int localIndex = 0; for (int i = 0; i < refIndex; i++) { Object pageOrPages = currParent.getPageOrPagesPotentiallyNotInitedFromReferenceAt(i); if (pageOrPages instanceof Page) { localIndex++; } else if (pageOrPages instanceof PageTree) { PageTree peerPageTree = (PageTree) pageOrPages; peerPageTree.init(); localIndex += peerPageTree.getNumberOfPages(); } } globalIndex += localIndex; currChildRef = currParentRef; currParentRef = (Reference) currParent.entries.get("Parent"); currParent = currParent.parent; } return globalIndex; } /** * Utility method for getting kid index. * * @param r * @return */ private int indexOfKidReference(Reference r) { for (int i = 0; i < kidsReferences.size(); i++) { Reference ref = (Reference) kidsReferences.get(i); if (ref.equals(r)) return i; } return -1; } /** * Utility method for initializing a page in the page tree. * * @param index index in the kids vector to initialize * @return */ private Object getPageOrPagesPotentiallyNotInitedFromReferenceAt(int index) { Object pageOrPages = kidsPageAndPages.get(index); if (pageOrPages == null) { Reference ref = (Reference) kidsReferences.get(index); pageOrPages = library.getObject(ref); kidsPageAndPages.set(index, pageOrPages); } return pageOrPages; } /** * Utility method for initializing a page with its page number * * @param globalIndex * @return */ private Page getPagePotentiallyNotInitedByRecursiveIndex(int globalIndex) { int globalIndexSoFar = 0; int numLocalKids = kidsPageAndPages.size(); for (int i = 0; i < numLocalKids; i++) { Object pageOrPages = getPageOrPagesPotentiallyNotInitedFromReferenceAt(i); if (pageOrPages instanceof Page) { if (globalIndex == globalIndexSoFar) return (Page) pageOrPages; globalIndexSoFar++; } else if (pageOrPages instanceof PageTree) { PageTree childPageTree = (PageTree) pageOrPages; childPageTree.init(); int numChildPages = childPageTree.getNumberOfPages(); if (globalIndex >= globalIndexSoFar && globalIndex < (globalIndexSoFar + numChildPages)) { return childPageTree.getPagePotentiallyNotInitedByRecursiveIndex( globalIndex - globalIndexSoFar); } globalIndexSoFar += numChildPages; } } return null; } /** * In a PDF file there is a root Pages object, which contains * children Page objects, as well as children PageTree objects, * all arranged in a tree. * getNumberOfPages() exists in every PageTree object, giving * the number of Page objects under it, recursively. * So, each PageTree object would have a different number of pages, * and only the root PageTree objects would have a number * representative of the whole Document. * * @return Total number of Page objects under this PageTree */ public int getNumberOfPages() { return kidsCount; } /** * Gets a Page from the PDF file, locks it for the user, * initializes the Page, and returns it. * <p/> * ICEpdf uses a caching and memory management mechanism * to reduce the CPU, I/O, and time to access a Page, * which requires a locking and releasing protocol. * Calls to the <code>getPage</code> must be matched with * corresponding calls to <code>releasePage</code>. * Calls cannot be nested, meaning that <code>releasePage</code> * must be called before a subsequent invocation of * <code>getPage</code> for the same <code>pageIndex</code>. * * @param pageNumber Zero-based index of the Page to return. * @param user The object that is asking for the Page to be locked on its behalf. * @return The requested Page. * @see #releasePage */ public Page getPage(int pageNumber, Object user) { if (pageNumber < 0) return null; Page p = getPagePotentiallyNotInitedByRecursiveIndex(pageNumber); if (p != null) { // Add Page to cache, and lock it from getting disposed library.memoryManager.lock(user, p); //p.init(); } return p; } /** * Get the page reference for the specified page number. * * @param pageNumber zero-based indox of page to find reference of. * @return found page reference or null if number could not be resolved. */ public Reference getPageReference(int pageNumber) { if (pageNumber < 0) return null; Page p = getPagePotentiallyNotInitedByRecursiveIndex(pageNumber); if (p != null) { return p.getPObjectReference(); } return null; } /** * Release the Page that was locked by <code>getPage</code> * for user. * <p/> * ICEpdf uses a caching and memory management mechanism * to reduce the CPU, I/O, and time to access a Page, * which requires a locking and releasing protocol. * Calls to the <code>getPage</code> must be matched with * corresponding calls to <code>releasePage</code>. * Calls cannot be nested, meaning that <code>releasePage</code> * must be called before a subsequent invocation of * <code>getPage</code> for the same <code>pageIndex</code>. * * @param page The Page that was locked * @param user The entity for whom the page was locked. * @see #getPage */ public void releasePage(Page page, Object user) { // Release lock, allowing it to be disposed if (library != null && library.memoryManager != null) library.memoryManager.release(user, page); } /** * Release the Page that was locked by <code>getPage</code> * for user. * <p/> * ICEpdf uses a caching and memory management mechanism * to reduce the CPU, I/O, and time to access a Page, * which requires a locking and releasing protocol. * Calls to the <code>getPage</code> must be matched with * corresponding calls to <code>releasePage</code>. * Calls cannot be nested, meaning that <code>releasePage</code> * must be called before a subsequent invocation of * <code>getPage</code> for the same <code>pageIndex</code>. * * @param pageNumber The page number of the Page that was locked. * @param user The entity for whom the page was locked. * @see #getPage */ public void releasePage(int pageNumber, Object user) { Page page = getPagePotentiallyNotInitedByRecursiveIndex(pageNumber); // Release lock, allowing it to be disposed library.memoryManager.release(user, page); } /** * Returns a summary of the PageTree dictionary values. * * @return dictionary values. */ public String toString() { return "PAGES= " + entries.toString(); } }