package com.tom_roush.pdfbox.multipdf;
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.COSName;
import com.tom_roush.pdfbox.cos.COSObject;
import com.tom_roush.pdfbox.cos.COSStream;
import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.pdmodel.PDPage;
import com.tom_roush.pdfbox.pdmodel.PDResources;
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle;
import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject;
import com.tom_roush.pdfbox.util.awt.AffineTransform;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Adds an overlay to an existing PDF document.
*
* Based on code contributed by Balazs Jerk.
*
*/
public class Overlay
{
/**
* Possible location of the overlayed pages: foreground or background.
*/
public enum Position
{
FOREGROUND, BACKGROUND
}
private LayoutPage defaultOverlayPage;
private LayoutPage firstPageOverlayPage;
private LayoutPage lastPageOverlayPage;
private LayoutPage oddPageOverlayPage;
private LayoutPage evenPageOverlayPage;
private final Map<Integer, PDDocument> specificPageOverlay = new HashMap<Integer, PDDocument>();
private Map<Integer, LayoutPage> specificPageOverlayPage = new HashMap<Integer, LayoutPage>();
private Position position = Position.BACKGROUND;
private String inputFileName = null;
private PDDocument inputPDFDocument = null;
private String outputFilename = null;
private String defaultOverlayFilename = null;
private PDDocument defaultOverlay = null;
private String firstPageOverlayFilename = null;
private PDDocument firstPageOverlay = null;
private String lastPageOverlayFilename = null;
private PDDocument lastPageOverlay = null;
private String allPagesOverlayFilename = null;
private PDDocument allPagesOverlay = null;
private String oddPageOverlayFilename = null;
private PDDocument oddPageOverlay = null;
private String evenPageOverlayFilename = null;
private PDDocument evenPageOverlay = null;
private int numberOfOverlayPages = 0;
private boolean useAllOverlayPages = false;
/**
* This will add overlays to a documents.
*
* @param specificPageOverlayFile map of overlay files for specific pages
* @throws IOException if something went wrong
*/
public void overlay(Map<Integer, String> specificPageOverlayFile)
throws IOException
{
try
{
loadPDFs();
for (Map.Entry<Integer, String> e : specificPageOverlayFile.entrySet())
{
PDDocument doc = loadPDF(e.getValue());
specificPageOverlay.put(e.getKey(), doc);
specificPageOverlayPage.put(e.getKey(), getLayoutPage(doc));
}
processPages(inputPDFDocument);
inputPDFDocument.save(outputFilename);
}
finally
{
if (inputPDFDocument != null)
{
inputPDFDocument.close();
}
if (defaultOverlay != null)
{
defaultOverlay.close();
}
if (firstPageOverlay != null)
{
firstPageOverlay.close();
}
if (lastPageOverlay != null)
{
lastPageOverlay.close();
}
if (allPagesOverlay != null)
{
allPagesOverlay.close();
}
if (oddPageOverlay != null)
{
oddPageOverlay.close();
}
if (evenPageOverlay != null)
{
evenPageOverlay.close();
}
for (Map.Entry<Integer, PDDocument> e : specificPageOverlay.entrySet())
{
e.getValue().close();
}
specificPageOverlay.clear();
specificPageOverlayPage.clear();
}
}
private void loadPDFs() throws IOException
{
// input PDF
if (inputFileName != null)
{
inputPDFDocument = loadPDF(inputFileName);
}
// default overlay PDF
if (defaultOverlayFilename != null)
{
defaultOverlay = loadPDF(defaultOverlayFilename);
}
if (defaultOverlay != null)
{
defaultOverlayPage = getLayoutPage(defaultOverlay);
}
// first page overlay PDF
if (firstPageOverlayFilename != null)
{
firstPageOverlay = loadPDF(firstPageOverlayFilename);
}
if (firstPageOverlay != null)
{
firstPageOverlayPage = getLayoutPage(firstPageOverlay);
}
// last page overlay PDF
if (lastPageOverlayFilename != null)
{
lastPageOverlay = loadPDF(lastPageOverlayFilename);
}
if (lastPageOverlay != null)
{
lastPageOverlayPage = getLayoutPage(lastPageOverlay);
}
// odd pages overlay PDF
if (oddPageOverlayFilename != null)
{
oddPageOverlay = loadPDF(oddPageOverlayFilename);
}
if (oddPageOverlay != null)
{
oddPageOverlayPage = getLayoutPage(oddPageOverlay);
}
// even pages overlay PDF
if (evenPageOverlayFilename != null)
{
evenPageOverlay = loadPDF(evenPageOverlayFilename);
}
if (evenPageOverlay != null)
{
evenPageOverlayPage = getLayoutPage(evenPageOverlay);
}
// all pages overlay PDF
if (allPagesOverlayFilename != null)
{
allPagesOverlay = loadPDF(allPagesOverlayFilename);
}
if (allPagesOverlay != null)
{
specificPageOverlayPage = getLayoutPages(allPagesOverlay);
useAllOverlayPages = true;
numberOfOverlayPages = specificPageOverlayPage.size();
}
}
private PDDocument loadPDF(String pdfName) throws IOException
{
return PDDocument.load(new File(pdfName));
}
/**
* Stores the overlay page information.
*/
private static final class LayoutPage
{
private final PDRectangle overlayMediaBox;
private final COSStream overlayContentStream;
private final COSDictionary overlayResources;
private LayoutPage(PDRectangle mediaBox, COSStream contentStream, COSDictionary resources)
{
overlayMediaBox = mediaBox;
overlayContentStream = contentStream;
overlayResources = resources;
}
}
private LayoutPage getLayoutPage(PDDocument doc) throws IOException
{
PDPage page = doc.getPage(0);
COSBase contents = page.getCOSObject().getDictionaryObject(COSName.CONTENTS);
PDResources resources = page.getResources();
if (resources == null)
{
resources = new PDResources();
}
return new LayoutPage(page.getMediaBox(), createContentStream(contents),
resources.getCOSObject());
}
private Map<Integer,LayoutPage> getLayoutPages(PDDocument doc) throws IOException
{
int numberOfPages = doc.getNumberOfPages();
Map<Integer,LayoutPage> layoutPages = new HashMap<Integer, Overlay.LayoutPage>(numberOfPages);
for (int i=0;i<numberOfPages;i++)
{
PDPage page = doc.getPage(i);
COSBase contents = page.getCOSObject().getDictionaryObject(COSName.CONTENTS);
PDResources resources = page.getResources();
if (resources == null)
{
resources = new PDResources();
}
layoutPages.put(i,new LayoutPage(page.getMediaBox(), createContentStream(contents),
resources.getCOSObject()));
}
return layoutPages;
}
private COSStream createContentStream(COSBase contents) throws IOException
{
List<COSStream> contentStreams = createContentStreamList(contents);
// concatenate streams
COSStream concatStream = new COSStream();
OutputStream out = concatStream.createOutputStream(COSName.FLATE_DECODE);
for (COSStream contentStream : contentStreams)
{
InputStream in = contentStream.getUnfilteredStream();
byte[] buf = new byte[2048];
int n;
while ((n = in.read(buf)) > 0)
{
out.write(buf, 0, n);
}
out.flush();
}
out.close();
return concatStream;
}
private List<COSStream> createContentStreamList(COSBase contents) throws IOException
{
List<COSStream> contentStreams = new ArrayList<COSStream>();
if (contents instanceof COSStream)
{
contentStreams.add((COSStream) contents);
}
else if (contents instanceof COSArray)
{
for (COSBase item : (COSArray) contents)
{
contentStreams.addAll(createContentStreamList(item));
}
}
else if (contents instanceof COSObject)
{
contentStreams.addAll(createContentStreamList(((COSObject) contents).getObject()));
}
else
{
throw new IOException("Contents are unknown type:" + contents.getClass().getName());
}
return contentStreams;
}
private void processPages(PDDocument document) throws IOException
{
int pageCount = 0;
for (PDPage page : document.getPages())
{
COSDictionary pageDictionary = page.getCOSObject();
COSBase contents = pageDictionary.getDictionaryObject(COSName.CONTENTS);
COSArray contentArray = new COSArray();
switch (position)
{
case FOREGROUND:
// save state
contentArray.add(createStream("q\n"));
addOriginalContent(contents, contentArray);
// restore state
contentArray.add(createStream("Q\n"));
// overlay content
overlayPage(contentArray, page, pageCount + 1, document.getNumberOfPages());
break;
case BACKGROUND:
// overlay content
overlayPage(contentArray, page, pageCount + 1, document.getNumberOfPages());
addOriginalContent(contents, contentArray);
break;
default:
throw new IOException("Unknown type of position:" + position);
}
pageDictionary.setItem(COSName.CONTENTS, contentArray);
pageCount++;
}
}
private void addOriginalContent(COSBase contents, COSArray contentArray) throws IOException
{
if (contents instanceof COSStream)
{
contentArray.add(contents);
}
else if (contents instanceof COSArray)
{
contentArray.addAll((COSArray) contents);
}
else
{
throw new IOException("Unknown content type:" + contents.getClass().getName());
}
}
private void overlayPage(COSArray array, PDPage page, int pageNumber, int numberOfPages)
throws IOException
{
LayoutPage layoutPage = null;
if (!useAllOverlayPages && specificPageOverlayPage.containsKey(pageNumber))
{
layoutPage = specificPageOverlayPage.get(pageNumber);
}
else if ((pageNumber == 1) && (firstPageOverlayPage != null))
{
layoutPage = firstPageOverlayPage;
}
else if ((pageNumber == numberOfPages) && (lastPageOverlayPage != null))
{
layoutPage = lastPageOverlayPage;
}
else if ((pageNumber % 2 == 1) && (oddPageOverlayPage != null))
{
layoutPage = oddPageOverlayPage;
}
else if ((pageNumber % 2 == 0) && (evenPageOverlayPage != null))
{
layoutPage = evenPageOverlayPage;
}
else if (defaultOverlayPage != null)
{
layoutPage = defaultOverlayPage;
}
else if (useAllOverlayPages)
{
int usePageNum = (pageNumber -1 ) % numberOfOverlayPages;
layoutPage = specificPageOverlayPage.get(usePageNum);
}
if (layoutPage != null)
{
PDResources resources = page.getResources();
if (resources == null)
{
resources = new PDResources();
page.setResources(resources);
}
COSName xObjectId = createOverlayXObject(page, layoutPage,
layoutPage.overlayContentStream);
array.add(createOverlayStream(page, layoutPage, xObjectId));
}
}
private COSName createOverlayXObject(PDPage page, LayoutPage layoutPage, COSStream contentStream)
{
PDFormXObject xobjForm = new PDFormXObject(contentStream);
xobjForm.setResources(new PDResources(layoutPage.overlayResources));
xobjForm.setFormType(1);
xobjForm.setBBox( layoutPage.overlayMediaBox.createRetranslatedRectangle());
xobjForm.setMatrix(new AffineTransform());
PDResources resources = page.getResources();
return resources.add(xobjForm, "OL");
}
private COSStream createOverlayStream(PDPage page, LayoutPage layoutPage, COSName xObjectId)
throws IOException
{
// create a new content stream that executes the XObject content
PDRectangle pageMediaBox = page.getMediaBox();
float hShift = (pageMediaBox.getWidth() - layoutPage.overlayMediaBox.getWidth()) / 2.0f;
float vShift = (pageMediaBox.getHeight() - layoutPage.overlayMediaBox.getHeight()) / 2.0f;
StringBuilder overlayStream = new StringBuilder();
overlayStream.append("q\nq 1 0 0 1 ");
overlayStream.append(float2String(hShift));
overlayStream.append(" ");
overlayStream.append(float2String(vShift) );
overlayStream.append(" cm /");
overlayStream.append(xObjectId.getName());
overlayStream.append(" Do Q\nQ\n");
return createStream(overlayStream.toString());
}
private String float2String(float floatValue)
{
// use a BigDecimal as intermediate state to avoid
// a floating point string representation of the float value
BigDecimal value = new BigDecimal(String.valueOf(floatValue));
String stringValue = value.toPlainString();
// remove fraction digit "0" only
if (stringValue.indexOf('.') > -1 && !stringValue.endsWith(".0"))
{
while (stringValue.endsWith("0") && !stringValue.endsWith(".0"))
{
stringValue = stringValue.substring(0,stringValue.length()-1);
}
}
return stringValue;
}
private COSStream createStream(String content) throws IOException
{
COSStream stream = new COSStream();
OutputStream out = stream.createOutputStream(COSName.FLATE_DECODE);
out.write(content.getBytes("ISO-8859-1"));
out.close();
return stream;
}
/**
* Sets the overlay position.
*
* @param overlayPosition the overlay position
*/
public void setOverlayPosition(Position overlayPosition)
{
position = overlayPosition;
}
/**
* Sets the file to be overlayed.
*
* @param inputFile the file to be overlayed
*/
public void setInputFile(String inputFile)
{
inputFileName = inputFile;
}
/**
* Sets the PDF to be overlayed.
*
* @param inputPDF the PDF to be overlayed
*/
public void setInputPDF(PDDocument inputPDF)
{
inputPDFDocument = inputPDF;
}
/**
* Returns the input file.
*
* @return the input file
*/
public String getInputFile()
{
return inputFileName;
}
/**
* Sets the output file.
*
* @param outputFile the output file
*/
public void setOutputFile(String outputFile)
{
outputFilename = outputFile;
}
/**
* Returns the output file.
*
* @return the output file
*/
public String getOutputFile()
{
return outputFilename;
}
/**
* Sets the default overlay file.
*
* @param defaultOverlayFile the default overlay file
*/
public void setDefaultOverlayFile(String defaultOverlayFile)
{
defaultOverlayFilename = defaultOverlayFile;
}
/**
* Sets the default overlay PDF.
*
* @param defaultOverlayPDF the default overlay PDF
*/
public void setDefaultOverlayPDF(PDDocument defaultOverlayPDF)
{
defaultOverlay = defaultOverlayPDF;
}
/**
* Returns the default overlay file.
*
* @return the default overlay file
*/
public String getDefaultOverlayFile()
{
return defaultOverlayFilename;
}
/**
* Sets the first page overlay file.
*
* @param firstPageOverlayFile the first page overlay file
*/
public void setFirstPageOverlayFile(String firstPageOverlayFile)
{
firstPageOverlayFilename = firstPageOverlayFile;
}
/**
* Sets the first page overlay PDF.
*
* @param firstPageOverlayPDF the first page overlay PDF
*/
public void setFirstPageOverlayPDF(PDDocument firstPageOverlayPDF)
{
firstPageOverlay = firstPageOverlayPDF;
}
/**
* Sets the last page overlay file.
*
* @param lastPageOverlayFile the last page overlay file
*/
public void setLastPageOverlayFile(String lastPageOverlayFile)
{
lastPageOverlayFilename = lastPageOverlayFile;
}
/**
* Sets the last page overlay PDF.
*
* @param lastPageOverlayPDF the last page overlay PDF
*/
public void setLastPageOverlayPDF(PDDocument lastPageOverlayPDF)
{
lastPageOverlay = lastPageOverlayPDF;
}
/**
* Sets the all pages overlay file.
*
* @param allPagesOverlayFile the all pages overlay file
*/
public void setAllPagesOverlayFile(String allPagesOverlayFile)
{
allPagesOverlayFilename = allPagesOverlayFile;
}
/**
* Sets the all pages overlay PDF.
*
* @param allPagesOverlayPDF the all pages overlay PDF
*/
public void setAllPagesOverlayPDF(PDDocument allPagesOverlayPDF)
{
allPagesOverlay = allPagesOverlayPDF;
}
/**
* Sets the odd page overlay file.
*
* @param oddPageOverlayFile the odd page overlay file
*/
public void setOddPageOverlayFile(String oddPageOverlayFile)
{
oddPageOverlayFilename = oddPageOverlayFile;
}
/**
* Sets the odd page overlay PDF.
*
* @param oddPageOverlayPDF the odd page overlay PDF
*/
public void setOddPageOverlayPDF(PDDocument oddPageOverlayPDF)
{
oddPageOverlay = oddPageOverlayPDF;
}
/**
* Sets the even page overlay file.
*
* @param evenPageOverlayFile the even page overlay file
*/
public void setEvenPageOverlayFile(String evenPageOverlayFile)
{
evenPageOverlayFilename = evenPageOverlayFile;
}
/**
* Sets the even page overlay PDF.
*
* @param evenPageOverlayPDF the even page overlay PDF
*/
public void setEvenPageOverlayPDF(PDDocument evenPageOverlayPDF)
{
evenPageOverlay = evenPageOverlayPDF;
}
}