package com.tom_roush.pdfbox.pdfparser;
import com.tom_roush.pdfbox.cos.COSArray;
import com.tom_roush.pdfbox.cos.COSBase;
import com.tom_roush.pdfbox.cos.COSDictionary;
import com.tom_roush.pdfbox.cos.COSInteger;
import com.tom_roush.pdfbox.cos.COSName;
import com.tom_roush.pdfbox.cos.COSObject;
import com.tom_roush.pdfbox.cos.COSStream;
import com.tom_roush.pdfbox.pdfwriter.COSWriterXRefEntry;
import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* @author Alexander Funk
*/
public class PDFXRefStream implements PDFXRef
{
private static final int ENTRY_OBJSTREAM = 2;
private static final int ENTRY_NORMAL = 1;
private static final int ENTRY_FREE = 0;
private final Map<Long, Object> streamData;
private final Set<Long> objectNumbers;
private final COSStream stream;
private long size = -1;
/**
* Create a fresh XRef stream like for a fresh file or an incremental update.
*/
public PDFXRefStream()
{
this.stream = new COSStream();
streamData = new TreeMap<Long, Object>();
objectNumbers = new TreeSet<Long>();
}
/**
* Returns the stream of the XRef.
* @return the XRef stream
* @throws IOException if something went wrong
*/
public COSStream getStream() throws IOException
{
stream.setItem(COSName.TYPE, COSName.XREF);
if (size == -1)
{
throw new IllegalArgumentException("size is not set in xrefstream");
}
stream.setLong(COSName.SIZE, getSizeEntry());
List<Long> indexEntry = getIndexEntry();
COSArray indexAsArray = new COSArray();
for (Long i : indexEntry)
{
indexAsArray.add(COSInteger.get(i));
}
stream.setItem(COSName.INDEX, indexAsArray);
int[] wEntry = getWEntry();
COSArray wAsArray = new COSArray();
for (int i = 0; i < wEntry.length; i++)
{
int j = wEntry[i];
wAsArray.add(COSInteger.get(j));
}
stream.setItem(COSName.W, wAsArray);
OutputStream outputStream = this.stream.createOutputStream(COSName.FLATE_DECODE);
writeStreamData(outputStream, wEntry);
outputStream.flush();
outputStream.close();
Set<COSName> keySet = this.stream.keySet();
for ( COSName cosName : keySet )
{
COSBase dictionaryObject = this.stream.getDictionaryObject(cosName);
dictionaryObject.setDirect(true);
}
return this.stream;
}
/**
* Copy all Trailer Information to this file.
*
* @param trailerDict dictionary to be added as trailer info
*/
public void addTrailerInfo(COSDictionary trailerDict)
{
Set<Entry<COSName, COSBase>> entrySet = trailerDict.entrySet();
for ( Entry<COSName, COSBase> entry : entrySet )
{
COSName key = entry.getKey();
if (COSName.INFO.equals(key) || COSName.ROOT.equals(key) || COSName.ENCRYPT.equals(key)
|| COSName.ID.equals(key) || COSName.PREV.equals(key))
{
stream.setItem(key, entry.getValue());
}
}
}
/**
* Add an new entry to the XRef stream.
*
* @param entry new entry to be added
*/
public void addEntry(COSWriterXRefEntry entry)
{
objectNumbers.add(entry.getKey().getNumber());
if (entry.isFree())
{
// what would be a f-Entry in the xref table
FreeReference value = new FreeReference();
value.nextGenNumber = entry.getKey().getGeneration();
value.nextFree = entry.getKey().getNumber();
streamData.put(value.nextFree, value);
}
else
{
// we don't care for ObjectStreamReferences for now and only handle
// normal references that would be f-Entrys in the xref table.
NormalReference value = new NormalReference();
value.genNumber = entry.getKey().getGeneration();
value.offset = entry.getOffset();
streamData.put(entry.getKey().getNumber(), value);
}
}
/**
* determines the minimal length required for all the lengths.
*
* @return the length information
*/
private int[] getWEntry()
{
long[] wMax = new long[3];
for ( Object entry : streamData.values() )
{
if (entry instanceof FreeReference)
{
FreeReference free = (FreeReference)entry;
wMax[0] = Math.max(wMax[0], ENTRY_FREE); // the type field for a free reference
wMax[1] = Math.max(wMax[1], free.nextFree);
wMax[2] = Math.max(wMax[2], free.nextGenNumber);
}
else if (entry instanceof NormalReference)
{
NormalReference ref = (NormalReference)entry;
wMax[0] = Math.max(wMax[0], ENTRY_NORMAL); // the type field for a normal reference
wMax[1] = Math.max(wMax[1], ref.offset);
wMax[2] = Math.max(wMax[2], ref.genNumber);
}
else if (entry instanceof ObjectStreamReference)
{
ObjectStreamReference objStream = (ObjectStreamReference)entry;
wMax[0] = Math.max(wMax[0], ENTRY_OBJSTREAM); // the type field for a objstm reference
wMax[1] = Math.max(wMax[1], objStream.offset);
wMax[2] = Math.max(wMax[2], objStream.objectNumberOfObjectStream);
}
// TODO add here if new standard versions define new types
else
{
throw new RuntimeException("unexpected reference type");
}
}
// find the max bytes needed to display that column
int[] w = new int[3];
for ( int i = 0; i < w.length; i++ )
{
while (wMax[i] > 0)
{
w[i]++;
wMax[i] >>= 8;
}
}
return w;
}
private long getSizeEntry()
{
return size;
}
/**
* Set the size of the XRef stream.
*
* @param streamSize size to bet set as stream size
*/
public void setSize(long streamSize)
{
this.size = streamSize;
}
private List<Long> getIndexEntry()
{
LinkedList<Long> linkedList = new LinkedList<Long>();
Long first = null;
Long length = null;
for ( Long objNumber : objectNumbers )
{
if (first == null)
{
first = objNumber;
length = 1L;
}
if (first + length == objNumber)
{
length += 1;
}
if (first + length < objNumber)
{
linkedList.add(first);
linkedList.add(length);
first = objNumber;
length = 1L;
}
}
linkedList.add(first);
linkedList.add(length);
return linkedList;
}
private void writeNumber(OutputStream os, long number, int bytes) throws IOException
{
byte[] buffer = new byte[bytes];
for ( int i = 0; i < bytes; i++ )
{
buffer[i] = (byte)(number & 0xff);
number >>= 8;
}
for ( int i = 0; i < bytes; i++ )
{
os.write(buffer[bytes-i-1]);
}
}
private void writeStreamData(OutputStream os, int[] w) throws IOException
{
// iterate over all streamData and write it in the required format
for ( Object entry : streamData.values() )
{
if (entry instanceof FreeReference)
{
FreeReference free = (FreeReference)entry;
writeNumber(os, ENTRY_FREE, w[0]);
writeNumber(os, free.nextFree, w[1]);
writeNumber(os, free.nextGenNumber, w[2]);
}
else if (entry instanceof NormalReference)
{
NormalReference ref = (NormalReference)entry;
writeNumber(os, ENTRY_NORMAL, w[0]);
writeNumber(os, ref.offset, w[1]);
writeNumber(os, ref.genNumber, w[2]);
}
else if (entry instanceof ObjectStreamReference)
{
ObjectStreamReference objStream = (ObjectStreamReference)entry;
writeNumber(os, ENTRY_OBJSTREAM, w[0]);
writeNumber(os, objStream.offset, w[1]);
writeNumber(os, objStream.objectNumberOfObjectStream, w[2]);
}
// TODO add here if new standard versions define new types
else
{
throw new RuntimeException("unexpected reference type");
}
}
}
/**
* A class representing an object stream reference.
*
*/
class ObjectStreamReference
{
long objectNumberOfObjectStream;
long offset;
}
/**
* A class representing a normal reference.
*
*/
class NormalReference
{
int genNumber;
long offset;
}
/**
* A class representing a free reference.
*
*/
class FreeReference
{
int nextGenNumber;
long nextFree;
}
/**
* {@inheritDoc}
*/
public COSObject getObject(int objectNumber)
{
return null;
}
}