You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by le...@apache.org on 2011/06/23 19:07:30 UTC
svn commit: r1138995 - in
/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox: cos/COSDocument.java
cos/COSName.java pdfparser/PDFParser.java
pdfparser/PDFXrefStreamParser.java pdfparser/XrefTrailerResolver.java
Author: lehmi
Date: Thu Jun 23 17:07:30 2011
New Revision: 1138995
URL: http://svn.apache.org/viewvc?rev=1138995&view=rev
Log:
PDFBOX-1016: added a specification conform xref/trailer parsing as proposed by Timo Boehme incl. some small changes/improvements
Added:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFXrefStreamParser.java
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java?rev=1138995&r1=1138994&r2=1138995&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java Thu Jun 23 17:07:30 2011
@@ -30,7 +30,6 @@ import org.apache.pdfbox.io.RandomAccess
import org.apache.pdfbox.io.RandomAccessBuffer;
import org.apache.pdfbox.io.RandomAccessFile;
import org.apache.pdfbox.pdfparser.PDFObjectStreamParser;
-import org.apache.pdfbox.pdfparser.PDFXrefStreamParser;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
import org.apache.pdfbox.persistence.util.COSObjectKey;
@@ -147,7 +146,7 @@ public class COSDocument extends COSBase
*
* @param scratchDir The directory to store a scratch file.
*
- * @throws IOException If there is an error creating the tmp file.
+ * @throws IOException If there is an error creating the tmp file.
*/
public COSDocument(File scratchDir) throws IOException {
this(scratchDir, false);
@@ -347,14 +346,14 @@ public class COSDocument extends COSBase
COSObject documentCatalog = getCatalog();
if (documentCatalog != null)
{
- COSDictionary acroForm = (COSDictionary)documentCatalog.getDictionaryObject(COSName.getPDFName("AcroForm"));
+ COSDictionary acroForm = (COSDictionary)documentCatalog.getDictionaryObject(COSName.ACRO_FORM);
if (acroForm !=null)
{
- COSArray fields = (COSArray)acroForm.getDictionaryObject("Fields");
+ COSArray fields = (COSArray)acroForm.getDictionaryObject(COSName.FIELDS);
for ( Object object : fields )
{
COSObject dict = (COSObject)object;
- if(dict.getItem(COSName.getPDFName("FT")).equals(COSName.getPDFName("Sig")))
+ if(dict.getItem(COSName.FT).equals(COSName.SIG))
{
COSBase dictionaryObject = dict.getDictionaryObject(COSName.V);
@@ -525,7 +524,7 @@ public class COSDocument extends COSBase
*/
public void dereferenceObjectStreams() throws IOException
{
- for( COSObject objStream : getObjectsByType( "ObjStm" ) )
+ for( COSObject objStream : getObjectsByType( COSName.OBJ_STM ) )
{
COSStream stream = (COSStream)objStream.getObject();
PDFObjectStreamParser parser =
@@ -585,14 +584,13 @@ public class COSDocument extends COSBase
}
/**
- * Used to populate the XRef HashMap. Will add an Xreftable entry
- * that maps ObjectKeys to byte offsets in the file.
- * @param objKey The objkey, with id and gen numbers
- * @param offset The byte offset in this file
+ * Populate XRef HashMap with given values.
+ * Each entry maps ObjectKeys to byte offsets in the file.
+ * @param _xrefTable xref table entries to be added
*/
- public void setXRef(COSObjectKey objKey, int offset)
+ public void addXRefTable( Map<COSObjectKey, Integer> xrefTable )
{
- xrefTable.put(objKey, offset);
+ this.xrefTable.putAll( xrefTable );
}
/**
@@ -606,27 +604,6 @@ public class COSDocument extends COSBase
}
/**
- * This method will search the list of objects for types of XRef and
- * uses the parsed data to populate the trailer information as well as
- * the xref Map.
- *
- * @throws IOException if there is an error parsing the stream
- */
- public void parseXrefStreams() throws IOException
- {
- COSDictionary trailerDict = new COSDictionary();
- for( COSObject xrefStream : getObjectsByType( "XRef" ) )
- {
- COSStream stream = (COSStream)xrefStream.getObject();
- trailerDict.addAll(stream);
- PDFXrefStreamParser parser =
- new PDFXrefStreamParser(stream, this, forceParsing);
- parser.parse();
- }
- setTrailer( trailerDict );
- }
-
- /**
* This method set the startxref value of the document. This will only
* be needed for incremental updates.
*
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java?rev=1138995&r1=1138994&r2=1138995&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java Thu Jun 23 17:07:30 2011
@@ -769,6 +769,11 @@ public final class COSName extends COSBa
*/
public static final COSName OBJ = new COSName("Obj");
+ /**
+ * A common COSName value.
+ */
+ public static final COSName OBJ_STM = new COSName( "ObjStm" );
+
/** the COSName for the content group tag. */
public static final COSName OC = new COSName("OC");
/** the COSName for an optional content group. */
@@ -1128,6 +1133,10 @@ public final class COSName extends COSBa
/** "XObject" */
public static final COSName XOBJECT = new COSName( "XObject" );
/**
+ * A common COSName value.
+ */
+ public static final COSName XREF = new COSName( "XRef" );
+ /**
* The prefix to a PDF name.
*/
public static final byte[] NAME_PREFIX = new byte[] { 47 }; // The / character
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java?rev=1138995&r1=1138994&r2=1138995&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFParser.java Thu Jun 23 17:07:30 2011
@@ -30,7 +30,9 @@ import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSDocument;
import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.exceptions.WrappedIOException;
import org.apache.pdfbox.io.RandomAccess;
import org.apache.pdfbox.pdmodel.PDDocument;
@@ -59,7 +61,11 @@ public class PDFParser extends BaseParse
* A list of duplicate objects found when Parsing the PDF
* File.
*/
- private List conflictList = new ArrayList();
+ private List<ConflictObj> conflictList = new ArrayList<ConflictObj>();
+
+ /** Collects all Xref/trailer objects and resolves them into single
+ * object using startxref reference */
+ private XrefTrailerResolver xrefTrailerResolver = new XrefTrailerResolver();
/**
* Temp file directory.
@@ -167,59 +173,55 @@ public class PDFParser extends BaseParse
skipToNextObj();
boolean wasLastParsedObjectEOF = false;
- try
+ while(true)
{
- while(true)
+ if(pdfSource.isEOF())
+ {
+ break;
+ }
+ try
{
- if(pdfSource.isEOF())
+ wasLastParsedObjectEOF = parseObject();
+ }
+ catch(IOException e)
+ {
+ /*
+ * PDF files may have random data after the EOF marker. Ignore errors if
+ * last object processed is EOF.
+ */
+ if( wasLastParsedObjectEOF )
{
break;
}
- try
+ if(isContinueOnError(e))
{
- wasLastParsedObjectEOF = parseObject();
+ /*
+ * Warning is sent to the PDFBox.log and to the Console that
+ * we skipped over an object
+ */
+ log.warn("Parsing Error, Skipping Object", e);
+ skipToNextObj();
}
- catch(IOException e)
- {
- if(isContinueOnError(e))
- {
- /*
- * Warning is sent to the PDFBox.log and to the Console that
- * we skipped over an object
- */
- log.warn("Parsing Error, Skipping Object", e);
- skipToNextObj();
- }
- else
- {
- throw e;
- }
+ else
+ {
+ throw e;
}
- skipSpaces();
- }
- //Test if we saw a trailer section. If not, look for an XRef Stream (Cross-Reference Stream)
- //to populate the trailer and xref information. For PDF 1.5 and above
- if( document.getTrailer() == null )
- {
- document.parseXrefStreams();
}
- if( !document.isEncrypted() )
- {
- document.dereferenceObjectStreams();
- }
- ConflictObj.resolveConflicts(document, conflictList);
+ skipSpaces();
}
- catch( IOException e )
+
+ // set xref to start with
+ xrefTrailerResolver.setStartxref( document.getStartXref() );
+
+ // get resolved xref table + trailer
+ document.setTrailer( xrefTrailerResolver.getTrailer() );
+ document.addXRefTable( xrefTrailerResolver.getXrefTable() );
+
+ if( !document.isEncrypted() )
{
- /*
- * PDF files may have random data after the EOF marker. Ignore errors if
- * last object processed is EOF.
- */
- if( !wasLastParsedObjectEOF )
- {
- throw e;
- }
+ document.dereferenceObjectStreams();
}
+ ConflictObj.resolveConflicts(document, conflictList);
}
catch( Throwable t )
{
@@ -447,7 +449,7 @@ public class PDFParser extends BaseParse
//xref table. Note: The contents of the Xref table are currently ignored
else if( peekedChar == 'x')
{
- parseXrefTable();
+ parseXrefTable( currentObjByteOffset );
}
// Note: startxref can occur in either a trailer section or by itself
else if (peekedChar == 't' || peekedChar == 's')
@@ -548,6 +550,15 @@ public class PDFParser extends BaseParse
if( pb instanceof COSDictionary )
{
pb = parseCOSStream( (COSDictionary)pb, getDocument().getScratchFile() );
+
+ // test for XRef type
+ final COSStream strmObj = (COSStream) pb;
+ final COSName objectType = (COSName)strmObj.getItem( COSName.TYPE );
+ if( objectType != null && objectType.equals( COSName.XREF ) )
+ {
+ // XRef stream
+ parseXrefStream( strmObj, currentObjByteOffset );
+ }
}
else
{
@@ -657,11 +668,11 @@ public class PDFParser extends BaseParse
/**
* This will parse the xref table from the stream and add it to the state
* The XrefTable contents are ignored.
- *
+ * @param startByteOffset the offset to start at
* @return false on parsing error
* @throws IOException If an IO error occurs.
*/
- private boolean parseXrefTable() throws IOException
+ private boolean parseXrefTable( int startByteOffset ) throws IOException
{
if(pdfSource.peek() != 'x')
{
@@ -672,6 +683,10 @@ public class PDFParser extends BaseParse
{
return false;
}
+
+ // signal start of new XRef
+ xrefTrailerResolver.nextXrefObj( startByteOffset );
+
/*
* Xref tables can have multiple sections.
* Each starts with a starting object id and a count.
@@ -708,7 +723,7 @@ public class PDFParser extends BaseParse
int currOffset = Integer.parseInt(splitString[0]);
int currGenID = Integer.parseInt(splitString[1]);
COSObjectKey objKey = new COSObjectKey(currObjID, currGenID);
- document.setXRef(objKey, currOffset);
+ xrefTrailerResolver.setXRef(objKey, currOffset);
}
catch(NumberFormatException e)
{
@@ -771,20 +786,29 @@ public class PDFParser extends BaseParse
skipSpaces();
COSDictionary parsedTrailer = parseCOSDictionary();
- COSDictionary docTrailer = document.getTrailer();
- if( docTrailer == null )
- {
- document.setTrailer( parsedTrailer );
- }
- else
- {
- docTrailer.addAll( parsedTrailer );
- }
+ xrefTrailerResolver.setTrailer( parsedTrailer );
+
skipSpaces();
return true;
}
/**
+ * Fills XRefTrailerResolver with data of given stream.
+ * Stream must be of type XRef.
+ * @param stream the stream to be read
+ * @param objByteOffset the offset to start at
+ * @throws IOException if there is an error parsing the stream
+ */
+ public void parseXrefStream( COSStream stream, int objByteOffset ) throws IOException
+ {
+ xrefTrailerResolver.nextXrefObj( objByteOffset );
+ xrefTrailerResolver.setTrailer( stream );
+ PDFXrefStreamParser parser =
+ new PDFXrefStreamParser( stream, document, forceParsing, xrefTrailerResolver );
+ parser.parse();
+ }
+
+ /**
* Used to resolve conflicts when a PDF Document has multiple objects with
* the same id number. Ideally, we could use the Xref table when parsing
* the document to be able to determine which of the objects with the same ID
@@ -820,12 +844,12 @@ public class PDFParser extends BaseParse
* table.
* @throws IOException
*/
- private static void resolveConflicts(COSDocument document, List conflictList) throws IOException
+ private static void resolveConflicts(COSDocument document, List<ConflictObj> conflictList) throws IOException
{
- Iterator conflicts = conflictList.iterator();
+ Iterator<ConflictObj> conflicts = conflictList.iterator();
while(conflicts.hasNext())
{
- ConflictObj o = (ConflictObj)conflicts.next();
+ ConflictObj o = conflicts.next();
Integer offset = new Integer(o.offset);
if(document.getXrefTable().containsValue(offset))
{
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFXrefStreamParser.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFXrefStreamParser.java?rev=1138995&r1=1138994&r2=1138995&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFXrefStreamParser.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/PDFXrefStreamParser.java Thu Jun 23 17:07:30 2011
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDocument;
import org.apache.pdfbox.cos.COSInteger;
import org.apache.pdfbox.cos.COSName;
@@ -38,36 +39,28 @@ import org.apache.pdfbox.persistence.uti
public class PDFXrefStreamParser extends BaseParser
{
private COSStream stream;
+ private XrefTrailerResolver xrefTrailerResolver;
/**
* Constructor.
*
- * @since Apache PDFBox 1.3.0
+ * @since 1.3.0
* @param strm The stream to parse.
* @param doc The document for the current parsing.
* @param forceParcing flag to skip malformed or otherwise unparseable
* input where possible
+ * @param xrefTrailerResolver resolver to read the xref/trailer information
+ *
* @throws IOException If there is an error initializing the stream.
*/
public PDFXrefStreamParser(
- COSStream strm, COSDocument doc, boolean forceParsing)
+ COSStream strm, COSDocument doc, boolean forceParsing,
+ XrefTrailerResolver xrefTrailerResolver )
throws IOException {
super(strm.getUnfilteredStream(), forceParsing);
setDocument(doc);
stream = strm;
- }
-
- /**
- * Constructor.
- *
- * @param strm The stream to parse.
- * @param doc The document for the current parsing.
- *
- * @throws IOException If there is an error initializing the stream.
- */
- public PDFXrefStreamParser(COSStream strm, COSDocument doc)
- throws IOException {
- this(strm, doc, false);
+ this.xrefTrailerResolver = xrefTrailerResolver;
}
/**
@@ -90,12 +83,12 @@ public class PDFXrefStreamParser extends
indexArray.add(stream.getDictionaryObject(COSName.SIZE));
}
- ArrayList objNums = new ArrayList();
+ ArrayList<Integer> objNums = new ArrayList<Integer>();
/*
* Populates objNums with all object numbers available
*/
- Iterator indexIter = indexArray.iterator();
+ Iterator<COSBase> indexIter = indexArray.iterator();
while(indexIter.hasNext())
{
int objID = ((COSInteger)indexIter.next()).intValue();
@@ -105,7 +98,7 @@ public class PDFXrefStreamParser extends
objNums.add(new Integer(objID + i));
}
}
- Iterator objIter = objNums.iterator();
+ Iterator<Integer> objIter = objNums.iterator();
/*
* Calculating the size of the line in bytes
*/
@@ -152,7 +145,7 @@ public class PDFXrefStreamParser extends
genNum += (currLine[i + w0 + w1] & 0x00ff) << ((w2 - i - 1) * 8);
}
COSObjectKey objKey = new COSObjectKey(objID.intValue(), genNum);
- document.setXRef(objKey, offset);
+ xrefTrailerResolver.setXRef(objKey, offset);
break;
case 2:
/*
Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java?rev=1138995&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/XrefTrailerResolver.java Thu Jun 23 17:07:30 2011
@@ -0,0 +1,211 @@
+/*
+ * 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.pdfparser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.persistence.util.COSObjectKey;
+
+/**
+ * This class will collect all XRef/trailer objects and creates correct
+ * xref/trailer information after all objects are read using startxref
+ * and 'Prev' information (unused XRef/trailer objects are discarded).
+ *
+ * In case of missing startxref or wrong startxref pointer all
+ * XRef/trailer objects are used to create xref table / trailer dictionary
+ * in order they occur.
+ *
+ * For each new xref object/XRef stream method {@link #nextXrefObj(int)}
+ * must be called with start byte position. All following calls to
+ * {@link #setXRef(COSObjectKey, int)} or {@link #setTrailer(COSDictionary)}
+ * will add the data for this byte position.
+ *
+ * After all objects are parsed the startxref position must be provided
+ * using {@link #setStartxref(int)}. This is used to build the chain of
+ * active xref/trailer objects used for creating document trailer and xref table.
+ *
+ * @author Timo Böhme (timo.boehme at ontochem.com)
+ */
+public class XrefTrailerResolver
+{
+
+ /**
+ * A class which represents a xref/trailer object
+ *
+ */
+ class XrefTrailerObj
+ {
+ private COSDictionary trailer = null;
+ private final Map<COSObjectKey, Integer> xrefTable = new HashMap<COSObjectKey, Integer>();
+ }
+
+ private final Map<Integer, XrefTrailerObj> bytePosToXrefMap = new HashMap<Integer, XrefTrailerObj>();
+ private XrefTrailerObj curXrefTrailerObj = null;
+ private XrefTrailerObj resolvedXrefTrailer = null;
+
+ /** Log instance. */
+ private static final Log log = LogFactory.getLog( XrefTrailerResolver.class );
+
+ /**
+ * Signals that a new XRef object (table or stream) starts.
+ * @param startBytePos the offset to start at
+ *
+ */
+ public void nextXrefObj( final int startBytePos )
+ {
+ bytePosToXrefMap.put( startBytePos, curXrefTrailerObj = new XrefTrailerObj() );
+ }
+
+ /**
+ * Populate XRef HashMap of current XRef object.
+ * Will add an Xreftable entry that maps ObjectKeys to byte offsets in the file.
+ * @param objKey The objkey, with id and gen numbers
+ * @param offset The byte offset in this file
+ */
+ public void setXRef( COSObjectKey objKey, int offset )
+ {
+ if ( curXrefTrailerObj == null )
+ {
+ // should not happen...
+ log.warn( "Cannot add XRef entry for '" + objKey.getNumber() + "' because XRef start was not signalled." );
+ return;
+ }
+ curXrefTrailerObj.xrefTable.put( objKey, offset );
+ }
+
+ /**
+ * Adds trailer information for current XRef object.
+ *
+ * @param trailer the current document trailer dictionary
+ */
+ public void setTrailer( COSDictionary trailer )
+ {
+ if ( curXrefTrailerObj == null )
+ {
+ // should not happen...
+ log.warn( "Cannot add trailer because XRef start was not signalled." );
+ return;
+ }
+ curXrefTrailerObj.trailer = trailer;
+ }
+
+ /**
+ * Sets the byte position of the first XRef
+ * (has to be called after very last startxref was read).
+ * This is used to resolve chain of active XRef/trailer.
+ *
+ * In case startxref position is not found we output a
+ * warning and use all XRef/trailer objects combined
+ * in byte position order.
+ * Thus for incomplete PDF documents with missing
+ * startxref one could call this method with parameter value -1.
+ */
+ public void setStartxref( int startxrefBytePos )
+ {
+ if ( resolvedXrefTrailer != null )
+ {
+ log.warn( "Method must be called only ones with last startxref value." );
+ return;
+ }
+
+ resolvedXrefTrailer = new XrefTrailerObj();
+ resolvedXrefTrailer.trailer = new COSDictionary();
+
+ XrefTrailerObj curObj = bytePosToXrefMap.get( startxrefBytePos );
+ List<Integer> xrefSeqBytePos = new ArrayList<Integer>();
+
+ if ( curObj == null )
+ {
+ // no XRef at given position
+ log.warn( "Did not found XRef object at specified startxref position " + startxrefBytePos );
+
+ // use all objects in byte position order (last entries overwrite previous ones)
+ xrefSeqBytePos.addAll( bytePosToXrefMap.keySet() );
+ Collections.sort( xrefSeqBytePos );
+ }
+ else
+ {
+ // found starting Xref object
+ // add this and follow chain defined by 'Prev' keys
+ xrefSeqBytePos.add( startxrefBytePos );
+ while ( curObj.trailer != null )
+ {
+ int prevBytePos = curObj.trailer.getInt( COSName.PREV, -1 );
+ if ( prevBytePos == -1 )
+ {
+ break;
+ }
+
+ curObj = bytePosToXrefMap.get( prevBytePos );
+ if ( curObj == null )
+ {
+ log.warn( "Did not found XRef object pointed to by 'Prev' key at position " + prevBytePos );
+ break;
+ }
+ xrefSeqBytePos.add( prevBytePos );
+
+ // sanity check to prevent infinite loops
+ if ( xrefSeqBytePos.size() >= bytePosToXrefMap.size() )
+ {
+ break;
+ }
+ }
+ // have to reverse order so that later XRefs will overwrite previous ones
+ Collections.reverse( xrefSeqBytePos );
+ }
+
+ // merge used and sorted XRef/trailer
+ for ( Integer bPos : xrefSeqBytePos )
+ {
+ curObj = bytePosToXrefMap.get( bPos );
+ if ( curObj.trailer != null )
+ {
+ resolvedXrefTrailer.trailer.addAll( curObj.trailer );
+ }
+ resolvedXrefTrailer.xrefTable.putAll( curObj.xrefTable );
+ }
+
+ }
+
+ /**
+ * Gets the resolved trailer. Might return <code>null</code> in case
+ * {@link #setStartxref(int)} was not called before.
+ *
+ */
+ public COSDictionary getTrailer()
+ {
+ return ( resolvedXrefTrailer == null ) ? null : resolvedXrefTrailer.trailer;
+ }
+
+ /**
+ * Gets the resolved xref table. Might return <code>null</code> in case
+ * {@link #setStartxref(int)} was not called before.
+ *
+ */
+ public Map<COSObjectKey, Integer> getXrefTable()
+ {
+ return ( resolvedXrefTrailer == null ) ? null : resolvedXrefTrailer.xrefTable;
+ }
+}