/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * <p>The <code>Destination</code> class defines a particular view of a * PDF document consisting of the following items:</p> * <ul> * <li>The page of the document to be displayed.</li> * <li>The location of the document window on that page.</li> * <li>The magnification (zoom) factor to use when displaying the page * Destinations may be associated with outline items, annotations, * or actions. </li> * </ul> * <p>Destination can be associated with outline items, annotations, or actions. * In each case the destination specifies the view of the document to be presented * when one of the respective objects is activated.</p> * <p>The Destination class currently only supports the Destination syntaxes, * [page /XYZ left top zoom], other syntax will be added in future releases. The * syntax [page /XYZ left top zoom] is defined as follows:</p> * <ul> * <li>page - designated page to show (Reference to a page).</li> * <li>/XYZ - named format of destination syntax.</li> * <li>left - x value of the upper left-and coordinate.</li> * <li>top - y value of the upper left-hand coordinate.</li> * <li>zoom - zoom factor to magnify page by.</li> * </ul> * <p>A null value for left, top or zoom specifies that the current view values * will be unchanged when navigating to the specified page. </p> * * @see org.icepdf.core.pobjects.annotations.Annotation * @see org.icepdf.core.pobjects.OutlineItem * @see org.icepdf.core.pobjects.actions.Action * @since 1.0 */ public class Destination { private static final Logger logger = Logger.getLogger(Destination.class.toString()); public static final Name D_KEY = new Name("D"); // Vector destination type formats. public static final Name TYPE_XYZ = new Name("XYZ"); public static final Name TYPE_FIT = new Name("Fit"); public static final Name TYPE_FITH = new Name("FitH"); public static final Name TYPE_FITV = new Name("FitV"); public static final Name TYPE_FITR = new Name("FitR"); public static final Name TYPE_FITB = new Name("FitB"); public static final Name TYPE_FITBH = new Name("FitBH"); public static final Name TYPE_FITBV = new Name("FitBV"); // library of all PDF document objects private Library library; // object containing all of the destinations parameters private Object object; // Reference object for destination private Reference ref; // type, /XYZ, /Fit, /FitH... private Name type; // Specified by /XYZ in the core, /(left)(top)(zoom) private Float left = null; private Float bottom = null; private Float right = null; private Float top = null; private Float zoom = null; // named Destination name, can be a name or String private Name namedDestination; // initiated flag private boolean inited; /** * Creates a new instance of a Destination. * * @param l document library. * @param h Destination dictionary entries. */ public Destination(Library l, Object h) { library = l; object = h; init(); } /** * Initiate the Destination. Retrieve any needed attributes. */ private void init() { // check for initiation if (inited) { return; } inited = true; // if vector we have found /XYZ if (object instanceof List) { parse((List) object); } // find named Destinations, this however is incomplete // @see #parser for more detailed information else if (object instanceof Name || object instanceof StringObject) { String s; // Make sure to decrypt this attribute if (object instanceof StringObject) { StringObject stringObject = (StringObject) object; s = stringObject.getDecryptedLiteralString(library.getSecurityManager()); } else { s = object.toString(); } // store the name namedDestination = new Name(s); boolean found = false; Catalog catalog = library.getCatalog(); if (catalog != null && catalog.getNames() != null) { NameTree nameTree = catalog.getNames().getDestsNameTree(); if (nameTree != null) { Object o = nameTree.searchName(s); if (o != null) { if (o instanceof List) { parse((List) o); found = true; } else if (o instanceof HashMap) { HashMap h = (HashMap) o; Object o1 = h.get(D_KEY); if (o1 instanceof List) { parse((List) o1); found = true; } } } } if (!found) { Dictionary dests = catalog.getDestinations(); if (dests != null) { Object ob = dests.getObject((Name) object); // list of destinations name->Dest pairs. if (ob instanceof List) { parse((List) ob); } // corner case for d attached list. else if (ob instanceof HashMap) { parse((List) (((HashMap) ob).get(D_KEY))); } else { if (logger.isLoggable(Level.FINE)) { logger.warning("Destination type missed=" + ob); } } } } } } } /** * Get the dictionary object, name, string or array. * * @return */ public Object getObject() { return object; } /** * Utility method for parsing the Destination attributes * * @param v vector of attributes associated with the Destination */ private void parse(List v) { if (v == null) return; // Assign a Reference Object ob = getDestValue(0, v); if (ob instanceof Reference) { ref = (Reference) ob; } // store type. ob = getDestValue(1, v); if (ob instanceof Name) { type = (Name) ob; } else if (ob != null) { type = new Name(ob.toString()); } // [page /XYZ left top zoom ] if (TYPE_XYZ.equals(type)) { ob = getDestValue(2, v); if (ob != null && !ob.equals("null")) { left = ((Number) ob).floatValue(); } ob = getDestValue(3, v); if (ob != null && !ob.equals("null")) { top = ((Number) ob).floatValue(); } // zoom can be a value but zero and null are treated as no zoom change. ob = getDestValue(4, v); if (ob != null && !ob.equals("null") && !ob.equals("0")) { zoom = ((Number) ob).floatValue(); } } // [page /FitH top] else if (TYPE_FITH.equals(type)) { ob = getDestValue(2, v); if (ob != null && !ob.equals("null")) { top = ((Number) ob).floatValue(); } } // [page /FitR left bottom right top] else if (TYPE_FITR.equals(type)) { ob = getDestValue(2, v); if (ob != null && !ob.equals("null")) { left = ((Number) ob).floatValue(); } ob = getDestValue(3, v); if (ob != null && !ob.equals("null")) { bottom = ((Number) ob).floatValue(); } ob = getDestValue(4, v); if (ob != null && !ob.equals("null")) { right = ((Number) ob).floatValue(); } ob = getDestValue(5, v); if (ob != null && !ob.equals("null")) { top = ((Number) ob).floatValue(); } } // [page /FitB] else if (TYPE_FITB.equals(type)) { // nothing to parse } // [page /FitBH top] else if (TYPE_FITBH.equals(type)) { ob = getDestValue(2, v); if (ob != null && !ob.equals("null")) { top = ((Number) ob).floatValue(); } } // [page /FitBV left] else if (TYPE_FITBV.equals(type)) { ob = getDestValue(2, v); if (ob != null && !ob.equals("null")) { left = ((Number) ob).floatValue(); } } } // utility to avoid indexing issues with malformed dest type formats. private static Object getDestValue(int index, List params) { if (params.size() > index) { return params.get(index); } return null; } /** * Gets the name of the named destination. * * @return name of destination if present, null otherwise. */ public Name getNamedDestination() { return namedDestination; } /** * Sets the named destination as a Named destination. It is assumed * the named destination already exists in the document. * * @param dest destination to associate with. */ public void setNamedDestination(Name dest) { namedDestination = dest; // only write out destination as names so we don't have worry about // encryption. object = dest; // re-parse as object should point to a new destination. inited = false; init(); } /** * Sets the destination syntax to the specified value. The Destinatoin * object clears the named destination and re initializes itself after the * assignment has been made. * * @param destinationSyntax new vector of destination syntax. */ public void setDestinationSyntax(List destinationSyntax) { // clear named destination namedDestination = null; object = destinationSyntax; // re-parse as object should point to a new destination. inited = false; init(); } /** * Utility for creating a /Fit or FitB syntax vector. * * @param page destination page pointer. * @param type type of destionation * @return new instance of vector containing well formed destination syntax. */ public static List<Object> destinationSyntax( Reference page, final Name type) { List<Object> destSyntax = new ArrayList<Object>(2); destSyntax.add(page); destSyntax.add(type); return destSyntax; } /** * Utility for creating a /FitH, /FitV, /FitBH or /FitBV syntax vector. * * @param page destination page pointer. * @param type type of destionation * @param offset offset coordinate value in page space for specified dest type. * @return new instance of vector containing well formed destination syntax. */ public static List<Object> destinationSyntax( Reference page, final Name type, Object offset) { List<Object> destSyntax = new ArrayList<Object>(3); destSyntax.add(page); destSyntax.add(type); destSyntax.add(offset); return destSyntax; } /** * Utility for creating a /XYZ syntax vector. * * @param page destination page pointer. * @param type type of destionation * @param left offset coordinate value in page space for specified dest type. * @param top offset coordinate value in page space for specified dest type. * @param zoom page zoom, 0 or null indicates no zoom. * @return new instance of vector containing well formed destination syntax. */ public static List<Object> destinationSyntax( Reference page, final Object type, Object left, Object top, Object zoom) { List<Object> destSyntax = new ArrayList<Object>(5); destSyntax.add(page); destSyntax.add(type); destSyntax.add(left); destSyntax.add(top); destSyntax.add(zoom); return destSyntax; } /** * Utility for creating a /FitR syntax vector. * * @param page destination page pointer. * @param type type of destionation * @param left offset coordinate value in page space for specified dest type. * @param top offset coordinate value in page space for specified dest type. * @param bottom offset coordinate value in page space for specified dest type. * @param right offset coordinate value in page space for specified dest type. * @return new instance of vector containing well formed destination syntax. */ public static List<Object> destinationSyntax( Reference page, final Object type, Object left, Object bottom, Object right, Object top) { List<Object> destSyntax = new ArrayList<Object>(6); destSyntax.add(page); destSyntax.add(type); destSyntax.add(left); destSyntax.add(bottom); destSyntax.add(right); destSyntax.add(top); return destSyntax; } /** * Gets the Page Reference specified by the destination. * * @return a Reference to the Page Object associated with this destination. */ public Reference getPageReference() { return ref; } /** * Gets the left offset from the top, left position of the page specified by * this destination. * * @return the left offset from the top, left position of the page. If not * specified Float.NaN is returned. */ public Float getLeft() { return left; } /** * Gets the top offset from the top, left position of the page specified by * this destination. * * @return the top offset from the top, left position of the page. If not * specified Float.NaN is returned. */ public Float getTop() { return top; } /** * Gets the zoom level specifed by the destination. * * @return the specified zoom level, Float.NaN if not specified. */ public Float getZoom() { return zoom; } /** * Gets the page reference represented by this destination * * @return reference of page that destination should show when executed. */ public Reference getRef() { return ref; } /** * Gets the type used in a vector of destination syntax. Will be null * if a named destination is used. * * @return type of destination syntax as defined by class constants. */ public Name getType() { return type; } /** * Bottom coordinate of a zoom box, if present left, right and top should * also be available. * * @return bottom coordinate of magnifcation box. */ public Float getBottom() { return bottom; } /** * Right coordinate of a zoom box, if present bottom, left and top should * also be available. * * @return rigth coordinate of zoom box */ public Float getRight() { return right; } /** * Get the destination properties encoded in post script form. * * @return either a destination Name or a Vector representing the * destination */ public Object getEncodedDestination() { // write out the destination name if (namedDestination != null) { return namedDestination; } // build and return a fector of changed valued. else if (object instanceof List) { List<Object> v = new ArrayList<Object>(7); if (ref != null) { v.add(ref); } // named dest type if (type != null) { v.add(type); } // left if (left != Float.NaN) { v.add(left); } // bottom if (bottom != Float.NaN) { v.add(bottom); } // right if (right != Float.NaN) { v.add(right); } // top if (top != Float.NaN) { v.add(top); } // zoom if (zoom != Float.NaN) { v.add(zoom); } return v; } return null; } /** * Returns a summary of the annotation dictionary values. * * @return dictionary values. */ public String toString() { return "Destination ref: " + getPageReference() + " , top: " + getTop() + " , left: " + getLeft() + " , zoom: " + getZoom(); } }