/**
* Copyright (c) 2003, www.pdfbox.org
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of pdfbox; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://www.pdfbox.org
*
*/
package org.pdfbox;
import org.apache.log4j.Logger;
import org.pdfbox.cos.COSArray;
import org.pdfbox.cos.COSBase;
import org.pdfbox.cos.COSDictionary;
import org.pdfbox.cos.COSName;
import org.pdfbox.cos.COSStream;
import org.pdfbox.cos.COSInteger;
import org.pdfbox.exceptions.COSVisitorException;
import org.pdfbox.pdfparser.PDFParser;
import org.pdfbox.pdfwriter.COSWriter;
import org.pdfbox.pdmodel.PDDocument;
import org.pdfbox.pdmodel.PDDocumentCatalog;
import org.pdfbox.pdmodel.PDPage;
import org.pdfbox.pdmodel.PDResources;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import java.util.Map;
/**
* Overlay on document with another one.
* e.g. Overlay an invoice with your company layout
*
* How it (should) work:
* If the document has 10 pages, and the layout 2 the following is the result:
*
* Document: 1234567890
* Layout : 1212121212
*
*
*
* @author Mario Ivankovits (mario@ops.co.at)
* @author Ben Litchfield (ben@csh.rit.edu)
*
* @version $Revision: 1.4 $
*/
public class Overlay
{
/**
* COSName constant.
*/
public static final COSName XOBJECT = COSName.getPDFName("XObject");
/**
* COSName constant.
*/
public static final COSName PROC_SET = COSName.getPDFName("ProcSet");
/**
* COSName constant.
*/
public static final COSName EXT_G_STATE = COSName.getPDFName("ExtGState" );
private static Logger log = Logger.getLogger(Overlay.class);
private List layoutPages = new ArrayList(10);
private PDDocument pdfOverlay;
private PDDocument pdfDocument;
private int pageCount = 0;
private COSStream saveGraphicsStateStream;
private COSStream restoreGraphicsStateStream;
/**
* This will overlay a document and write out the results.
*
* usage: java org.pdfbox.Overlay <overlay.pdf> <document.pdf> <result.pdf>
*
* @param args The command line arguments.
*
* @throws IOException If there is an error reading/writing the document.
* @throws COSVisitorException If there is an error writing the document.
*/
public static void main( String[] args ) throws IOException, COSVisitorException
{
if( args.length != 3 )
{
usage();
System.exit(1);
}
else
{
PDDocument overlay = null;
PDDocument pdf = null;
try
{
overlay = getDocument( args[0] );
pdf = getDocument( args[1] );
Overlay overlayer = new Overlay();
overlayer.overlay( overlay, pdf );
writeDocument( pdf, args[2] );
}
finally
{
if( overlay != null )
{
overlay.close();
}
if( pdf != null )
{
pdf.close();
}
}
}
}
private static void writeDocument( PDDocument pdf, String filename ) throws IOException, COSVisitorException
{
FileOutputStream output = null;
COSWriter writer = null;
try
{
output = new FileOutputStream( filename );
writer = new COSWriter( output );
writer.write( pdf );
}
finally
{
if( writer != null )
{
writer.close();
}
if( output != null )
{
output.close();
}
}
}
private static PDDocument getDocument( String filename ) throws IOException
{
FileInputStream input = null;
PDFParser parser = null;
PDDocument result = null;
try
{
input = new FileInputStream( filename );
parser = new PDFParser( input );
parser.parse();
result = parser.getPDDocument();
}
finally
{
if( input != null )
{
input.close();
}
}
return result;
}
private static void usage()
{
System.err.println( "usage: java " + Overlay.class.getName() + " " );
}
/**
* Private class.
*/
private static class LayoutPage
{
private final COSBase contents;
private final COSDictionary res;
private final Map objectNameMap;
/**
* Constructor.
*
* @param contentsValue The contents.
* @param resValue The resource dictionary
* @param objectNameMapValue The map
*/
public LayoutPage(COSBase contentsValue, COSDictionary resValue, Map objectNameMapValue)
{
contents = contentsValue;
res = resValue;
objectNameMap = objectNameMapValue;
}
}
/**
* This will overlay two documents onto each other. The overlay document is
* repeatedly overlayed onto the destination document for every page in the
* destination.
*
* @param overlay The document to copy onto the destination
* @param destination The file that the overlay should be placed on.
*
* @return The destination pdf, same as argument passed in.
*
* @throws IOException If there is an error accessing data.
*/
public PDDocument overlay( PDDocument overlay, PDDocument destination ) throws IOException
{
pdfOverlay = overlay;
pdfDocument = destination;
PDDocumentCatalog overlayCatalog = pdfOverlay.getDocumentCatalog();
collectLayoutPages( overlayCatalog.getAllPages() );
COSDictionary saveGraphicsStateDic = new COSDictionary();
saveGraphicsStateStream = new COSStream( saveGraphicsStateDic, pdfDocument.getDocument().getScratchFile() );
OutputStream saveStream = saveGraphicsStateStream.createUnfilteredStream();
saveStream.write( " q\n".getBytes() );
saveStream.flush();
restoreGraphicsStateStream = new COSStream( saveGraphicsStateDic, pdfDocument.getDocument().getScratchFile() );
OutputStream restoreStream = restoreGraphicsStateStream.createUnfilteredStream();
restoreStream.write( " Q\n".getBytes() );
restoreStream.flush();
PDDocumentCatalog pdfCatalog = pdfDocument.getDocumentCatalog();
processPages( pdfCatalog.getAllPages() );
return pdfDocument;
}
private void collectLayoutPages( List pages) throws IOException
{
Iterator pagesIter = pages.iterator();
while( pagesIter.hasNext() )
{
PDPage page = (PDPage)pagesIter.next();
COSBase contents = page.getCOSDictionary().getDictionaryObject( COSName.CONTENTS );
PDResources resources = page.findResources();
if( resources == null )
{
resources = new PDResources();
page.setResources( resources );
}
COSDictionary res = resources.getCOSDictionary();
if( contents instanceof COSStream )
{
COSStream stream = (COSStream) contents;
Map objectNameMap = new TreeMap();
stream = makeUniqObjectNames(objectNameMap, stream);
layoutPages.add(new LayoutPage(stream, res, objectNameMap));
}
else if( contents instanceof COSArray )
{
throw new UnsupportedOperationException("Layout pages with COSArray currently not supported.");
// layoutPages.add(new LayoutPage(contents, res));
}
else
{
throw new IOException( "Contents are unknown type:" + contents.getClass().getName() );
}
}
}
private COSStream makeUniqObjectNames(Map objectNameMap, COSStream stream) throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream(10240);
byte[] buf = new byte[10240];
int read;
InputStream is = stream.getUnfilteredStream();
while ((read = is.read(buf)) > -1)
{
baos.write(buf, 0, read);
}
buf = baos.toByteArray();
baos = new ByteArrayOutputStream(buf.length + 100);
StringBuffer sbObjectName = new StringBuffer(10);
boolean bInObjectIdent = false;
boolean bInText = false;
boolean bInEscape = false;
for (int i = 0; i
//
//
//
array.add(0, saveGraphicsStateStream );
array.add( restoreGraphicsStateStream );
array.add(layoutPage.contents);
}
/**
* merges two dictionaries.
*
* @param dest
* @param source
*/
private void mergeDictionary(COSName name, COSDictionary dest, COSDictionary source, Map objectNameMap)
{
COSDictionary destDict = (COSDictionary) dest.getDictionaryObject(name);
COSDictionary sourceDict = (COSDictionary) source.getDictionaryObject(name);
if (destDict == null)
{
destDict = new COSDictionary();
dest.setItem(name, destDict);
}
if( sourceDict != null )
{
Iterator iterKeys = sourceDict.keyList().iterator();
while (iterKeys.hasNext())
{
COSName key = (COSName) iterKeys.next();
COSName mappedKey = (COSName) objectNameMap.get(key.getName());
if (mappedKey == null)
{
// object not needet
if (log.isDebugEnabled())
{
log.debug("MergeDict " + name.getName() +
": Object " + key.getName() + " not used by content. Discarded.");
}
continue;
}
destDict.setItem(mappedKey, sourceDict.getItem(key));
}
}
}
/**
* merges two arrays.
*
* @param dest
* @param source
*/
private void mergeArray(COSName name, COSDictionary dest, COSDictionary source)
{
COSArray destDict = (COSArray) dest.getDictionaryObject(name);
COSArray sourceDict = (COSArray) source.getDictionaryObject(name);
if (destDict == null)
{
destDict = new COSArray();
dest.setItem(name, destDict);
}
for (int sourceDictIdx = 0; sourceDict != null && sourceDictIdx