Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.itextpdf.text.pdf.PdfCopy Maven / Gradle / Ivy
/*
* $Id: 51dc6461dda35cb95248fe4ff8446e433d202e12 $
*
* This file is part of the iText (R) project.
* Copyright (c) 1998-2016 iText Group NV
* Authors: Bruno Lowagie, Paulo Soares, et al.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA, 02110-1301 USA, or download the license from the following URL:
* http://itextpdf.com/terms-of-use/
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* In accordance with Section 7(b) of the GNU Affero General Public License,
* a covered work must retain the producer line in every PDF that is created
* or manipulated using iText.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the iText software without
* disclosing the source code of your own applications.
* These activities include: offering paid services to customers as an ASP,
* serving PDFs on the fly in a web application, shipping iText with a closed
* source product.
*
* For more information, please contact iText Software Corp. at this
* address: [email protected]
*/
package com.itextpdf.text.pdf;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.exceptions.BadPasswordException;
import com.itextpdf.text.log.Counter;
import com.itextpdf.text.log.CounterFactory;
import com.itextpdf.text.log.Logger;
import com.itextpdf.text.log.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* Make copies of PDF documents. Documents can be edited after reading and
* before writing them out.
* @author Mark Thompson
*/
public class PdfCopy extends PdfWriter {
private static final Logger LOGGER = LoggerFactory.getLogger(PdfCopy.class);
/**
* This class holds information about indirect references, since they are
* renumbered by iText.
*/
static class IndirectReferences {
PdfIndirectReference theRef;
boolean hasCopied;
IndirectReferences(PdfIndirectReference ref) {
theRef = ref;
hasCopied = false;
}
void setCopied() { hasCopied = true; }
void setNotCopied() { hasCopied = false; }
boolean getCopied() { return hasCopied; }
PdfIndirectReference getRef() { return theRef; }
@Override
public String toString() {
String ext = "";
if (hasCopied) ext += " Copied";
return getRef() + ext;
}
}
protected static Counter COUNTER = CounterFactory.getCounter(PdfCopy.class);
protected Counter getCounter() {
return COUNTER;
}
protected HashMap indirects;
protected HashMap> indirectMap;
protected HashMap parentObjects;
protected HashSet disableIndirects;
protected PdfReader reader;
protected int[] namePtr = {0};
/** Holds value of property rotateContents. */
private boolean rotateContents = true;
protected PdfArray fieldArray;
protected HashSet fieldTemplates;
private PdfStructTreeController structTreeController = null;
private int currentStructArrayNumber = 0;
//remember to avoid coping
protected PRIndirectReference structTreeRootReference;
//to remove unused objects
protected LinkedHashMap indirectObjects;
//PdfIndirectObjects, that generate PdfWriter.addToBody(PdfObject) method, already saved to PdfBody
protected ArrayList savedObjects;
//imported pages from getImportedPage(PdfReader, int, boolean)
protected ArrayList importedPages;
//for correct update of kids in StructTreeRootController
protected boolean updateRootKids = false;
static private final PdfName annotId = new PdfName("iTextAnnotId");
static private int annotIdCnt = 0;
protected boolean mergeFields = false;
private boolean needAppearances = false;
private boolean hasSignature;
private PdfIndirectReference acroForm;
private HashMap> tabOrder;
private ArrayList calculationOrderRefs;
private PdfDictionary resources;
protected ArrayList fields;
private ArrayList calculationOrder;
private HashMap fieldTree;
private HashMap unmergedMap;
private HashMap unmergedIndirectRefsMap;
private HashMap mergedMap;
private HashSet mergedSet;
private boolean mergeFieldsInternalCall = false;
private static final PdfName iTextTag = new PdfName("_iTextTag_");
private static final Integer zero = Integer.valueOf(0);
private HashSet mergedRadioButtons = new HashSet();
private HashMap mergedTextFields = new HashMap();
private HashSet readersWithImportedStructureTreeRootKids = new HashSet();
protected static class ImportedPage {
int pageNumber;
PdfReader reader;
PdfArray mergedFields;
PdfIndirectReference annotsIndirectReference;
ImportedPage(PdfReader reader, int pageNumber, boolean keepFields) {
this.pageNumber = pageNumber;
this.reader = reader;
if (keepFields) {
mergedFields = new PdfArray();
}
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ImportedPage)) return false;
ImportedPage other = (ImportedPage)o;
return this.pageNumber == other.pageNumber && this.reader.equals(other.reader);
}
@Override
public String toString() {
return Integer.toString(pageNumber);
}
}
/**
* Constructor
* @param document document
* @param os outputstream
*/
public PdfCopy(Document document, OutputStream os) throws DocumentException {
super(new PdfDocument(), os);
document.addDocListener(pdf);
pdf.addWriter(this);
indirectMap = new HashMap>();
parentObjects = new HashMap();
disableIndirects = new HashSet();
indirectObjects = new LinkedHashMap();
savedObjects = new ArrayList();
importedPages = new ArrayList();
}
/**
* Setting page events isn't possible with Pdf(Smart)Copy.
* Use the PageStamp class if you want to add content to copied pages.
* @see com.itextpdf.text.pdf.PdfWriter#setPageEvent(com.itextpdf.text.pdf.PdfPageEvent)
*/
@Override
public void setPageEvent(PdfPageEvent event) {
throw new UnsupportedOperationException();
}
/** Getter for property rotateContents.
* @return Value of property rotateContents.
*
*/
public boolean isRotateContents() {
return this.rotateContents;
}
/** Setter for property rotateContents.
* @param rotateContents New value of property rotateContents.
*
*/
public void setRotateContents(boolean rotateContents) {
this.rotateContents = rotateContents;
}
public void setMergeFields() {
this.mergeFields = true;
resources = new PdfDictionary();
fields = new ArrayList();
calculationOrder = new ArrayList();
fieldTree = new LinkedHashMap();
unmergedMap = new HashMap();
unmergedIndirectRefsMap = new HashMap();
mergedMap = new HashMap();
mergedSet = new HashSet();
}
/**
* Grabs a page from the input document
* @param reader the reader of the document
* @param pageNumber which page to get
* @return the page
*/
@Override
public PdfImportedPage getImportedPage(PdfReader reader, int pageNumber) {
if (mergeFields && !mergeFieldsInternalCall) {
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("1.method.cannot.be.used.in.mergeFields.mode.please.use.addDocument", "getImportedPage"));
}
if (mergeFields) {
ImportedPage newPage = new ImportedPage(reader, pageNumber, mergeFields);
importedPages.add(newPage);
}
if (structTreeController != null)
structTreeController.reader = null;
disableIndirects.clear();
parentObjects.clear();
return getImportedPageImpl(reader, pageNumber);
}
public PdfImportedPage getImportedPage(PdfReader reader, int pageNumber, boolean keepTaggedPdfStructure) throws BadPdfFormatException {
if (mergeFields && !mergeFieldsInternalCall) {
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("1.method.cannot.be.used.in.mergeFields.mode.please.use.addDocument", "getImportedPage"));
}
updateRootKids = false;
if (!keepTaggedPdfStructure) {
if (mergeFields) {
ImportedPage newPage = new ImportedPage(reader, pageNumber, mergeFields);
importedPages.add(newPage);
}
return getImportedPageImpl(reader, pageNumber);
}
if (structTreeController != null) {
if (reader != structTreeController.reader)
structTreeController.setReader(reader);
} else {
structTreeController = new PdfStructTreeController(reader, this);
}
ImportedPage newPage = new ImportedPage(reader, pageNumber, mergeFields);
switch (checkStructureTreeRootKids(newPage)) {
case -1: //-1 - clear , update
clearIndirects(reader);
updateRootKids = true;
break;
case 0: //0 - not clear, not update
updateRootKids = false;
break;
case 1: //1 - not clear, update
updateRootKids = true;
break;
}
importedPages.add(newPage);
disableIndirects.clear();
parentObjects.clear();
return getImportedPageImpl(reader, pageNumber);
}
private void clearIndirects(PdfReader reader) {
HashMap currIndirects = indirectMap.get(reader);
ArrayList forDelete = new ArrayList();
for (Map.Entry entry: currIndirects.entrySet()) {
PdfIndirectReference iRef = entry.getValue().theRef;
RefKey key = new RefKey(iRef);
PdfIndirectObject iobj = indirectObjects.get(key);
if (iobj == null) {
forDelete.add(entry.getKey());
}
else if (iobj.object.isArray() || iobj.object.isDictionary() || iobj.object.isStream()) {
forDelete.add(entry.getKey());
}
}
for (RefKey key: forDelete)
currIndirects.remove(key);
}
//0 - not clear, not update
//-1 - clear , update
//1 - not clear, update
private int checkStructureTreeRootKids(ImportedPage newPage) {
//start of document;
if (importedPages.size() == 0) return 1;
boolean readerExist = false;
for (ImportedPage page: importedPages) {
if (page.reader.equals(newPage.reader)) {
readerExist = true;
break;
}
}
//add new reader;
if (!readerExist) return 1;
ImportedPage lastPage = importedPages.get(importedPages.size() - 1);
boolean equalReader = lastPage.reader.equals(newPage.reader);
//reader exist, correct order;
if (equalReader && newPage.pageNumber > lastPage.pageNumber) {
if (readersWithImportedStructureTreeRootKids.contains(newPage.reader))
return 0;
else
return 1;
}
//reader exist, incorrect order;
return -1;
}
protected void structureTreeRootKidsForReaderImported(PdfReader reader) {
readersWithImportedStructureTreeRootKids.add(reader);
}
protected void fixStructureTreeRoot(HashSet activeKeys, HashSet activeClassMaps) {
HashMap newClassMap = new HashMap(activeClassMaps.size());
for (PdfName key: activeClassMaps) {
PdfObject cm = structureTreeRoot.classes.get(key);
if (cm != null) newClassMap.put(key, cm);
}
structureTreeRoot.classes = newClassMap;
PdfArray kids = structureTreeRoot.getAsArray(PdfName.K);
if (kids != null) {
for (int i = 0; i < kids.size(); ++i) {
PdfIndirectReference iref = (PdfIndirectReference)kids.getPdfObject(i);
RefKey key = new RefKey(iref);
if (!activeKeys.contains(key)) kids.remove(i--);
}
}
}
protected PdfImportedPage getImportedPageImpl(PdfReader reader, int pageNumber) {
if (currentPdfReaderInstance != null) {
if (currentPdfReaderInstance.getReader() != reader) {
// TODO: Removed - the user should be responsible for closing all PdfReaders. But, this could cause a lot of memory leaks in code out there that hasn't been properly closing things - maybe add a finalizer to PdfReader that calls PdfReader#close() ??
// try {
// currentPdfReaderInstance.getReader().close();
// currentPdfReaderInstance.getReaderFile().close();
// }
// catch (IOException ioe) {
// // empty on purpose
// }
currentPdfReaderInstance = super.getPdfReaderInstance(reader);
}
}
else {
currentPdfReaderInstance = super.getPdfReaderInstance(reader);
}
//currentPdfReaderInstance.setOutputToPdf(false);
return currentPdfReaderInstance.getImportedPage(pageNumber);
}
/**
* Translate a PRIndirectReference to a PdfIndirectReference
* In addition, translates the object numbers, and copies the
* referenced object to the output file.
* NB: PRIndirectReferences (and PRIndirectObjects) really need to know what
* file they came from, because each file has its own namespace. The translation
* we do from their namespace to ours is *at best* heuristic, and guaranteed to
* fail under some circumstances.
*/
protected PdfIndirectReference copyIndirect(PRIndirectReference in, boolean keepStructure, boolean directRootKids) throws IOException, BadPdfFormatException {
PdfIndirectReference theRef;
RefKey key = new RefKey(in);
IndirectReferences iRef = indirects.get(key);
PdfObject obj = PdfReader.getPdfObjectRelease(in);
if ((keepStructure) && (directRootKids))
if (obj instanceof PdfDictionary) {
PdfDictionary dict = (PdfDictionary) obj;
if (dict.contains(PdfName.PG))
return null;
}
if (iRef != null) {
theRef = iRef.getRef();
if (iRef.getCopied()) {
return theRef;
}
}
else {
theRef = body.getPdfIndirectReference();
iRef = new IndirectReferences(theRef);
indirects.put(key, iRef);
}
if (obj != null && obj.isDictionary()) {
PdfObject type = PdfReader.getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.TYPE));
if (type != null) {
if ((PdfName.PAGE.equals(type))) {
return theRef;
}
if ((PdfName.CATALOG.equals(type))) {
LOGGER.warn(MessageLocalization.getComposedMessage("make.copy.of.catalog.dictionary.is.forbidden"));
return null;
}
}
}
iRef.setCopied();
if (obj != null) parentObjects.put(obj, in);
PdfObject res = copyObject(obj, keepStructure, directRootKids);
if (disableIndirects.contains(obj))
iRef.setNotCopied();
if (res != null)
{
addToBody(res, theRef);
return theRef;
}
else {
indirects.remove(key);
return null;
}
}
/**
* Translate a PRIndirectReference to a PdfIndirectReference
* In addition, translates the object numbers, and copies the
* referenced object to the output file.
* NB: PRIndirectReferences (and PRIndirectObjects) really need to know what
* file they came from, because each file has its own namespace. The translation
* we do from their namespace to ours is *at best* heuristic, and guaranteed to
* fail under some circumstances.
*/
protected PdfIndirectReference copyIndirect(PRIndirectReference in) throws IOException, BadPdfFormatException {
return copyIndirect(in, false, false);
}
/**
* Translate a PRDictionary to a PdfDictionary. Also translate all of the
* objects contained in it.
*/
protected PdfDictionary copyDictionary(PdfDictionary in, boolean keepStruct, boolean directRootKids)
throws IOException, BadPdfFormatException {
PdfDictionary out = new PdfDictionary(in.size());
PdfObject type = PdfReader.getPdfObjectRelease(in.get(PdfName.TYPE));
if (keepStruct)
{
if ((directRootKids) && (in.contains(PdfName.PG)))
{
PdfObject curr = in;
disableIndirects.add(curr);
while (parentObjects.containsKey(curr) && !(disableIndirects.contains(curr))) {
curr = parentObjects.get(curr);
disableIndirects.add(curr);
}
return null;
}
PdfName structType = in.getAsName(PdfName.S);
structTreeController.addRole(structType);
structTreeController.addClass(in);
}
if (structTreeController != null && structTreeController.reader != null && (in.contains(PdfName.STRUCTPARENTS) || in.contains(PdfName.STRUCTPARENT))) {
PdfName key = PdfName.STRUCTPARENT;
if (in.contains(PdfName.STRUCTPARENTS)) {
key = PdfName.STRUCTPARENTS;
}
PdfObject value = in.get(key);
out.put(key, new PdfNumber(currentStructArrayNumber));
structTreeController.copyStructTreeForPage((PdfNumber) value, currentStructArrayNumber++);
}
for (Object element : in.getKeys()) {
PdfName key = (PdfName)element;
PdfObject value = in.get(key);
if (structTreeController != null && structTreeController.reader != null && (key.equals(PdfName.STRUCTPARENTS) || key.equals(PdfName.STRUCTPARENT))) {
continue;
}
if (PdfName.PAGE.equals(type)) {
if (!key.equals(PdfName.B) && !key.equals(PdfName.PARENT)) {
parentObjects.put(value, in);
PdfObject res = copyObject(value, keepStruct, directRootKids);
if (res != null)
out.put(key, res);
}
}
else {
PdfObject res;
if (tagged && value.isIndirect() && isStructTreeRootReference((PRIndirectReference)value)) {
res = structureTreeRoot.getReference();
} else {
res = copyObject(value, keepStruct, directRootKids);
}
if (res != null)
out.put(key, res);
}
}
return out;
}
/**
* Translate a PRDictionary to a PdfDictionary. Also translate all of the
* objects contained in it.
*/
protected PdfDictionary copyDictionary(PdfDictionary in)
throws IOException, BadPdfFormatException {
return copyDictionary(in, false, false);
}
/**
* Translate a PRStream to a PdfStream. The data part copies itself.
*/
protected PdfStream copyStream(PRStream in) throws IOException, BadPdfFormatException {
PRStream out = new PRStream(in, null);
for (Object element : in.getKeys()) {
PdfName key = (PdfName) element;
PdfObject value = in.get(key);
parentObjects.put(value, in);
PdfObject res = copyObject(value);
if (res != null)
out.put(key, res);
}
return out;
}
/**
* Translate a PRArray to a PdfArray. Also translate all of the objects contained
* in it
*/
protected PdfArray copyArray(PdfArray in, boolean keepStruct, boolean directRootKids) throws IOException, BadPdfFormatException {
PdfArray out = new PdfArray(in.size());
for (Iterator i = in.listIterator(); i.hasNext();) {
PdfObject value = i.next();
parentObjects.put(value, in);
PdfObject res = copyObject(value, keepStruct, directRootKids);
if (res != null)
out.add(res);
}
return out;
}
/**
* Translate a PRArray to a PdfArray. Also translate all of the objects contained
* in it
*/
protected PdfArray copyArray(PdfArray in) throws IOException, BadPdfFormatException {
return copyArray(in, false, false);
}
/**
* Translate a PR-object to a Pdf-object
*/
protected PdfObject copyObject(PdfObject in, boolean keepStruct, boolean directRootKids) throws IOException,BadPdfFormatException {
if (in == null)
return PdfNull.PDFNULL;
switch (in.type) {
case PdfObject.DICTIONARY:
return copyDictionary((PdfDictionary)in, keepStruct, directRootKids);
case PdfObject.INDIRECT:
if (!keepStruct && !directRootKids)
// fix for PdfSmartCopy
return copyIndirect((PRIndirectReference)in);
else
return copyIndirect((PRIndirectReference)in, keepStruct, directRootKids);
case PdfObject.ARRAY:
return copyArray((PdfArray)in, keepStruct, directRootKids);
case PdfObject.NUMBER:
case PdfObject.NAME:
case PdfObject.STRING:
case PdfObject.NULL:
case PdfObject.BOOLEAN:
case 0://PdfIndirectReference
return in;
case PdfObject.STREAM:
return copyStream((PRStream)in);
// return in;
default:
if (in.type < 0) {
String lit = ((PdfLiteral)in).toString();
if (lit.equals("true") || lit.equals("false")) {
return new PdfBoolean(lit);
}
return new PdfLiteral(lit);
}
System.out.println("CANNOT COPY type " + in.type);
return null;
}
}
/**
* Translate a PR-object to a Pdf-object
*/
protected PdfObject copyObject(PdfObject in) throws IOException,BadPdfFormatException {
return copyObject(in, false, false);
}
/**
* convenience method. Given an imported page, set our "globals"
*/
protected int setFromIPage(PdfImportedPage iPage) {
int pageNum = iPage.getPageNumber();
PdfReaderInstance inst = currentPdfReaderInstance = iPage.getPdfReaderInstance();
reader = inst.getReader();
setFromReader(reader);
return pageNum;
}
/**
* convenience method. Given a reader, set our "globals"
*/
protected void setFromReader(PdfReader reader) {
this.reader = reader;
indirects = indirectMap.get(reader);
if (indirects == null) {
indirects = new HashMap();
indirectMap.put(reader,indirects);
}
}
/**
* Add an imported page to our output
* @param iPage an imported page
* @throws IOException, BadPdfFormatException
*/
public void addPage(PdfImportedPage iPage) throws IOException, BadPdfFormatException {
if (mergeFields && !mergeFieldsInternalCall) {
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("1.method.cannot.be.used.in.mergeFields.mode.please.use.addDocument", "addPage"));
}
int pageNum = setFromIPage(iPage);
PdfDictionary thePage = reader.getPageN(pageNum);
PRIndirectReference origRef = reader.getPageOrigRef(pageNum);
reader.releasePage(pageNum);
RefKey key = new RefKey(origRef);
PdfIndirectReference pageRef;
IndirectReferences iRef = indirects.get(key);
if (iRef != null && !iRef.getCopied()) {
pageReferences.add(iRef.getRef());
iRef.setCopied();
}
pageRef = getCurrentPage();
if (iRef == null) {
iRef = new IndirectReferences(pageRef);
indirects.put(key, iRef);
}
iRef.setCopied();
if (tagged)
structTreeRootReference = (PRIndirectReference)reader.getCatalog().get(PdfName.STRUCTTREEROOT);
PdfDictionary newPage = copyDictionary(thePage);
if (mergeFields) {
ImportedPage importedPage = importedPages.get(importedPages.size() - 1);
importedPage.annotsIndirectReference = body.getPdfIndirectReference();
newPage.put(PdfName.ANNOTS, importedPage.annotsIndirectReference);
}
root.addPage(newPage);
iPage.setCopied();
++currentPageNumber;
pdf.setPageCount(currentPageNumber);
structTreeRootReference = null;
}
/**
* Adds a blank page.
* @param rect The page dimension
* @param rotation The rotation angle in degrees
* @throws DocumentException
* @since 2.1.5
*/
public void addPage(Rectangle rect, int rotation) throws DocumentException {
if (mergeFields && !mergeFieldsInternalCall) {
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("1.method.cannot.be.used.in.mergeFields.mode.please.use.addDocument", "addPage"));
}
PdfRectangle mediabox = new PdfRectangle(rect, rotation);
PageResources resources = new PageResources();
PdfPage page = new PdfPage(mediabox, new HashMap(), resources.getResources(), 0);
page.put(PdfName.TABS, getTabs());
root.addPage(page);
++currentPageNumber;
pdf.setPageCount(currentPageNumber);
}
public void addDocument(PdfReader reader, List pagesToKeep) throws DocumentException, IOException {
if (indirectMap.containsKey(reader)) {
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("document.1.has.already.been.added", reader.toString()));
}
reader.selectPages(pagesToKeep, false);
addDocument(reader);
}
/**
* Copy document fields to a destination document.
* @param reader a document where fields are copied from.
* @throws DocumentException
* @throws IOException
*/
public void copyDocumentFields(PdfReader reader) throws DocumentException, IOException {
if (!document.isOpen()) {
throw new DocumentException(MessageLocalization.getComposedMessage("the.document.is.not.open.yet.you.can.only.add.meta.information"));
}
if (indirectMap.containsKey(reader)) {
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("document.1.has.already.been.added", reader.toString()));
}
if (!reader.isOpenedWithFullPermissions())
throw new BadPasswordException(MessageLocalization.getComposedMessage("pdfreader.not.opened.with.owner.password"));
if (!mergeFields)
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("1.method.can.be.only.used.in.mergeFields.mode.please.use.addDocument", "copyDocumentFields"));
indirects = new HashMap();
indirectMap.put(reader, indirects);
reader.consolidateNamedDestinations();
reader.shuffleSubsetNames();
if (tagged && PdfStructTreeController.checkTagged(reader)) {
structTreeRootReference = (PRIndirectReference)reader.getCatalog().get(PdfName.STRUCTTREEROOT);
if (structTreeController != null) {
if (reader != structTreeController.reader)
structTreeController.setReader(reader);
} else {
structTreeController = new PdfStructTreeController(reader, this);
}
}
List annotationsToBeCopied = new ArrayList();
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
PdfDictionary page = reader.getPageNRelease(i);
if (page != null && page.contains(PdfName.ANNOTS)) {
PdfArray annots = page.getAsArray(PdfName.ANNOTS);
if (annots != null && annots.size() > 0) {
if (importedPages.size() < i)
throw new DocumentException(MessageLocalization.getComposedMessage("there.are.not.enough.imported.pages.for.copied.fields"));
indirectMap.get(reader).put(new RefKey(reader.pageRefs.getPageOrigRef(i)), new IndirectReferences(pageReferences.get(i - 1)));
for (int j = 0; j < annots.size(); j++) {
PdfDictionary annot = annots.getAsDict(j);
if (annot != null) {
annot.put(annotId, new PdfNumber(++annotIdCnt));
annotationsToBeCopied.add(annots.getPdfObject(j));
}
}
}
}
}
for (PdfObject annot : annotationsToBeCopied) {
copyObject(annot);
}
if (tagged && structTreeController != null)
structTreeController.attachStructTreeRootKids(null);
AcroFields acro = reader.getAcroFields();
boolean needapp = !acro.isGenerateAppearances();
if (needapp)
needAppearances = true;
fields.add(acro);
updateCalculationOrder(reader);
structTreeRootReference = null;
}
public void addDocument(PdfReader reader) throws DocumentException, IOException {
if ( ! document.isOpen() ) {
throw new DocumentException(MessageLocalization.getComposedMessage("the.document.is.not.open.yet.you.can.only.add.meta.information"));
}
if (indirectMap.containsKey(reader)) {
throw new IllegalArgumentException(MessageLocalization.getComposedMessage("document.1.has.already.been.added", reader.toString()));
}
if (!reader.isOpenedWithFullPermissions())
throw new BadPasswordException(MessageLocalization.getComposedMessage("pdfreader.not.opened.with.owner.password"));
if (mergeFields) {
reader.consolidateNamedDestinations();
reader.shuffleSubsetNames();
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
PdfDictionary page = reader.getPageNRelease(i);
if (page != null && page.contains(PdfName.ANNOTS)) {
PdfArray annots = page.getAsArray(PdfName.ANNOTS);
if (annots != null) {
for (int j = 0; j < annots.size(); j++) {
PdfDictionary annot = annots.getAsDict(j);
if (annot != null)
annot.put(annotId, new PdfNumber(++annotIdCnt));
}
}
}
}
AcroFields acro = reader.getAcroFields();
// when a document with NeedAppearances is encountered, the flag is set
// in the resulting document.
boolean needapp = !acro.isGenerateAppearances();
if (needapp)
needAppearances = true;
fields.add(acro);
updateCalculationOrder(reader);
}
boolean tagged = this.tagged && PdfStructTreeController.checkTagged(reader);
mergeFieldsInternalCall = true;
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
addPage(getImportedPage(reader, i, tagged));
}
mergeFieldsInternalCall = false;
}
@Override
public PdfIndirectObject addToBody(final PdfObject object, final PdfIndirectReference ref) throws IOException {
return this.addToBody(object, ref, false);
}
@Override
public PdfIndirectObject addToBody(final PdfObject object, final PdfIndirectReference ref, boolean formBranching) throws IOException {
if (formBranching) {
updateReferences(object);
}
PdfIndirectObject iobj;
if ((tagged || mergeFields) && indirectObjects != null && (object.isArray() || object.isDictionary() || object.isStream() || object.isNull())) {
RefKey key = new RefKey(ref);
PdfIndirectObject obj = indirectObjects.get(key);
if (obj == null) {
obj = new PdfIndirectObject(ref, object, this);
indirectObjects.put(key, obj);
}
iobj = obj;
} else {
iobj = super.addToBody(object, ref);
}
if (mergeFields && object.isDictionary()) {
PdfNumber annotId = ((PdfDictionary)object).getAsNumber(PdfCopy.annotId);
if (annotId != null) {
if (formBranching) {
mergedMap.put(annotId.intValue(), iobj);
mergedSet.add(iobj);
} else {
unmergedMap.put(annotId.intValue(), iobj);
unmergedIndirectRefsMap.put(new RefKey(iobj.number, iobj.generation), iobj);
}
}
}
return iobj;
}
@Override
protected void cacheObject(PdfIndirectObject iobj) {
if ((tagged || mergeFields) && indirectObjects != null) {
savedObjects.add(iobj);
RefKey key = new RefKey(iobj.number, iobj.generation);
if (!indirectObjects.containsKey(key)) indirectObjects.put(key, iobj);
}
}
@Override
protected void flushTaggedObjects() throws IOException {
try {
fixTaggedStructure();
} catch (ClassCastException ex) {
} finally {flushIndirectObjects();}
}
@Override
protected void flushAcroFields() throws IOException, BadPdfFormatException {
if (mergeFields) {
try {
//save annotations that appear just at page level (comments, popups)
for (ImportedPage page : importedPages) {
PdfDictionary pageDict = page.reader.getPageN(page.pageNumber);
if (pageDict != null) {
PdfArray pageFields = pageDict.getAsArray(PdfName.ANNOTS);
if (pageFields == null || pageFields.size() == 0)
continue;
for (AcroFields.Item items: page.reader.getAcroFields().getFields().values()) {
for(PdfIndirectReference ref: items.widget_refs) {
pageFields.arrayList.remove(ref);
}
}
indirects = indirectMap.get(page.reader);
for (PdfObject ref: pageFields.arrayList)
page.mergedFields.add(copyObject(ref));
}
}
//ok, remove old fields and build create new one
for (PdfReader reader : indirectMap.keySet()) {
reader.removeFields();
}
mergeFields();
createAcroForms();
} catch (ClassCastException ex) {
} finally {
if (!tagged)
flushIndirectObjects();
}
}
}
protected void fixTaggedStructure() throws IOException {
HashMap numTree = structureTreeRoot.getNumTree();
HashSet activeKeys = new HashSet();
ArrayList actives = new ArrayList();
int pageRefIndex = 0;
if (mergeFields && acroForm != null) {
actives.add(acroForm);
activeKeys.add(new RefKey(acroForm));
}
for (PdfIndirectReference page : pageReferences) {
actives.add(page);
activeKeys.add(new RefKey(page));
}
//from end, because some objects can appear on several pages because of MCR (out16.pdf)
for (int i = numTree.size() - 1; i >= 0; --i) {
PdfIndirectReference currNum = numTree.get(i);
if (currNum == null) {
continue;
}
RefKey numKey = new RefKey(currNum);
PdfObject obj = indirectObjects.get(numKey).object;
if (obj.isDictionary()) {
boolean addActiveKeys = false;
if (pageReferences.contains(((PdfDictionary)obj).get(PdfName.PG))) {
addActiveKeys = true;
} else {
PdfDictionary k = PdfStructTreeController.getKDict((PdfDictionary)obj);
if (k != null && pageReferences.contains(k.get(PdfName.PG))) {
addActiveKeys = true;
}
}
if (addActiveKeys) {
activeKeys.add(numKey);
actives.add(currNum);
} else {
numTree.remove(i);
}
} else if (obj.isArray()) {
activeKeys.add(numKey);
actives.add(currNum);
PdfArray currNums = (PdfArray)obj;
PdfIndirectReference currPage = pageReferences.get(pageRefIndex++);
actives.add(currPage);
activeKeys.add(new RefKey(currPage));
PdfIndirectReference prevKid = null;
for (int j = 0; j < currNums.size(); j++) {
PdfIndirectReference currKid = (PdfIndirectReference)currNums.getDirectObject(j);
if (currKid.equals(prevKid)) continue;
RefKey kidKey = new RefKey(currKid);
activeKeys.add(kidKey);
actives.add(currKid);
PdfIndirectObject iobj = indirectObjects.get(kidKey);
if (iobj.object.isDictionary()) {
PdfDictionary dict = (PdfDictionary)iobj.object;
PdfIndirectReference pg = (PdfIndirectReference)dict.get(PdfName.PG);
//if pg is real page - do nothing, else set correct pg and remove first MCID if exists
if (pg != null && !pageReferences.contains(pg) && !pg.equals(currPage)){
dict.put(PdfName.PG, currPage);
PdfArray kids = dict.getAsArray(PdfName.K);
if (kids != null) {
PdfObject firstKid = kids.getDirectObject(0);
if (firstKid.isNumber()) kids.remove(0);
}
}
}
prevKid = currKid;
}
}
}
HashSet activeClassMaps = new HashSet();
//collect all active objects from current active set (include kids, classmap, attributes)
findActives(actives, activeKeys, activeClassMaps);
//find parents of active objects
ArrayList newRefs = findActiveParents(activeKeys);
//find new objects with incorrect Pg; if find, set Pg from first correct kid. This correct kid must be.
fixPgKey(newRefs, activeKeys);
//remove unused kids of StructTreeRoot and remove unused objects from class map
fixStructureTreeRoot(activeKeys, activeClassMaps);
for(Map.Entry entry: indirectObjects.entrySet()) {
if (!activeKeys.contains(entry.getKey())) {
entry.setValue(null);
}
else {
if (entry.getValue().object.isArray()) {
removeInactiveReferences((PdfArray)entry.getValue().object, activeKeys);
} else if (entry.getValue().object.isDictionary()) {
PdfObject kids = ((PdfDictionary)entry.getValue().object).get(PdfName.K);
if (kids != null && kids.isArray())
removeInactiveReferences((PdfArray)kids, activeKeys);
}
}
}
}
private void removeInactiveReferences(PdfArray array, HashSet activeKeys) {
for (int i = 0; i < array.size(); ++i) {
PdfObject obj = array.getPdfObject(i);
if ((obj.type() == 0 && !activeKeys.contains(new RefKey((PdfIndirectReference)obj))) ||
(obj.isDictionary() && containsInactivePg((PdfDictionary)obj, activeKeys)))
array.remove(i--);
}
}
private boolean containsInactivePg(PdfDictionary dict, HashSet activeKeys) {
PdfObject pg = dict.get(PdfName.PG);
if (pg != null && !activeKeys.contains(new RefKey((PdfIndirectReference)pg)))
return true;
return false;
}
//return new found objects
private ArrayList findActiveParents(HashSet activeKeys){
ArrayList newRefs = new ArrayList();
ArrayList tmpActiveKeys = new ArrayList(activeKeys);
for (int i = 0; i < tmpActiveKeys.size(); ++i) {
PdfIndirectObject iobj = indirectObjects.get(tmpActiveKeys.get(i));
if (iobj == null || !iobj.object.isDictionary()) continue;
PdfObject parent = ((PdfDictionary)iobj.object).get(PdfName.P);
if (parent != null && parent.type() == 0) {
RefKey key = new RefKey((PdfIndirectReference)parent);
if (!activeKeys.contains(key)) {
activeKeys.add(key);
tmpActiveKeys.add(key);
newRefs.add((PdfIndirectReference) parent);
}
}
}
return newRefs;
}
private void fixPgKey(ArrayList newRefs, HashSet activeKeys){
for (PdfIndirectReference iref: newRefs) {
PdfIndirectObject iobj = indirectObjects.get(new RefKey(iref));
if (iobj == null || !iobj.object.isDictionary()) continue;
PdfDictionary dict = (PdfDictionary)iobj.object;
PdfObject pg = dict.get(PdfName.PG);
if (pg == null || activeKeys.contains(new RefKey((PdfIndirectReference)pg))) continue;
PdfArray kids = dict.getAsArray(PdfName.K);
if (kids == null) continue;
for (int i = 0; i < kids.size(); ++i) {
PdfObject obj = kids.getPdfObject(i);
if (obj.type() != 0) {
kids.remove(i--);
} else {
PdfIndirectObject kid = indirectObjects.get(new RefKey((PdfIndirectReference)obj));
if (kid != null && kid.object.isDictionary()) {
PdfObject kidPg = ((PdfDictionary)kid.object).get(PdfName.PG);
if (kidPg != null && activeKeys.contains(new RefKey((PdfIndirectReference)kidPg))) {
dict.put(PdfName.PG, kidPg);
break;
}
}
}
}
}
}
private void findActives(ArrayList actives, HashSet activeKeys, HashSet activeClassMaps){
//collect all active objects from current active set (include kids, classmap, attributes)
for (int i = 0; i < actives.size(); ++i) {
RefKey key = new RefKey(actives.get(i));
PdfIndirectObject iobj = indirectObjects.get(key);
if (iobj == null || iobj.object == null) continue;
switch (iobj.object.type()){
case 0://PdfIndirectReference
findActivesFromReference((PdfIndirectReference)iobj.object, actives, activeKeys);
break;
case PdfObject.ARRAY:
findActivesFromArray((PdfArray)iobj.object, actives, activeKeys, activeClassMaps);
break;
case PdfObject.DICTIONARY:
case PdfObject.STREAM:
findActivesFromDict((PdfDictionary)iobj.object, actives, activeKeys, activeClassMaps);
break;
}
}
}
private void findActivesFromReference(PdfIndirectReference iref, ArrayList actives, HashSet activeKeys) {
RefKey key = new RefKey(iref);
PdfIndirectObject iobj = indirectObjects.get(key);
if (iobj != null && iobj.object.isDictionary() && containsInactivePg((PdfDictionary) iobj.object, activeKeys)) return;
if(!activeKeys.contains(key)) {
activeKeys.add(key);
actives.add(iref);
}
}
private void findActivesFromArray(PdfArray array, ArrayList actives, HashSet activeKeys, HashSet activeClassMaps) {
for (PdfObject obj: array) {
switch (obj.type()) {
case 0://PdfIndirectReference
findActivesFromReference((PdfIndirectReference)obj, actives, activeKeys);
break;
case PdfObject.ARRAY:
findActivesFromArray((PdfArray)obj, actives, activeKeys, activeClassMaps);
break;
case PdfObject.DICTIONARY:
case PdfObject.STREAM:
findActivesFromDict((PdfDictionary)obj, actives, activeKeys, activeClassMaps);
break;
}
}
}
private void findActivesFromDict(PdfDictionary dict, ArrayList actives, HashSet activeKeys, HashSet activeClassMaps) {
if (containsInactivePg(dict, activeKeys)) return;
for (PdfName key: dict.getKeys()) {
PdfObject obj = dict.get(key);
if (key.equals(PdfName.P)) continue;
else if (key.equals(PdfName.C)) { //classmap
if (obj.isArray()) {
for (PdfObject cm: (PdfArray)obj) {
if (cm.isName()) activeClassMaps.add((PdfName)cm);
}
}
else if (obj.isName()) activeClassMaps.add((PdfName)obj);
continue;
}
switch (obj.type()) {
case 0://PdfIndirectReference
findActivesFromReference((PdfIndirectReference)obj, actives, activeKeys);
break;
case PdfObject.ARRAY:
findActivesFromArray((PdfArray)obj, actives, activeKeys, activeClassMaps);
break;
case PdfObject.DICTIONARY:
case PdfObject.STREAM:
findActivesFromDict((PdfDictionary)obj, actives, activeKeys, activeClassMaps);
break;
}
}
}
protected void flushIndirectObjects() throws IOException {
for (PdfIndirectObject iobj: savedObjects)
indirectObjects.remove(new RefKey(iobj.number, iobj.generation));
HashSet inactives = new HashSet();
for(Map.Entry entry: indirectObjects.entrySet()) {
if (entry.getValue() != null)
writeObjectToBody(entry.getValue());
else
inactives.add(entry.getKey());
}
ArrayList pdfCrossReferences = new ArrayList(body.xrefs);
for (PdfBody.PdfCrossReference cr : pdfCrossReferences) {
RefKey key = new RefKey(cr.getRefnum(), 0);
if (inactives.contains(key))
body.xrefs.remove(cr);
}
indirectObjects = null;
}
private void writeObjectToBody(PdfIndirectObject object) throws IOException {
boolean skipWriting = false;
if (mergeFields) {
updateAnnotationReferences(object.object);
if (object.object.isDictionary() || object.object.isStream()) {
PdfDictionary dictionary = (PdfDictionary)object.object;
if (unmergedIndirectRefsMap.containsKey(new RefKey(object.number, object.generation))) {
PdfNumber annotId = dictionary.getAsNumber(PdfCopy.annotId);
if (annotId != null && mergedMap.containsKey(annotId.intValue()))
skipWriting = true;
}
if (mergedSet.contains(object)) {
PdfNumber annotId = dictionary.getAsNumber(PdfCopy.annotId);
if (annotId != null) {
PdfIndirectObject unmerged = unmergedMap.get(annotId.intValue());
if (unmerged != null && unmerged.object.isDictionary()) {
PdfNumber structParent = ((PdfDictionary)unmerged.object).getAsNumber(PdfName.STRUCTPARENT);
if (structParent != null) {
dictionary.put(PdfName.STRUCTPARENT, structParent);
}
}
}
}
}
}
if (!skipWriting) {
PdfDictionary dictionary = null;
PdfNumber annotId = null;
if (mergeFields && object.object.isDictionary()) {
dictionary = (PdfDictionary)object.object;
annotId = dictionary.getAsNumber(PdfCopy.annotId);
if (annotId != null)
dictionary.remove(PdfCopy.annotId);
}
body.add(object.object, object.number, object.generation, true);
if (annotId != null) {
dictionary.put(PdfCopy.annotId, annotId);
}
}
}
private void updateAnnotationReferences(PdfObject obj) {
if (obj.isArray()) {
PdfArray array = (PdfArray)obj;
for (int i = 0; i < array.size(); i++) {
PdfObject o = array.getPdfObject(i);
if (o != null && o.type() == 0) {
PdfIndirectObject entry = unmergedIndirectRefsMap.get(new RefKey((PdfIndirectReference)o));
if (entry != null) {
if (entry.object.isDictionary()) {
PdfNumber annotId = ((PdfDictionary) entry.object).getAsNumber(PdfCopy.annotId);
if (annotId != null) {
PdfIndirectObject merged = mergedMap.get(annotId.intValue());
if (merged != null) {
array.set(i, merged.getIndirectReference());
}
}
}
}
} else {
updateAnnotationReferences(o);
}
}
} else if (obj.isDictionary() || obj.isStream()) {
PdfDictionary dictionary = (PdfDictionary)obj;
for (PdfName key : dictionary.getKeys()) {
PdfObject o = dictionary.get(key);
if (o != null && o.type() == 0) {
PdfIndirectObject entry = unmergedIndirectRefsMap.get(new RefKey((PdfIndirectReference)o));
if (entry != null) {
if (entry.object.isDictionary()) {
PdfNumber annotId = ((PdfDictionary) entry.object).getAsNumber(PdfCopy.annotId);
if (annotId != null) {
PdfIndirectObject merged = mergedMap.get(annotId.intValue());
if (merged != null) {
dictionary.put(key, merged.getIndirectReference());
}
}
}
}
} else {
updateAnnotationReferences(o);
}
}
}
}
private void updateCalculationOrder(PdfReader reader) {
PdfDictionary catalog = reader.getCatalog();
PdfDictionary acro = catalog.getAsDict(PdfName.ACROFORM);
if (acro == null)
return;
PdfArray co = acro.getAsArray(PdfName.CO);
if (co == null || co.size() == 0)
return;
AcroFields af = reader.getAcroFields();
for (int k = 0; k < co.size(); ++k) {
PdfObject obj = co.getPdfObject(k);
if (obj == null || !obj.isIndirect())
continue;
String name = getCOName(reader, (PRIndirectReference) obj);
if (af.getFieldItem(name) == null)
continue;
name = "." + name;
if (calculationOrder.contains(name))
continue;
calculationOrder.add(name);
}
}
private static String getCOName(PdfReader reader, PRIndirectReference ref) {
String name = "";
while (ref != null) {
PdfObject obj = PdfReader.getPdfObject(ref);
if (obj == null || obj.type() != PdfObject.DICTIONARY)
break;
PdfDictionary dic = (PdfDictionary)obj;
PdfString t = dic.getAsString(PdfName.T);
if (t != null) {
name = t.toUnicodeString()+ "." + name;
}
ref = (PRIndirectReference)dic.get(PdfName.PARENT);
}
if (name.endsWith("."))
name = name.substring(0, name.length() - 2);
return name;
}
private void mergeFields() {
int pageOffset = 0;
for (int k = 0; k < fields.size(); ++k) {
AcroFields af = fields.get(k);
Map fd = af.getFields();
if (pageOffset < importedPages.size() && importedPages.get(pageOffset).reader == af.reader) {
addPageOffsetToField(fd, pageOffset);
pageOffset += af.reader.getNumberOfPages();
}
mergeWithMaster(fd);
}
}
private void addPageOffsetToField(Map fd, int pageOffset) {
if (pageOffset == 0)
return;
for (AcroFields.Item item: fd.values()) {
for (int k = 0; k < item.size(); ++k) {
int p = item.getPage(k).intValue();
item.forcePage(k, p + pageOffset);
}
}
}
private void mergeWithMaster(Map fd) {
for (Map.Entry entry: fd.entrySet()) {
String name = entry.getKey();
mergeField(name, entry.getValue());
}
}
@SuppressWarnings("unchecked")
private void mergeField(String name, AcroFields.Item item) {
HashMap map = fieldTree;
StringTokenizer tk = new StringTokenizer(name, ".");
if (!tk.hasMoreTokens())
return;
while (true) {
String s = tk.nextToken();
Object obj = map.get(s);
if (tk.hasMoreTokens()) {
if (obj == null) {
obj = new LinkedHashMap();
map.put(s, obj);
map = (HashMap)obj;
continue;
}
else if (obj instanceof HashMap)
map = (HashMap)obj;
else
return;
}
else {
if (obj instanceof HashMap)
return;
PdfDictionary merged = item.getMerged(0);
if (obj == null) {
PdfDictionary field = new PdfDictionary();
if (PdfName.SIG.equals(merged.get(PdfName.FT)))
hasSignature = true;
for (Object element : merged.getKeys()) {
PdfName key = (PdfName)element;
if (fieldKeys.contains(key))
field.put(key, merged.get(key));
}
ArrayList list = new ArrayList();
list.add(field);
createWidgets(list, item);
map.put(s, list);
}
else {
ArrayList list = (ArrayList)obj;
PdfDictionary field = (PdfDictionary)list.get(0);
PdfName type1 = (PdfName)field.get(PdfName.FT);
PdfName type2 = (PdfName)merged.get(PdfName.FT);
if (type1 == null || !type1.equals(type2))
return;
int flag1 = 0;
PdfObject f1 = field.get(PdfName.FF);
if (f1 != null && f1.isNumber())
flag1 = ((PdfNumber)f1).intValue();
int flag2 = 0;
PdfObject f2 = merged.get(PdfName.FF);
if (f2 != null && f2.isNumber())
flag2 = ((PdfNumber)f2).intValue();
if (type1.equals(PdfName.BTN)) {
if (((flag1 ^ flag2) & PdfFormField.FF_PUSHBUTTON) != 0)
return;
if ((flag1 & PdfFormField.FF_PUSHBUTTON) == 0 && ((flag1 ^ flag2) & PdfFormField.FF_RADIO) != 0)
return;
}
else if (type1.equals(PdfName.CH)) {
if (((flag1 ^ flag2) & PdfFormField.FF_COMBO) != 0)
return;
}
createWidgets(list, item);
}
return;
}
}
}
private void createWidgets(ArrayList list, AcroFields.Item item) {
for (int k = 0; k < item.size(); ++k) {
list.add(item.getPage(k));
PdfDictionary merged = item.getMerged(k);
PdfObject dr = merged.get(PdfName.DR);
if (dr != null)
PdfFormField.mergeResources(resources, (PdfDictionary)PdfReader.getPdfObject(dr));
PdfDictionary widget = new PdfDictionary();
for (Object element : merged.getKeys()) {
PdfName key = (PdfName)element;
if (widgetKeys.contains(key))
widget.put(key, merged.get(key));
}
widget.put(iTextTag, new PdfNumber(item.getTabOrder(k).intValue() + 1));
list.add(widget);
}
}
private PdfObject propagate(PdfObject obj) throws IOException {
if (obj == null) {
return new PdfNull();
} else if (obj.isArray()) {
PdfArray a = (PdfArray)obj;
for (int i = 0; i < a.size(); i++) {
a.set(i, propagate(a.getPdfObject(i)));
}
return a;
} else if (obj.isDictionary() || obj.isStream()) {
PdfDictionary d = (PdfDictionary)obj;
for (PdfName key : d.getKeys()) {
d.put(key, propagate(d.get(key)));
}
return d;
} else if (obj.isIndirect()) {
obj = PdfReader.getPdfObject(obj);
return addToBody(propagate(obj)).getIndirectReference();
} else
return obj;
}
private void createAcroForms() throws IOException, BadPdfFormatException {
if (fieldTree.isEmpty()) {
//write annotations that appear just at page level (comments, popups)
for (ImportedPage importedPage : importedPages) {
if (importedPage.mergedFields.size() > 0)
addToBody(importedPage.mergedFields, importedPage.annotsIndirectReference);
}
return;
}
PdfDictionary form = new PdfDictionary();
form.put(PdfName.DR, propagate(resources));
if (needAppearances) {
form.put(PdfName.NEEDAPPEARANCES, PdfBoolean.PDFTRUE);
}
form.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
tabOrder = new HashMap>();
calculationOrderRefs = new ArrayList(calculationOrder);
form.put(PdfName.FIELDS, branchForm(fieldTree, null, ""));
if (hasSignature)
form.put(PdfName.SIGFLAGS, new PdfNumber(3));
PdfArray co = new PdfArray();
for (int k = 0; k < calculationOrderRefs.size(); ++k) {
Object obj = calculationOrderRefs.get(k);
if (obj instanceof PdfIndirectReference)
co.add((PdfIndirectReference)obj);
}
if (co.size() > 0)
form.put(PdfName.CO, co);
this.acroForm = addToBody(form).getIndirectReference();
for (ImportedPage importedPage : importedPages) {
addToBody(importedPage.mergedFields, importedPage.annotsIndirectReference);
}
}
private void updateReferences(PdfObject obj) {
if (obj.isDictionary() || obj.isStream()) {
PdfDictionary dictionary = (PdfDictionary)obj;
for (PdfName key : dictionary.getKeys()) {
PdfObject o = dictionary.get(key);
if (o.isIndirect()) {
PdfReader reader = ((PRIndirectReference)o).getReader();
HashMap indirects = indirectMap.get(reader);
IndirectReferences indRef = indirects.get(new RefKey((PRIndirectReference)o));
if (indRef != null) {
dictionary.put(key, indRef.getRef());
}
} else {
updateReferences(o);
}
}
} else if (obj.isArray()) {
PdfArray array = (PdfArray)obj;
for (int i = 0; i < array.size(); i++) {
PdfObject o = array.getPdfObject(i);
if (o.isIndirect()) {
PdfReader reader = ((PRIndirectReference)o).getReader();
HashMap indirects = indirectMap.get(reader);
IndirectReferences indRef = indirects.get(new RefKey((PRIndirectReference)o));
if (indRef != null) {
array.set(i, indRef.getRef());
}
} else {
updateReferences(o);
}
}
}
}
@SuppressWarnings("unchecked")
private PdfArray branchForm(HashMap level, PdfIndirectReference parent, String fname) throws IOException, BadPdfFormatException {
PdfArray arr = new PdfArray();
for (Map.Entry entry: level.entrySet()) {
String name = entry.getKey();
Object obj = entry.getValue();
PdfIndirectReference ind = getPdfIndirectReference();
PdfDictionary dic = new PdfDictionary();
if (parent != null)
dic.put(PdfName.PARENT, parent);
dic.put(PdfName.T, new PdfString(name, PdfObject.TEXT_UNICODE));
String fname2 = fname + "." + name;
int coidx = calculationOrder.indexOf(fname2);
if (coidx >= 0)
calculationOrderRefs.set(coidx, ind);
if (obj instanceof HashMap) {
dic.put(PdfName.KIDS, branchForm((HashMap) obj, ind, fname2));
arr.add(ind);
addToBody(dic, ind, true);
}
else {
ArrayList list = (ArrayList)obj;
dic.mergeDifferent((PdfDictionary) list.get(0));
if (list.size() == 3) {
dic.mergeDifferent((PdfDictionary)list.get(2));
int page = ((Integer)list.get(1)).intValue();
PdfArray annots = importedPages.get(page - 1).mergedFields;
PdfNumber nn = (PdfNumber)dic.get(iTextTag);
dic.remove(iTextTag);
dic.put(PdfName.TYPE, PdfName.ANNOT);
adjustTabOrder(annots, ind, nn);
} else {
PdfDictionary field = (PdfDictionary)list.get(0);
PdfArray kids = new PdfArray();
for (int k = 1; k < list.size(); k += 2) {
int page = ((Integer)list.get(k)).intValue();
PdfArray annots = importedPages.get(page - 1).mergedFields;
PdfDictionary widget = new PdfDictionary();
widget.merge((PdfDictionary)list.get(k + 1));
widget.put(PdfName.PARENT, ind);
PdfNumber nn = (PdfNumber)widget.get(iTextTag);
widget.remove(iTextTag);
if (PdfCopy.isTextField(field)) {
PdfString v = field.getAsString(PdfName.V);
PdfObject ap = widget.getDirectObject(PdfName.AP);
if (v != null && ap != null) {
if (!mergedTextFields.containsKey(list)) {
mergedTextFields.put(list, v);
} else {
try {
TextField tx = new TextField(this, null, null);
fields.get(0).decodeGenericDictionary(widget, tx);
Rectangle box = PdfReader.getNormalizedRectangle(widget.getAsArray(PdfName.RECT));
if (tx.getRotation() == 90 || tx.getRotation() == 270)
box = box.rotate();
tx.setBox(box);
tx.setText(mergedTextFields.get(list).toUnicodeString());
PdfAppearance app = tx.getAppearance();
((PdfDictionary)ap).put(PdfName.N, app.getIndirectReference());
} catch (DocumentException e) {
//do nothing
}
}
}
} else if (PdfCopy.isCheckButton(field)) {
PdfName v = field.getAsName(PdfName.V);
PdfName as = widget.getAsName(PdfName.AS);
if (v != null && as != null)
widget.put(PdfName.AS, v);
} else if (PdfCopy.isRadioButton(field)) {
PdfName v = field.getAsName(PdfName.V);
PdfName as = widget.getAsName(PdfName.AS);
if (v != null && as != null && !as.equals(getOffStateName(widget))) {
if (!mergedRadioButtons.contains(list)) {
mergedRadioButtons.add(list);
widget.put(PdfName.AS, v);
} else {
widget.put(PdfName.AS, getOffStateName(widget));
}
}
}
widget.put(PdfName.TYPE, PdfName.ANNOT);
PdfIndirectReference wref = addToBody(widget, getPdfIndirectReference(), true).getIndirectReference();
adjustTabOrder(annots, wref, nn);
kids.add(wref);
}
dic.put(PdfName.KIDS, kids);
}
arr.add(ind);
addToBody(dic, ind, true);
}
}
return arr;
}
private void adjustTabOrder(PdfArray annots, PdfIndirectReference ind, PdfNumber nn) {
int v = nn.intValue();
ArrayList t = tabOrder.get(annots);
if (t == null) {
t = new ArrayList();
int size = annots.size() - 1;
for (int k = 0; k < size; ++k) {
t.add(zero);
}
t.add(Integer.valueOf(v));
tabOrder.put(annots, t);
annots.add(ind);
}
else {
int size = t.size() - 1;
for (int k = size; k >= 0; --k) {
if (t.get(k).intValue() <= v) {
t.add(k + 1, Integer.valueOf(v));
annots.add(k + 1, ind);
size = -2;
break;
}
}
if (size != -2) {
t.add(0, Integer.valueOf(v));
annots.add(0, ind);
}
}
}
/*
* the getCatalog method is part of PdfWriter.
* we wrap this so that we can extend it
*/
@Override
protected PdfDictionary getCatalog(PdfIndirectReference rootObj) {
try {
PdfDictionary theCat = pdf.getCatalog(rootObj);
buildStructTreeRootForTagged(theCat);
if (fieldArray != null) {
addFieldResources(theCat);
} else if (mergeFields && acroForm != null) {
theCat.put(PdfName.ACROFORM, acroForm);
}
return theCat;
}
catch (IOException e) {
throw new ExceptionConverter(e);
}
}
protected boolean isStructTreeRootReference(PdfIndirectReference prRef) {
if (prRef == null || structTreeRootReference == null)
return false;
return prRef.number == structTreeRootReference.number && prRef.generation == structTreeRootReference.generation;
}
private void addFieldResources(PdfDictionary catalog) throws IOException {
if (fieldArray == null)
return;
PdfDictionary acroForm = new PdfDictionary();
catalog.put(PdfName.ACROFORM, acroForm);
acroForm.put(PdfName.FIELDS, fieldArray);
acroForm.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
if (fieldTemplates.isEmpty())
return;
PdfDictionary dr = new PdfDictionary();
acroForm.put(PdfName.DR, dr);
for (PdfTemplate template: fieldTemplates) {
PdfFormField.mergeResources(dr, (PdfDictionary)template.getResources());
}
// if (dr.get(PdfName.ENCODING) == null) dr.put(PdfName.ENCODING, PdfName.WIN_ANSI_ENCODING);
PdfDictionary fonts = dr.getAsDict(PdfName.FONT);
if (fonts == null) {
fonts = new PdfDictionary();
dr.put(PdfName.FONT, fonts);
}
if (!fonts.contains(PdfName.HELV)) {
PdfDictionary dic = new PdfDictionary(PdfName.FONT);
dic.put(PdfName.BASEFONT, PdfName.HELVETICA);
dic.put(PdfName.ENCODING, PdfName.WIN_ANSI_ENCODING);
dic.put(PdfName.NAME, PdfName.HELV);
dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
fonts.put(PdfName.HELV, addToBody(dic).getIndirectReference());
}
if (!fonts.contains(PdfName.ZADB)) {
PdfDictionary dic = new PdfDictionary(PdfName.FONT);
dic.put(PdfName.BASEFONT, PdfName.ZAPFDINGBATS);
dic.put(PdfName.NAME, PdfName.ZADB);
dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
fonts.put(PdfName.ZADB, addToBody(dic).getIndirectReference());
}
}
/**
* Signals that the Document
was closed and that no other
* Elements
will be added.
*
* The pages-tree is built and written to the outputstream.
* A Catalog is constructed, as well as an Info-object,
* the reference table is composed and everything is written
* to the outputstream embedded in a Trailer.
*/
@Override
public void close() {
if (open) {
pdf.close();
super.close();
// Users are responsible for closing PdfReader
// if (ri != null) {
// try {
// ri.getReader().close();
// ri.getReaderFile().close();
// }
// catch (IOException ioe) {
// // empty on purpose
// }
// }
}
}
public PdfIndirectReference add(PdfOutline outline) { return null; }
@Override
public void addAnnotation(PdfAnnotation annot) { }
@Override
PdfIndirectReference add(PdfPage page, PdfContents contents) throws PdfException { return null; }
@Override
public void freeReader(PdfReader reader) throws IOException {
if (mergeFields)
throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("it.is.not.possible.to.free.reader.in.merge.fields.mode"));
PdfArray array = reader.trailer.getAsArray(PdfName.ID);
if (array != null)
originalFileID = array.getAsString(0).getBytes();
indirectMap.remove(reader);
// TODO: Removed - the user should be responsible for closing all PdfReaders. But, this could cause a lot of memory leaks in code out there that hasn't been properly closing things - maybe add a finalizer to PdfReader that calls PdfReader#close() ??
// if (currentPdfReaderInstance != null) {
// if (currentPdfReaderInstance.getReader() == reader) {
// try {
// currentPdfReaderInstance.getReader().close();
// currentPdfReaderInstance.getReaderFile().close();
// }
// catch (IOException ioe) {
// // empty on purpose
// }
currentPdfReaderInstance = null;
// }
// }
super.freeReader(reader);
}
protected PdfName getOffStateName(PdfDictionary widget) {
return PdfName.Off;
}
protected static final HashSet widgetKeys = new HashSet();
protected static final HashSet fieldKeys = new HashSet();
static {
widgetKeys.add(PdfName.SUBTYPE);
widgetKeys.add(PdfName.CONTENTS);
widgetKeys.add(PdfName.RECT);
widgetKeys.add(PdfName.NM);
widgetKeys.add(PdfName.M);
widgetKeys.add(PdfName.F);
widgetKeys.add(PdfName.BS);
widgetKeys.add(PdfName.BORDER);
widgetKeys.add(PdfName.AP);
widgetKeys.add(PdfName.AS);
widgetKeys.add(PdfName.C);
widgetKeys.add(PdfName.A);
widgetKeys.add(PdfName.STRUCTPARENT);
widgetKeys.add(PdfName.OC);
widgetKeys.add(PdfName.H);
widgetKeys.add(PdfName.MK);
widgetKeys.add(PdfName.DA);
widgetKeys.add(PdfName.Q);
widgetKeys.add(PdfName.P);
widgetKeys.add(PdfName.TYPE);
widgetKeys.add(annotId);
fieldKeys.add(PdfName.AA);
fieldKeys.add(PdfName.FT);
fieldKeys.add(PdfName.TU);
fieldKeys.add(PdfName.TM);
fieldKeys.add(PdfName.FF);
fieldKeys.add(PdfName.V);
fieldKeys.add(PdfName.DV);
fieldKeys.add(PdfName.DS);
fieldKeys.add(PdfName.RV);
fieldKeys.add(PdfName.OPT);
fieldKeys.add(PdfName.MAXLEN);
fieldKeys.add(PdfName.TI);
fieldKeys.add(PdfName.I);
fieldKeys.add(PdfName.LOCK);
fieldKeys.add(PdfName.SV);
}
static Integer getFlags(PdfDictionary field) {
PdfName type = field.getAsName(PdfName.FT);
if (!PdfName.BTN.equals(type))
return null;
PdfNumber flags = field.getAsNumber(PdfName.FF);
if (flags == null)
return null;
return flags.intValue();
}
static boolean isCheckButton(PdfDictionary field) {
Integer flags = getFlags(field);
return flags == null || ((flags.intValue() & PdfFormField.FF_PUSHBUTTON) == 0 && (flags.intValue() & PdfFormField.FF_RADIO) == 0);
}
static boolean isRadioButton(PdfDictionary field) {
Integer flags = getFlags(field);
return flags != null && (flags.intValue() & PdfFormField.FF_PUSHBUTTON) == 0 && (flags.intValue() & PdfFormField.FF_RADIO) != 0;
}
static boolean isTextField(PdfDictionary field) {
PdfName type = field.getAsName(PdfName.FT);
return PdfName.TX.equals(type);
}
/**
* Create a page stamp. New content and annotations, including new fields, are allowed.
* The fields added cannot have parents in another pages. This method modifies the PdfReader instance.
* The general usage to stamp something in a page is:
*
*
* PdfImportedPage page = copy.getImportedPage(reader, 1);
* PdfCopy.PageStamp ps = copy.createPageStamp(page);
* ps.addAnnotation(PdfAnnotation.createText(copy, new Rectangle(50, 180, 70, 200), "Hello", "No Thanks", true, "Comment"));
* PdfContentByte under = ps.getUnderContent();
* under.addImage(img);
* PdfContentByte over = ps.getOverContent();
* over.beginText();
* over.setFontAndSize(bf, 18);
* over.setTextMatrix(30, 30);
* over.showText("total page " + totalPage);
* over.endText();
* ps.alterContents();
* copy.addPage(page);
*
* @param iPage an imported page
* @return the PageStamp
*/
public PageStamp createPageStamp(PdfImportedPage iPage) {
int pageNum = iPage.getPageNumber();
PdfReader reader = iPage.getPdfReaderInstance().getReader();
if (isTagged())
throw new RuntimeException(MessageLocalization.getComposedMessage("creating.page.stamp.not.allowed.for.tagged.reader"));
PdfDictionary pageN = reader.getPageN(pageNum);
return new PageStamp(reader, pageN, this);
}
public static class PageStamp {
PdfDictionary pageN;
PdfCopy.StampContent under;
PdfCopy.StampContent over;
PageResources pageResources;
PdfReader reader;
PdfCopy cstp;
PageStamp(PdfReader reader, PdfDictionary pageN, PdfCopy cstp) {
this.pageN = pageN;
this.reader = reader;
this.cstp = cstp;
}
public PdfContentByte getUnderContent(){
if (under == null) {
if (pageResources == null) {
pageResources = new PageResources();
PdfDictionary resources = pageN.getAsDict(PdfName.RESOURCES);
pageResources.setOriginalResources(resources, cstp.namePtr);
}
under = new PdfCopy.StampContent(cstp, pageResources);
}
return under;
}
public PdfContentByte getOverContent(){
if (over == null) {
if (pageResources == null) {
pageResources = new PageResources();
PdfDictionary resources = pageN.getAsDict(PdfName.RESOURCES);
pageResources.setOriginalResources(resources, cstp.namePtr);
}
over = new PdfCopy.StampContent(cstp, pageResources);
}
return over;
}
public void alterContents() throws IOException {
if (over == null && under == null)
return;
PdfArray ar = null;
PdfObject content = PdfReader.getPdfObject(pageN.get(PdfName.CONTENTS), pageN);
if (content == null) {
ar = new PdfArray();
pageN.put(PdfName.CONTENTS, ar);
} else if (content.isArray()) {
ar = (PdfArray)content;
} else if (content.isStream()) {
ar = new PdfArray();
ar.add(pageN.get(PdfName.CONTENTS));
pageN.put(PdfName.CONTENTS, ar);
} else {
ar = new PdfArray();
pageN.put(PdfName.CONTENTS, ar);
}
ByteBuffer out = new ByteBuffer();
if (under != null) {
out.append(PdfContents.SAVESTATE);
applyRotation(pageN, out);
out.append(under.getInternalBuffer());
out.append(PdfContents.RESTORESTATE);
}
if (over != null)
out.append(PdfContents.SAVESTATE);
PdfStream stream = new PdfStream(out.toByteArray());
stream.flateCompress(cstp.getCompressionLevel());
PdfIndirectReference ref1 = cstp.addToBody(stream).getIndirectReference();
ar.addFirst(ref1);
out.reset();
if (over != null) {
out.append(' ');
out.append(PdfContents.RESTORESTATE);
out.append(PdfContents.SAVESTATE);
applyRotation(pageN, out);
out.append(over.getInternalBuffer());
out.append(PdfContents.RESTORESTATE);
stream = new PdfStream(out.toByteArray());
stream.flateCompress(cstp.getCompressionLevel());
ar.add(cstp.addToBody(stream).getIndirectReference());
}
pageN.put(PdfName.RESOURCES, pageResources.getResources());
}
void applyRotation(PdfDictionary pageN, ByteBuffer out) {
if (!cstp.rotateContents)
return;
Rectangle page = reader.getPageSizeWithRotation(pageN);
int rotation = page.getRotation();
switch (rotation) {
case 90:
out.append(PdfContents.ROTATE90);
out.append(page.getTop());
out.append(' ').append('0').append(PdfContents.ROTATEFINAL);
break;
case 180:
out.append(PdfContents.ROTATE180);
out.append(page.getRight());
out.append(' ');
out.append(page.getTop());
out.append(PdfContents.ROTATEFINAL);
break;
case 270:
out.append(PdfContents.ROTATE270);
out.append('0').append(' ');
out.append(page.getRight());
out.append(PdfContents.ROTATEFINAL);
break;
}
}
private void addDocumentField(PdfIndirectReference ref) {
if (cstp.fieldArray == null)
cstp.fieldArray = new PdfArray();
cstp.fieldArray.add(ref);
}
private void expandFields(PdfFormField field, ArrayList allAnnots) {
allAnnots.add(field);
ArrayList kids = field.getKids();
if (kids != null) {
for (PdfFormField f : kids)
expandFields(f, allAnnots);
}
}
public void addAnnotation(PdfAnnotation annot) {
try {
ArrayList allAnnots = new ArrayList();
if (annot.isForm()) {
PdfFormField field = (PdfFormField)annot;
if (field.getParent() != null)
return;
expandFields(field, allAnnots);
if (cstp.fieldTemplates == null)
cstp.fieldTemplates = new HashSet();
}
else
allAnnots.add(annot);
for (int k = 0; k < allAnnots.size(); ++k) {
annot = allAnnots.get(k);
if (annot.isForm()) {
if (!annot.isUsed()) {
HashSet templates = annot.getTemplates();
if (templates != null)
cstp.fieldTemplates.addAll(templates);
}
PdfFormField field = (PdfFormField)annot;
if (field.getParent() == null)
addDocumentField(field.getIndirectReference());
}
if (annot.isAnnotation()) {
PdfObject pdfobj = PdfReader.getPdfObject(pageN.get(PdfName.ANNOTS), pageN);
PdfArray annots = null;
if (pdfobj == null || !pdfobj.isArray()) {
annots = new PdfArray();
pageN.put(PdfName.ANNOTS, annots);
}
else
annots = (PdfArray)pdfobj;
annots.add(annot.getIndirectReference());
if (!annot.isUsed()) {
PdfRectangle rect = (PdfRectangle)annot.get(PdfName.RECT);
if (rect != null && (rect.left() != 0 || rect.right() != 0 || rect.top() != 0 || rect.bottom() != 0)) {
int rotation = reader.getPageRotation(pageN);
Rectangle pageSize = reader.getPageSizeWithRotation(pageN);
switch (rotation) {
case 90:
annot.put(PdfName.RECT, new PdfRectangle(
pageSize.getTop() - rect.bottom(),
rect.left(),
pageSize.getTop() - rect.top(),
rect.right()));
break;
case 180:
annot.put(PdfName.RECT, new PdfRectangle(
pageSize.getRight() - rect.left(),
pageSize.getTop() - rect.bottom(),
pageSize.getRight() - rect.right(),
pageSize.getTop() - rect.top()));
break;
case 270:
annot.put(PdfName.RECT, new PdfRectangle(
rect.bottom(),
pageSize.getRight() - rect.left(),
rect.top(),
pageSize.getRight() - rect.right()));
break;
}
}
}
}
if (!annot.isUsed()) {
annot.setUsed();
cstp.addToBody(annot, annot.getIndirectReference());
}
}
}
catch (IOException e) {
throw new ExceptionConverter(e);
}
}
}
public static class StampContent extends PdfContentByte {
PageResources pageResources;
/** Creates a new instance of StampContent */
StampContent(PdfWriter writer, PageResources pageResources) {
super(writer);
this.pageResources = pageResources;
}
/**
* Gets a duplicate of this PdfContentByte
. All
* the members are copied by reference but the buffer stays different.
*
* @return a copy of this PdfContentByte
*/
@Override
public PdfContentByte getDuplicate() {
return new PdfCopy.StampContent(writer, pageResources);
}
@Override
PageResources getPageResources() {
return pageResources;
}
}
}