/* * 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.HashMap; import java.util.List; import java.util.Set; /** * <P>The trailer of a PDF file enables an application reading the file to quickly * find the cross-reference table and certain special objects. Applications * should read a PDF file from its end. The last line of the file contains only * the end-of-file marker, %%EOF.</p> * <br> * <p>A document can have more then one trailer reference. It is important to use * the addTrailer() method if a subsequent trailer is found, or the * addPreviousTrailer() method if a previous trailer is found, depending on if * the PDF file is being read linearly, or via random access seeking.</p> * <br> * <p>If the Prev key entry is present then the document has more then one * cross-reference section. There is a numerical value, which is typically * associated with the trailer, that comes after startxref, and before %%EOF. * It is byte offset from the beginning of the file to the beginning of the last * cross-reference section.</p> * <br> * <p>In a regular PDF, it's the address of the current xref table. In a linearized * PDF, it's the address of the xref table at the file beginning, or zero. * In an updated PDF, it's the address of the current xref table. In all cases, * the LastCrossReferenceSection field, at the end of the PDF file, points * to the byte offset from the beginning of the file, of the "last" xref section, * which means the xref section with the highest precedence. For each xref section, * its following trailer section has a Prev field, which points to the byte * offset from the beginning of the file, of the xref section with one less * degree of precedence.</p> * * @since 1.1 */ public class PTrailer extends Dictionary { public static final Name SIZE_KEY = new Name("Size"); public static final Name PREV_KEY = new Name("Prev"); public static final Name ROOT_KEY = new Name("Root"); public static final Name ENCRYPT_KEY = new Name("Encrypt"); public static final Name INFO_KEY = new Name("Info"); public static final Name ID_KEY = new Name("ID"); public static final Name XREFSTM_KEY = new Name("XRefStm"); // Position in the file. The LazyObjectLoader typically keeps this info // for all PDF objects, but the bootstrapping PTrialer is an exception, // and we need its location for writing incremental updates, so for // consistency we'll have all PTrailers maintain their position. private long position; // documents cross reference table private CrossReference crossReferenceTable; // documents cross reference stream. private CrossReference crossReferenceStream; /** * Create a new PTrailer object * * @param dictionary dictionary associated with the trailer */ public PTrailer(Library library, HashMap dictionary, CrossReference xrefTable, CrossReference xrefStream) { super(library, dictionary); crossReferenceTable = xrefTable; crossReferenceStream = xrefStream; if (crossReferenceTable != null) crossReferenceTable.setTrailer(this); if (crossReferenceStream != null) crossReferenceStream.setTrailer(this); } /** * Gets the total number of entries in the file's cross-reference table, as * defined by the combination of the original section and all updated sections. * Equivalently, this value is 1 greater than the highest object number * used in the file. * <ul> * <li>Note: Any object in a cross-reference section whose number is * greater than this value is ignored and considered missing.</li> * </ul> * <br> * <b>Required : </b> must not be an indirect reference * * @return total number of entries in the file's cross-reference table */ public int getNumberOfObjects() { return library.getInt(entries, SIZE_KEY); } /** * Gets the byte offset from the beginning of the file to the beginning of the * previous cross-reference section. * <br> * (Present only if the file has more than one cross-reference section; must * not be an indirect reference) * * @return byte offset from beginning of the file to the beginning of the * previous cross-reference section */ public long getPrev() { return library.getLong(entries, PREV_KEY); } /** * Depending on if the PDF file is version 1.4 or before, or 1.5 or after, * it may have a cross reference table, or cross reference stream, or both. * If there are both, then the cross reference table has precedence. * * @return the cross reference object with the highest precedence, for this trailer */ protected CrossReference getPrimaryCrossReference() { if (crossReferenceTable != null) return crossReferenceTable; loadXRefStmIfApplicable(); return crossReferenceStream; } /** * Gets the cross reference table. * * @return cross reference table object; null, if one does not exist. */ protected CrossReference getCrossReferenceTable() { return crossReferenceTable; } /** * Gets the cross reference stream. * * @return cross reference stream object; null, if one does not exist. */ protected CrossReference getCrossReferenceStream() { return crossReferenceStream; } /** * Gets the catalog reference for the PDF document contained in the file. * <br> * <b>Required : </b> must not be an indirect reference * * @return reference number of catalog reference. */ public Reference getRootCatalogReference() { return library.getObjectReference(entries, ROOT_KEY); } /** * Gets the Catalog entry for this PDF document. * * @return Catalog entry. */ @SuppressWarnings("unchecked") public Catalog getRootCatalog() { Object tmp = library.getObject(entries, ROOT_KEY); // specification states the the root entry must be a indirect if (tmp instanceof Catalog) { return (Catalog) tmp; } // there are however a few instances where the dictionary is specified // directly else if (tmp instanceof HashMap) { return new Catalog(library, (HashMap<Object, Object>) tmp); } // if no root was found we return so that the use will be notified // of the problem which is the PDF can not be loaded. else { return null; } } /** * The document's encryption dictionary * <br> * <b>Required : </b> if document is encrypted; PDF 1.1 * * @return encryption dictionary */ @SuppressWarnings("unchecked") public HashMap<Object, Object> getEncrypt() { Object encryptParams = library.getObject(entries, ENCRYPT_KEY); if (encryptParams instanceof HashMap) { return (HashMap) encryptParams; } else { return null; } } /** * The document's information dictionary * <br> * <b>Optional : </b> must be an indirect reference. * * @return information dictionary */ public PInfo getInfo() { Object info = library.getObject(entries, INFO_KEY); if (info instanceof HashMap) { return new PInfo(library, (HashMap) info); } else { return null; } } /** * A vector of two strings constituting a file identifier * <br> * <b>Optional : </b> PDF 1.1. * * @return vector containing constituting file identifier */ public List getID() { return (List) library.getObject(entries, ID_KEY); } /** * @return The position in te file where this trailer is located */ public long getPosition() { return position; } /** * After this PTrailer is parsed, we store it's location within the PDF * here, for future use. */ public void setPosition(long pos) { position = pos; } /** * Add the trailer dictionary to the current trailer object's dictionary. * * @param nextTrailer document trailer object */ @SuppressWarnings("unchecked") protected void addNextTrailer(PTrailer nextTrailer) { nextTrailer.getPrimaryCrossReference().addToEndOfChainOfPreviousXRefs(getPrimaryCrossReference()); // Later key,value pairs take precedence over previous entries HashMap nextDictionary = nextTrailer.getDictionary(); HashMap currDictionary = getDictionary(); Set currKeys = currDictionary.keySet(); for (Object currKey : currKeys) { if (!nextDictionary.containsKey(currKey)) { Object currValue = currDictionary.get(currKey); nextDictionary.put(currKey, currValue); } } } @SuppressWarnings("unchecked") protected void addPreviousTrailer(PTrailer previousTrailer) { //System.out.println("PTrailer.addPreviousTrailer()"); getPrimaryCrossReference().addToEndOfChainOfPreviousXRefs(previousTrailer.getPrimaryCrossReference()); // Later key,value pairs take precedence over previous entries HashMap currDictionary = getDictionary(); HashMap prevDictionary = previousTrailer.getDictionary(); Set prevKeys = prevDictionary.keySet(); for (Object prevKey : prevKeys) { if (!currDictionary.containsKey(prevKey)) { Object prevValue = prevDictionary.get(prevKey); currDictionary.put(prevKey, prevValue); } } } protected void onDemandLoadAndSetupPreviousTrailer() { //System.out.println("PTrailer.onDemandLoadAndSetupPreviousTrailer() : " + this); //try { throw new RuntimeException(); } catch(Exception e) { e.printStackTrace(); } long position = getPrev(); if (position > 0L) { PTrailer prevTrailer = library.getTrailerByFilePosition(position); if (prevTrailer != null) addPreviousTrailer(prevTrailer); } } protected void loadXRefStmIfApplicable() { if (crossReferenceStream == null) { long xrefStreamPosition = library.getLong(entries, XREFSTM_KEY); if (xrefStreamPosition > 0L) { // OK, this is a little weird, but basically, any XRef stream // dictionary is also a Trailer dictionary, so our Parser // makes both of the objects. // Now, we don't actually want to chain the trailer as our // previous, but only want its CrossReferenceStream to make // our own PTrailer trailer = library.getTrailerByFilePosition(xrefStreamPosition); if (trailer != null) crossReferenceStream = trailer.getCrossReferenceStream(); } } } /** * Get the trailer object's dictionary. * * @return dictionary */ public HashMap getDictionary() { return entries; } /** * Returns a summary of the PTrailer dictionary values. * * @return dictionary values. */ public String toString() { return "PTRAILER= " + entries.toString() + " xref table=" + crossReferenceTable + " xref stream=" + crossReferenceStream; } }