iText 5 itextpdf.jar Source Code

itextpdf.jar is a component in iText 5 Java library to provide core functionalities. iText Java library allows you to generate and manage PDF documents.

The Source Code files are provided at iText GitHub site.

You can compile it to generate your JAR file, using pom.xml as the build configuration file.

The source code of itextpdf-5.5.14.jar is provided below:

✍: FYIcenter.com

com/itextpdf/text/pdf/PdfStamperImp.java

/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2020 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: sales@itextpdf.com
 */
package com.itextpdf.text.pdf;

import com.itextpdf.awt.geom.AffineTransform;
import com.itextpdf.awt.geom.Point;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.Version;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.exceptions.BadPasswordException;
import com.itextpdf.text.io.RandomAccessSource;
import com.itextpdf.text.io.RandomAccessSourceFactory;
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 com.itextpdf.text.pdf.AcroFields.Item;
import com.itextpdf.text.pdf.collection.PdfCollection;
import com.itextpdf.text.pdf.interfaces.PdfViewerPreferences;
import com.itextpdf.text.pdf.internal.PdfIsoKeys;
import com.itextpdf.text.pdf.internal.PdfViewerPreferencesImp;
import com.itextpdf.text.xml.xmp.PdfProperties;
import com.itextpdf.text.xml.xmp.XmpBasicProperties;
import com.itextpdf.text.xml.xmp.XmpWriter;
import com.itextpdf.xmp.XMPException;
import com.itextpdf.xmp.XMPMeta;
import com.itextpdf.xmp.XMPMetaFactory;
import com.itextpdf.xmp.options.SerializeOptions;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

class PdfStamperImp extends PdfWriter {
    HashMap<PdfReader, IntHashtable> readers2intrefs = new HashMap<PdfReader, IntHashtable>();
    HashMap<PdfReader, RandomAccessFileOrArray> readers2file = new HashMap<PdfReader, RandomAccessFileOrArray>();
    protected RandomAccessFileOrArray file;
    PdfReader reader;
    IntHashtable myXref = new IntHashtable();
    /**
     * Integer(page number) -> PageStamp
     */
    HashMap<PdfDictionary, PageStamp> pagesToContent = new HashMap<PdfDictionary, PageStamp>();
    protected boolean closed = false;
    /**
     * Holds value of property rotateContents.
     */
    private boolean rotateContents = true;
    protected AcroFields acroFields;
    protected boolean flat = false;
    protected boolean flatFreeText = false;
    protected boolean flatannotations = false;
    protected int namePtr[] = {0};
    protected HashSet<String> partialFlattening = new HashSet<String>();
    protected boolean useVp = false;
    protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
    protected HashSet<PdfTemplate> fieldTemplates = new HashSet<PdfTemplate>();
    protected boolean fieldsAdded = false;
    protected int sigFlags = 0;
    protected boolean append;
    protected IntHashtable marked;
    protected int initialXrefSize;
    protected PdfAction openAction;
    protected HashMap<Object, PdfObject> namedDestinations = new HashMap<Object, PdfObject>();

    protected Counter COUNTER = CounterFactory.getCounter(PdfStamper.class);
    private Logger logger;

    protected Counter getCounter() {
        return COUNTER;
    }

    /* Flag which defines if PdfLayer objects from existing pdf have been already read.
     * If no new layers were registered and user didn't fetched layers explicitly via getPdfLayers() method
     * then original layers are never read - they are simply copied to the new document with whole original catalog. */
    private boolean originalLayersAreRead = false;

    //Hash map of standard fonts used in flattening of annotations to prevent fonts duplication
    private HashMap<String, PdfIndirectReference> builtInAnnotationFonts = new HashMap<String, PdfIndirectReference>();
    private static HashMap<String, String> fromShortToFullAnnotationFontNames = new HashMap<String, String>();

    static {
        fromShortToFullAnnotationFontNames.put("CoBO", BaseFont.COURIER_BOLDOBLIQUE);
        fromShortToFullAnnotationFontNames.put("CoBo", BaseFont.COURIER_BOLD);
        fromShortToFullAnnotationFontNames.put("CoOb", BaseFont.COURIER_OBLIQUE);
        fromShortToFullAnnotationFontNames.put("Cour", BaseFont.COURIER);
        fromShortToFullAnnotationFontNames.put("HeBO", BaseFont.HELVETICA_BOLDOBLIQUE);
        fromShortToFullAnnotationFontNames.put("HeBo", BaseFont.HELVETICA_BOLD);
        fromShortToFullAnnotationFontNames.put("HeOb", BaseFont.HELVETICA_OBLIQUE);
        fromShortToFullAnnotationFontNames.put("Helv", BaseFont.HELVETICA);
        fromShortToFullAnnotationFontNames.put("Symb", BaseFont.SYMBOL);
        fromShortToFullAnnotationFontNames.put("TiBI", BaseFont.TIMES_BOLDITALIC);
        fromShortToFullAnnotationFontNames.put("TiBo", BaseFont.TIMES_BOLD);
        fromShortToFullAnnotationFontNames.put("TiIt", BaseFont.TIMES_ITALIC);
        fromShortToFullAnnotationFontNames.put("TiRo", BaseFont.TIMES_ROMAN);
        fromShortToFullAnnotationFontNames.put("ZaDb", BaseFont.ZAPFDINGBATS);
    }

    private double[] DEFAULT_MATRIX = {1, 0, 0, 1, 0, 0};

    /**
     * Creates new PdfStamperImp.
     *
     * @param reader     the read PDF
     * @param os         the output destination
     * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
     *                   document
     * @param append
     * @throws DocumentException on error
     * @throws IOException
     */
    protected PdfStamperImp(PdfReader reader, OutputStream os, char pdfVersion, boolean append) throws DocumentException, IOException {
        super(new PdfDocument(), os);
        this.logger = LoggerFactory.getLogger(PdfStamperImp.class);
        if (!reader.isOpenedWithFullPermissions())
            throw new BadPasswordException(MessageLocalization.getComposedMessage("pdfreader.not.opened.with.owner.password"));
        if (reader.isTampered())
            throw new DocumentException(MessageLocalization.getComposedMessage("the.original.document.was.reused.read.it.again.from.file"));
        reader.setTampered(true);
        this.reader = reader;
        file = reader.getSafeFile();
        this.append = append;
        if (reader.isEncrypted() && (append || PdfReader.unethicalreading)) {
            crypto = new PdfEncryption(reader.getDecrypt());
        }
        if (append) {
            if (reader.isRebuilt())
                throw new DocumentException(MessageLocalization.getComposedMessage("append.mode.requires.a.document.without.errors.even.if.recovery.was.possible"));
            pdf_version.setAppendmode(true);
            if ( pdfVersion == 0 ) {
                pdf_version.setPdfVersion(reader.getPdfVersion());
            } else {
                pdf_version.setPdfVersion(pdfVersion);
            }
            byte buf[] = new byte[8192];
            int n;
            while ((n = file.read(buf)) > 0)
                this.os.write(buf, 0, n);
            prevxref = reader.getLastXref();
            reader.setAppendable(true);
        } else {
            if (pdfVersion == 0)
                super.setPdfVersion(reader.getPdfVersion());
            else
                super.setPdfVersion(pdfVersion);
        }

        if (reader.isTagged()) {
            this.setTagged();
        }

        super.open();
        pdf.addWriter(this);
        if (append) {
            body.setRefnum(reader.getXrefSize());
            marked = new IntHashtable();
            if (reader.isNewXrefType())
                fullCompression = true;
            if (reader.isHybridXref())
                fullCompression = false;
        }
        initialXrefSize = reader.getXrefSize();
        readColorProfile();
    }

    protected void readColorProfile() {
        PdfObject outputIntents = reader.getCatalog().getAsArray(PdfName.OUTPUTINTENTS);
        if (outputIntents != null && ((PdfArray) outputIntents).size() > 0) {
            PdfStream iccProfileStream = null;
            for (int i = 0; i < ((PdfArray) outputIntents).size(); i++) {
                PdfDictionary outputIntentDictionary = ((PdfArray) outputIntents).getAsDict(i);
                if (outputIntentDictionary != null) {
                    iccProfileStream = outputIntentDictionary.getAsStream(PdfName.DESTOUTPUTPROFILE);
                    if (iccProfileStream != null)
                        break;
                }
            }

            if (iccProfileStream instanceof PRStream) {
                try {
                    colorProfile = ICC_Profile.getInstance(PdfReader.getStreamBytes((PRStream) iccProfileStream));
                } catch (IOException exc) {
                    throw new ExceptionConverter(exc);
                }
            }
        }
    }

    protected void setViewerPreferences() {
        reader.setViewerPreferences(viewerPreferences);
        markUsed(reader.getTrailer().get(PdfName.ROOT));
    }

    protected void close(Map<String, String> moreInfo) throws IOException {
        if (closed) {
            return;
        }
        if (useVp) {
            setViewerPreferences();
        }
        if (flat) {
            flatFields();
        }
        if (flatFreeText) {
            flatFreeTextFields();
        }
        if (flatannotations) {
            flattenAnnotations();
        }
        addFieldResources();
        PdfDictionary catalog = reader.getCatalog();
        getPdfVersion().addToCatalog(catalog);
        PdfDictionary acroForm = (PdfDictionary) PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), reader.getCatalog());
        if (acroFields != null && acroFields.getXfa().isChanged()) {
            markUsed(acroForm);
            if (!flat) {
                acroFields.getXfa().setXfa(this);
            }
        }
        if (sigFlags != 0) {
            if (acroForm != null) {
                acroForm.put(PdfName.SIGFLAGS, new PdfNumber(sigFlags));
                markUsed(acroForm);
                markUsed(catalog);
            }
        }
        closed = true;
        addSharedObjectsToBody();
        setOutlines();
        setJavaScript();
        addFileAttachments();
        // [C11] Output Intents
        if (extraCatalog != null) {
            catalog.mergeDifferent(extraCatalog);
        }
        if (openAction != null) {
            catalog.put(PdfName.OPENACTION, openAction);
        }
        if (pdf.pageLabels != null) {
            catalog.put(PdfName.PAGELABELS, pdf.pageLabels.getDictionary(this));
        }
        // OCG
        if (!documentOCG.isEmpty()) {
            fillOCProperties(false);
            PdfDictionary ocdict = catalog.getAsDict(PdfName.OCPROPERTIES);
            if (ocdict == null) {
                reader.getCatalog().put(PdfName.OCPROPERTIES, OCProperties);
            } else {
                ocdict.put(PdfName.OCGS, OCProperties.get(PdfName.OCGS));
                PdfDictionary ddict = ocdict.getAsDict(PdfName.D);
                if (ddict == null) {
                    ddict = new PdfDictionary();
                    ocdict.put(PdfName.D, ddict);
                }
                ddict.put(PdfName.ORDER, OCProperties.getAsDict(PdfName.D).get(PdfName.ORDER));
                ddict.put(PdfName.RBGROUPS, OCProperties.getAsDict(PdfName.D).get(PdfName.RBGROUPS));
                ddict.put(PdfName.OFF, OCProperties.getAsDict(PdfName.D).get(PdfName.OFF));
                ddict.put(PdfName.AS, OCProperties.getAsDict(PdfName.D).get(PdfName.AS));
            }
            PdfWriter.checkPdfIsoConformance(this, PdfIsoKeys.PDFISOKEY_LAYER, OCProperties);
        }
        // metadata
        int skipInfo = -1;
        PdfIndirectReference iInfo = reader.getTrailer().getAsIndirectObject(PdfName.INFO);
        if (iInfo != null) {
            skipInfo = iInfo.getNumber();
        }
        PdfDictionary oldInfo = reader.getTrailer().getAsDict(PdfName.INFO);
        String producer = null;
        if (oldInfo != null && oldInfo.get(PdfName.PRODUCER) != null) {
            producer = oldInfo.getAsString(PdfName.PRODUCER).toUnicodeString();
        }
        Version version = Version.getInstance();
        if (producer == null || version.getVersion().indexOf(version.getProduct()) == -1) {
            producer = version.getVersion();
        } else {
            int idx = producer.indexOf("; modified using");
            StringBuffer buf;
            if (idx == -1)
                buf = new StringBuffer(producer);
            else
                buf = new StringBuffer(producer.substring(0, idx));
            buf.append("; modified using ");
            buf.append(version.getVersion());
            producer = buf.toString();
        }
        PdfIndirectReference info = null;
        PdfDictionary newInfo = new PdfDictionary();
        if (oldInfo != null) {
            for (Object element : oldInfo.getKeys()) {
                PdfName key = (PdfName) element;
                PdfObject value = oldInfo.get(key);
                newInfo.put(key, value);
            }
        }
        if (moreInfo != null) {
            for (Map.Entry<String, String> entry : moreInfo.entrySet()) {
                String key = entry.getKey();
                PdfName keyName = new PdfName(key);
                String value = entry.getValue();
                if (value == null)
                    newInfo.remove(keyName);
                else
                    newInfo.put(keyName, new PdfString(value, PdfObject.TEXT_UNICODE));
            }
        }
        PdfDate date = new PdfDate();
        newInfo.put(PdfName.MODDATE, date);
        newInfo.put(PdfName.PRODUCER, new PdfString(producer, PdfObject.TEXT_UNICODE));
        if (append) {
            if (iInfo == null) {
                info = addToBody(newInfo, false).getIndirectReference();
            } else {
                info = addToBody(newInfo, iInfo.getNumber(), false).getIndirectReference();
            }
        } else {
            info = addToBody(newInfo, false).getIndirectReference();
        }
        // XMP
        byte[] altMetadata = null;
        PdfObject xmpo = PdfReader.getPdfObject(catalog.get(PdfName.METADATA));
        if (xmpo != null && xmpo.isStream()) {
            altMetadata = PdfReader.getStreamBytesRaw((PRStream) xmpo);
            PdfReader.killIndirect(catalog.get(PdfName.METADATA));
        }
        PdfStream xmp = null;
        if (xmpMetadata != null) {
            altMetadata = xmpMetadata;
        } else if (xmpWriter != null) {
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                PdfProperties.setProducer(xmpWriter.getXmpMeta(), producer);
                XmpBasicProperties.setModDate(xmpWriter.getXmpMeta(), date.getW3CDate());
                XmpBasicProperties.setMetaDataDate(xmpWriter.getXmpMeta(), date.getW3CDate());
                xmpWriter.serialize(baos);
                xmpWriter.close();
                xmp = new PdfStream(baos.toByteArray());
            } catch (XMPException exc) {
                xmpWriter = null;
            }
        }
        if (xmp == null && altMetadata != null) {
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                if (moreInfo == null || xmpMetadata != null) {
                    XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(altMetadata);

                    PdfProperties.setProducer(xmpMeta, producer);
                    XmpBasicProperties.setModDate(xmpMeta, date.getW3CDate());
                    XmpBasicProperties.setMetaDataDate(xmpMeta, date.getW3CDate());

                    SerializeOptions serializeOptions = new SerializeOptions();
                    serializeOptions.setPadding(2000);
                    XMPMetaFactory.serialize(xmpMeta, baos, serializeOptions);
                } else {
                    XmpWriter xmpw = createXmpWriter(baos, newInfo);
                    xmpw.close();
                }
                xmp = new PdfStream(baos.toByteArray());
            } catch (XMPException e) {
                xmp = new PdfStream(altMetadata);
            } catch (IOException e) {
                xmp = new PdfStream(altMetadata);
            }
        }
        if (xmp != null) {
            xmp.put(PdfName.TYPE, PdfName.METADATA);
            xmp.put(PdfName.SUBTYPE, PdfName.XML);
            if (crypto != null && !crypto.isMetadataEncrypted()) {
                PdfArray ar = new PdfArray();
                ar.add(PdfName.CRYPT);
                xmp.put(PdfName.FILTER, ar);
            }
            if (append && xmpo != null) {
                body.add(xmp, xmpo.getIndRef());
            } else {
                catalog.put(PdfName.METADATA, body.add(xmp).getIndirectReference());
                markUsed(catalog);
            }
        }

        if (!namedDestinations.isEmpty())
            updateNamedDestinations();
        close(info, skipInfo);
    }

    protected void close(PdfIndirectReference info, int skipInfo) throws IOException {
        alterContents();
        int rootN = ((PRIndirectReference) reader.trailer.get(PdfName.ROOT)).getNumber();
        if (append) {
            int keys[] = marked.getKeys();
            for (int k = 0; k < keys.length; ++k) {
                int j = keys[k];
                PdfObject obj = reader.getPdfObjectRelease(j);
                if (obj != null && skipInfo != j && j < initialXrefSize) {
                    addToBody(obj, obj.getIndRef(), j != rootN);
                }
            }
            for (int k = initialXrefSize; k < reader.getXrefSize(); ++k) {
                PdfObject obj = reader.getPdfObject(k);
                if (obj != null) {
                    addToBody(obj, getNewObjectNumber(reader, k, 0));
                }
            }
        } else {
            for (int k = 1; k < reader.getXrefSize(); ++k) {
                PdfObject obj = reader.getPdfObjectRelease(k);
                if (obj != null && skipInfo != k) {
                    addToBody(obj, getNewObjectNumber(reader, k, 0), k != rootN);
                }
            }
        }

        PdfIndirectReference encryption = null;
        PdfObject fileID = null;
        if (crypto != null) {
            if (append) {
                encryption = reader.getCryptoRef();
            } else {
                PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false);
                encryption = encryptionObject.getIndirectReference();
            }
            fileID = crypto.getFileID(true);
        } else {
            PdfArray IDs = reader.trailer.getAsArray(PdfName.ID);
            if (IDs != null && IDs.getAsString(0) != null) {
                fileID = PdfEncryption.createInfoId(IDs.getAsString(0).getBytes(), true);
            } else {
                fileID = PdfEncryption.createInfoId(PdfEncryption.createDocumentId(), true);
            }
        }
        PRIndirectReference iRoot = (PRIndirectReference) reader.trailer.get(PdfName.ROOT);
        PdfIndirectReference root = new PdfIndirectReference(0, getNewObjectNumber(reader, iRoot.getNumber(), 0));
        // write the cross-reference table of the body
        body.writeCrossReferenceTable(os, root, info, encryption, fileID, prevxref);
        if (fullCompression) {
            writeKeyInfo(os);
            os.write(getISOBytes("startxref\n"));
            os.write(getISOBytes(String.valueOf(body.offset())));
            os.write(getISOBytes("\n%%EOF\n"));
        } else {
            PdfTrailer trailer = new PdfTrailer(body.size(),
                    body.offset(),
                    root,
                    info,
                    encryption,
                    fileID, prevxref);
            trailer.toPdf(this, os);
        }
        os.flush();
        if (isCloseStream())
            os.close();
        getCounter().written(os.getCounter());
    }

    void applyRotation(PdfDictionary pageN, ByteBuffer out) {
        if (!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;
        }
    }

    protected void alterContents() throws IOException {
        for (Object element : pagesToContent.values()) {
            PageStamp ps = (PageStamp) element;
            PdfDictionary pageN = ps.pageN;
            markUsed(pageN);
            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 = new PdfArray((PdfArray) content);
                pageN.put(PdfName.CONTENTS, ar);
            } 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 (ps.under != null) {
                out.append(PdfContents.SAVESTATE);
                applyRotation(pageN, out);
                out.append(ps.under.getInternalBuffer());
                out.append(PdfContents.RESTORESTATE);
            }
            if (ps.over != null)
                out.append(PdfContents.SAVESTATE);
            PdfStream stream = new PdfStream(out.toByteArray());
            stream.flateCompress(compressionLevel);
            ar.addFirst(addToBody(stream).getIndirectReference());
            out.reset();
            if (ps.over != null) {
                out.append(' ');
                out.append(PdfContents.RESTORESTATE);
                ByteBuffer buf = ps.over.getInternalBuffer();
                out.append(buf.getBuffer(), 0, ps.replacePoint);
                out.append(PdfContents.SAVESTATE);
                applyRotation(pageN, out);
                out.append(buf.getBuffer(), ps.replacePoint, buf.size() - ps.replacePoint);
                out.append(PdfContents.RESTORESTATE);
                stream = new PdfStream(out.toByteArray());
                stream.flateCompress(compressionLevel);
                ar.add(addToBody(stream).getIndirectReference());
            }
            alterResources(ps);
        }
    }

    void alterResources(PageStamp ps) {
        ps.pageN.put(PdfName.RESOURCES, ps.pageResources.getResources());
    }

    @Override
    protected int getNewObjectNumber(PdfReader reader, int number, int generation) {
        IntHashtable ref = readers2intrefs.get(reader);
        if (ref != null) {
            int n = ref.get(number);
            if (n == 0) {
                n = getIndirectReferenceNumber();
                ref.put(number, n);
            }
            return n;
        }
        if (currentPdfReaderInstance == null) {
            if (append && number < initialXrefSize)
                return number;
            int n = myXref.get(number);
            if (n == 0) {
                n = getIndirectReferenceNumber();
                myXref.put(number, n);
            }
            return n;
        } else
            return currentPdfReaderInstance.getNewObjectNumber(number, generation);
    }

    @Override
    RandomAccessFileOrArray getReaderFile(PdfReader reader) {
        if (readers2intrefs.containsKey(reader)) {
            RandomAccessFileOrArray raf = readers2file.get(reader);
            if (raf != null)
                return raf;
            return reader.getSafeFile();
        }
        if (currentPdfReaderInstance == null)
            return file;
        else
            return currentPdfReaderInstance.getReaderFile();
    }

    /**
     * @param reader
     * @param openFile
     * @throws IOException
     */
    public void registerReader(PdfReader reader, boolean openFile) throws IOException {
        if (readers2intrefs.containsKey(reader))
            return;
        readers2intrefs.put(reader, new IntHashtable());
        if (openFile) {
            RandomAccessFileOrArray raf = reader.getSafeFile();
            readers2file.put(reader, raf);
            raf.reOpen();
        }
    }

    /**
     * @param reader
     */
    public void unRegisterReader(PdfReader reader) {
        if (!readers2intrefs.containsKey(reader))
            return;
        readers2intrefs.remove(reader);
        RandomAccessFileOrArray raf = readers2file.get(reader);
        if (raf == null)
            return;
        readers2file.remove(reader);
        try {
            raf.close();
        } catch (Exception e) {
        }
    }

    static void findAllObjects(PdfReader reader, PdfObject obj, IntHashtable hits) {
        if (obj == null)
            return;
        switch (obj.type()) {
            case PdfObject.INDIRECT:
                PRIndirectReference iref = (PRIndirectReference) obj;
                if (reader != iref.getReader())
                    return;
                if (hits.containsKey(iref.getNumber()))
                    return;
                hits.put(iref.getNumber(), 1);
                findAllObjects(reader, PdfReader.getPdfObject(obj), hits);
                return;
            case PdfObject.ARRAY:
                PdfArray a = (PdfArray) obj;
                for (int k = 0; k < a.size(); ++k) {
                    findAllObjects(reader, a.getPdfObject(k), hits);
                }
                return;
            case PdfObject.DICTIONARY:
            case PdfObject.STREAM:
                PdfDictionary dic = (PdfDictionary) obj;
                for (Object element : dic.getKeys()) {
                    PdfName name = (PdfName) element;
                    findAllObjects(reader, dic.get(name), hits);
                }
                return;
        }
    }

    /**
     * @param fdf
     * @throws IOException
     */
    public void addComments(FdfReader fdf) throws IOException {
        if (readers2intrefs.containsKey(fdf))
            return;
        PdfDictionary catalog = fdf.getCatalog();
        catalog = catalog.getAsDict(PdfName.FDF);
        if (catalog == null)
            return;
        PdfArray annots = catalog.getAsArray(PdfName.ANNOTS);
        if (annots == null || annots.size() == 0)
            return;
        registerReader(fdf, false);
        IntHashtable hits = new IntHashtable();
        HashMap<String, PdfObject> irt = new HashMap<String, PdfObject>();
        ArrayList<PdfObject> an = new ArrayList<PdfObject>();
        for (int k = 0; k < annots.size(); ++k) {
            PdfObject obj = annots.getPdfObject(k);
            PdfDictionary annot = (PdfDictionary) PdfReader.getPdfObject(obj);
            PdfNumber page = annot.getAsNumber(PdfName.PAGE);
            if (page == null || page.intValue() >= reader.getNumberOfPages())
                continue;
            findAllObjects(fdf, obj, hits);
            an.add(obj);
            if (obj.type() == PdfObject.INDIRECT) {
                PdfObject nm = PdfReader.getPdfObject(annot.get(PdfName.NM));
                if (nm != null && nm.type() == PdfObject.STRING)
                    irt.put(nm.toString(), obj);
            }
        }
        int arhits[] = hits.getKeys();
        for (int k = 0; k < arhits.length; ++k) {
            int n = arhits[k];
            PdfObject obj = fdf.getPdfObject(n);
            if (obj.type() == PdfObject.DICTIONARY) {
                PdfObject str = PdfReader.getPdfObject(((PdfDictionary) obj).get(PdfName.IRT));
                if (str != null && str.type() == PdfObject.STRING) {
                    PdfObject i = irt.get(str.toString());
                    if (i != null) {
                        PdfDictionary dic2 = new PdfDictionary();
                        dic2.merge((PdfDictionary) obj);
                        dic2.put(PdfName.IRT, i);
                        obj = dic2;
                    }
                }
            }
            addToBody(obj, getNewObjectNumber(fdf, n, 0));
        }
        for (int k = 0; k < an.size(); ++k) {
            PdfObject obj = an.get(k);
            PdfDictionary annot = (PdfDictionary) PdfReader.getPdfObject(obj);
            PdfNumber page = annot.getAsNumber(PdfName.PAGE);
            PdfDictionary dic = reader.getPageN(page.intValue() + 1);
            PdfArray annotsp = (PdfArray) PdfReader.getPdfObject(dic.get(PdfName.ANNOTS), dic);
            if (annotsp == null) {
                annotsp = new PdfArray();
                dic.put(PdfName.ANNOTS, annotsp);
                markUsed(dic);
            }
            markUsed(annotsp);
            annotsp.add(obj);
        }
    }

    PageStamp getPageStamp(int pageNum) {
        PdfDictionary pageN = reader.getPageN(pageNum);
        PageStamp ps = pagesToContent.get(pageN);
        if (ps == null) {
            ps = new PageStamp(this, reader, pageN);
            pagesToContent.put(pageN, ps);
        }
        ps.pageN.setIndRef(reader.getPageOrigRef(pageNum));
        return ps;
    }

    PdfContentByte getUnderContent(int pageNum) {
        if (pageNum < 1 || pageNum > reader.getNumberOfPages())
            return null;
        PageStamp ps = getPageStamp(pageNum);
        if (ps.under == null)
            ps.under = new StampContent(this, ps);
        return ps.under;
    }

    PdfContentByte getOverContent(int pageNum) {
        if (pageNum < 1 || pageNum > reader.getNumberOfPages())
            return null;
        PageStamp ps = getPageStamp(pageNum);
        if (ps.over == null)
            ps.over = new StampContent(this, ps);
        return ps.over;
    }

    void correctAcroFieldPages(int page) {
        if (acroFields == null)
            return;
        if (page > reader.getNumberOfPages())
            return;
        Map<String, Item> fields = acroFields.getFields();
        for (AcroFields.Item item : fields.values()) {
            for (int k = 0; k < item.size(); ++k) {
                int p = item.getPage(k).intValue();
                if (p >= page)
                    item.forcePage(k, p + 1);
            }
        }
    }

    private static void moveRectangle(PdfDictionary dic2, PdfReader r, int pageImported, PdfName key, String name) {
        Rectangle m = r.getBoxSize(pageImported, name);
        if (m == null)
            dic2.remove(key);
        else
            dic2.put(key, new PdfRectangle(m));
    }

    void replacePage(PdfReader r, int pageImported, int pageReplaced) {
        PdfDictionary pageN = reader.getPageN(pageReplaced);
        if (pagesToContent.containsKey(pageN))
            throw new IllegalStateException(MessageLocalization.getComposedMessage("this.page.cannot.be.replaced.new.content.was.already.added"));
        PdfImportedPage p = getImportedPage(r, pageImported);
        PdfDictionary dic2 = reader.getPageNRelease(pageReplaced);
        dic2.remove(PdfName.RESOURCES);
        dic2.remove(PdfName.CONTENTS);
        moveRectangle(dic2, r, pageImported, PdfName.MEDIABOX, "media");
        moveRectangle(dic2, r, pageImported, PdfName.CROPBOX, "crop");
        moveRectangle(dic2, r, pageImported, PdfName.TRIMBOX, "trim");
        moveRectangle(dic2, r, pageImported, PdfName.ARTBOX, "art");
        moveRectangle(dic2, r, pageImported, PdfName.BLEEDBOX, "bleed");
        dic2.put(PdfName.ROTATE, new PdfNumber(r.getPageRotation(pageImported)));
        PdfContentByte cb = getOverContent(pageReplaced);
        cb.addTemplate(p, 0, 0);
        PageStamp ps = pagesToContent.get(pageN);
        ps.replacePoint = ps.over.getInternalBuffer().size();
    }

    void insertPage(int pageNumber, Rectangle mediabox) {
        Rectangle media = new Rectangle(mediabox);
        int rotation = media.getRotation() % 360;
        PdfDictionary page = new PdfDictionary(PdfName.PAGE);
        page.put(PdfName.RESOURCES, new PdfDictionary());
        page.put(PdfName.ROTATE, new PdfNumber(rotation));
        page.put(PdfName.MEDIABOX, new PdfRectangle(media, rotation));
        PRIndirectReference pref = reader.addPdfObject(page);
        PdfDictionary parent;
        PRIndirectReference parentRef;
        if (pageNumber > reader.getNumberOfPages()) {
            PdfDictionary lastPage = reader.getPageNRelease(reader.getNumberOfPages());
            parentRef = (PRIndirectReference) lastPage.get(PdfName.PARENT);
            parentRef = new PRIndirectReference(reader, parentRef.getNumber());
            parent = (PdfDictionary) PdfReader.getPdfObject(parentRef);
            PdfArray kids = (PdfArray) PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
            kids.add(pref);
            markUsed(kids);
            reader.pageRefs.insertPage(pageNumber, pref);
        } else {
            if (pageNumber < 1)
                pageNumber = 1;
            PdfDictionary firstPage = reader.getPageN(pageNumber);
            PRIndirectReference firstPageRef = reader.getPageOrigRef(pageNumber);
            reader.releasePage(pageNumber);
            parentRef = (PRIndirectReference) firstPage.get(PdfName.PARENT);
            parentRef = new PRIndirectReference(reader, parentRef.getNumber());
            parent = (PdfDictionary) PdfReader.getPdfObject(parentRef);
            PdfArray kids = (PdfArray) PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
            int len = kids.size();
            int num = firstPageRef.getNumber();
            for (int k = 0; k < len; ++k) {
                PRIndirectReference cur = (PRIndirectReference) kids.getPdfObject(k);
                if (num == cur.getNumber()) {
                    kids.add(k, pref);
                    break;
                }
            }
            if (len == kids.size())
                throw new RuntimeException(MessageLocalization.getComposedMessage("internal.inconsistence"));
            markUsed(kids);
            reader.pageRefs.insertPage(pageNumber, pref);
            correctAcroFieldPages(pageNumber);
        }
        page.put(PdfName.PARENT, parentRef);
        while (parent != null) {
            markUsed(parent);
            PdfNumber count = (PdfNumber) PdfReader.getPdfObjectRelease(parent.get(PdfName.COUNT));
            parent.put(PdfName.COUNT, new PdfNumber(count.intValue() + 1));
            parent = parent.getAsDict(PdfName.PARENT);
        }
    }

    /**
     * Getter for property rotateContents.
     *
     * @return Value of property rotateContents.
     */
    boolean isRotateContents() {
        return this.rotateContents;
    }

    /**
     * Setter for property rotateContents.
     *
     * @param rotateContents New value of property rotateContents.
     */
    void setRotateContents(boolean rotateContents) {
        this.rotateContents = rotateContents;
    }

    boolean isContentWritten() {
        return body.size() > 1;
    }

    AcroFields getAcroFields() {
        if (acroFields == null) {
            acroFields = new AcroFields(reader, this);
        }
        return acroFields;
    }

    void setFormFlattening(boolean flat) {
        this.flat = flat;
    }

    void setFreeTextFlattening(boolean flat) {
        this.flatFreeText = flat;
    }

    boolean partialFormFlattening(String name) {
        getAcroFields();
        if (acroFields.getXfa().isXfaPresent())
            throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("partial.form.flattening.is.not.supported.with.xfa.forms"));
        if (!acroFields.getFields().containsKey(name))
            return false;
        partialFlattening.add(name);
        return true;
    }

    protected void flatFields() {
        if (append)
            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("field.flattening.is.not.supported.in.append.mode"));
        getAcroFields();
        Map<String, Item> fields = acroFields.getFields();
        if (fieldsAdded && partialFlattening.isEmpty()) {
            for (String s : fields.keySet()) {
                partialFlattening.add(s);
            }
        }
        PdfDictionary acroForm = reader.getCatalog().getAsDict(PdfName.ACROFORM);
        PdfArray acroFds = null;
        if (acroForm != null) {
            acroFds = (PdfArray) PdfReader.getPdfObject(acroForm.get(PdfName.FIELDS), acroForm);
        }
        for (Map.Entry<String, Item> entry : fields.entrySet()) {
            String name = entry.getKey();
            if (!partialFlattening.isEmpty() && !partialFlattening.contains(name))
                continue;
            AcroFields.Item item = entry.getValue();
            for (int k = 0; k < item.size(); ++k) {
                PdfDictionary merged = item.getMerged(k);
                PdfNumber ff = merged.getAsNumber(PdfName.F);
                int flags = 0;
                if (ff != null)
                    flags = ff.intValue();
                int page = item.getPage(k).intValue();
                if (page < 1)
                    continue;
                PdfDictionary appDic = merged.getAsDict(PdfName.AP);
                PdfObject as_n = null;
                if (appDic != null) {
                    as_n = appDic.getDirectObject(PdfName.N);
                    if (as_n == null)
                        as_n = appDic.getAsStream(PdfName.N);
                    if (as_n == null)
                        as_n = appDic.getAsDict(PdfName.N);
                }
                //The rotation can be already applied if the appearance stream was written to document during setField method
                boolean applyRotation = false;
                if (acroFields.isGenerateAppearances()) {
                    if (appDic == null || as_n == null) {
                        try {
                            acroFields.regenerateField(name);
                            appDic = acroFields.getFieldItem(name).getMerged(k).getAsDict(PdfName.AP);
                        }
                        // if we can't create appearances for some reason, we'll just continue
                        catch (IOException e) {
                        } catch (DocumentException e) {
                        }
                    } else if (as_n.isStream()) {
                        PdfStream stream = (PdfStream) as_n;
                        PdfArray bbox = stream.getAsArray(PdfName.BBOX);
                        PdfArray rect = merged.getAsArray(PdfName.RECT);
                        if (bbox != null && rect != null) {
                            applyRotation = true;
                            float rectWidth = rect.getAsNumber(2).floatValue() - rect.getAsNumber(0).floatValue();
                            float bboxWidth = bbox.getAsNumber(2).floatValue() - bbox.getAsNumber(0).floatValue();
                            float rectHeight = rect.getAsNumber(3).floatValue() - rect.getAsNumber(1).floatValue();
                            float bboxHeight = bbox.getAsNumber(3).floatValue() - bbox.getAsNumber(1).floatValue();
                            //Take field rotation into account
                            double fieldRotation = 0;
                            if(merged.getAsDict(PdfName.MK) != null){
                                if(merged.getAsDict(PdfName.MK).get(PdfName.R) != null){
                                    fieldRotation = merged.getAsDict(PdfName.MK).getAsNumber(PdfName.R).floatValue();
                                }
                            }
                            //Cast to radians
                            fieldRotation = fieldRotation * Math.PI/180;
                            //Clamp to [-2*Pi, 2*Pi]
                            fieldRotation = fieldRotation%(2*Math.PI);

                            if(fieldRotation%Math.PI != 0){
                                float temp = rectWidth;
                                rectWidth = rectHeight;
                                rectHeight = temp;
                            }
                            float widthCoef = Math.abs(bboxWidth != 0 ? rectWidth / bboxWidth : Float.MAX_VALUE);
                            float heightCoef = Math.abs(bboxHeight != 0 ? rectHeight / bboxHeight : Float.MAX_VALUE);

                            //Update matrix entry. Any field rotations present here will be overwritten and handled when adding the template to the canvas?
                            NumberArray array = new NumberArray(widthCoef, 0, 0, heightCoef, 0, 0);
                            stream.put(PdfName.MATRIX, array);
                            markUsed(stream);
                        }
                    }
                } else if (appDic != null && as_n != null && (as_n.isStream() || as_n.isDictionary())) {
                    PdfArray bbox = ((PdfDictionary) as_n).getAsArray(PdfName.BBOX);
                    PdfArray rect = merged.getAsArray(PdfName.RECT);
                    if (bbox != null && rect != null) {
                        float widthDiff = (bbox.getAsNumber(2).floatValue() - bbox.getAsNumber(0).floatValue()) -
                                (rect.getAsNumber(2).floatValue() - rect.getAsNumber(0).floatValue());
                        float heightDiff = (bbox.getAsNumber(3).floatValue() - bbox.getAsNumber(1).floatValue()) -
                                (rect.getAsNumber(3).floatValue() - rect.getAsNumber(1).floatValue());
                        if (Math.abs(widthDiff) > 1 || Math.abs(heightDiff) > 1) {
                            try {
                                //simulate Adobe behavior.
                                acroFields.setGenerateAppearances(true);
                                acroFields.regenerateField(name);
                                acroFields.setGenerateAppearances(false);
                                appDic = acroFields.getFieldItem(name).getMerged(k).getAsDict(PdfName.AP);
                            }
                            // if we can't create appearances for some reason, we'll just continue
                            catch (IOException e) {
                            } catch (DocumentException e) {
                            }
                        }
                    }
                }

                if (appDic != null && (flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) == 0) {
                    PdfObject obj = appDic.get(PdfName.N);
                    PdfAppearance app = null;
                    PdfObject objReal = PdfReader.getPdfObject(obj);
                    if (obj != null) {
                        if (obj instanceof PdfIndirectReference && !obj.isIndirect())
                            app = new PdfAppearance((PdfIndirectReference) obj);
                        else if (objReal instanceof PdfStream) {
                            ((PdfDictionary) objReal).put(PdfName.SUBTYPE, PdfName.FORM);
                            app = new PdfAppearance((PdfIndirectReference) obj);
                        } else {
                            if (objReal != null && objReal.isDictionary()) {
                                PdfName as = merged.getAsName(PdfName.AS);
                                if (as != null) {
                                    PdfIndirectReference iref = (PdfIndirectReference) ((PdfDictionary) objReal).get(as);
                                    if (iref != null) {
                                        app = new PdfAppearance(iref);
                                        if (iref.isIndirect()) {
                                            objReal = PdfReader.getPdfObject(iref);
                                            ((PdfDictionary) objReal).put(PdfName.SUBTYPE, PdfName.FORM);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (app != null) {
                        Rectangle box = PdfReader.getNormalizedRectangle(merged.getAsArray(PdfName.RECT));
                        PdfContentByte cb = getOverContent(page);
                        cb.setLiteral("Q ");
                        if (applyRotation) {
                            /*
                            Apply field rotation
                             */
                            AffineTransform tf = new AffineTransform();
                            double fieldRotation = 0;
                            if (merged.getAsDict(PdfName.MK) != null) {
                                if (merged.getAsDict(PdfName.MK).get(PdfName.R) != null) {
                                    fieldRotation = merged.getAsDict(PdfName.MK).getAsNumber(PdfName.R).floatValue();
                                }
                            }
                            //Cast to radians
                            fieldRotation = fieldRotation * Math.PI / 180;
                            //Clamp to [-2*Pi, 2*Pi]
                            fieldRotation = fieldRotation % (2 * Math.PI);
                            //Calculate transformation matrix
                            tf = calculateTemplateTransformationMatrix(tf, fieldRotation, box);
                            cb.addTemplate(app, tf);
                        } else {
                            if(objReal instanceof PdfDictionary && ((PdfDictionary)objReal).getAsArray(PdfName.BBOX) != null) {
                                Rectangle bBox = PdfReader.getNormalizedRectangle((((PdfDictionary)objReal).getAsArray(PdfName.BBOX)));
                                cb.addTemplate(app, (box.getWidth() / bBox.getWidth()), 0, 0, (box.getHeight() / bBox.getHeight()), box.getLeft(), box.getBottom());
                            } else {
                                cb.addTemplate(app, box.getLeft(), box.getBottom());
                            }
                        }
                        cb.setLiteral("q ");
                    }
                }
                if (partialFlattening.isEmpty())
                    continue;
                PdfDictionary pageDic = reader.getPageN(page);
                PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
                if (annots == null)
                    continue;
                for (int idx = 0; idx < annots.size(); ++idx) {
                    PdfObject ran = annots.getPdfObject(idx);
                    if (!ran.isIndirect())
                        continue;
                    PdfObject ran2 = item.getWidgetRef(k);
                    if (!ran2.isIndirect())
                        continue;
                    if (((PRIndirectReference) ran).getNumber() == ((PRIndirectReference) ran2).getNumber()) {
                        annots.remove(idx--);
                        PRIndirectReference wdref = (PRIndirectReference) ran2;
                        while (true) {
                            PdfDictionary wd = (PdfDictionary) PdfReader.getPdfObject(wdref);
                            PRIndirectReference parentRef = (PRIndirectReference) wd.get(PdfName.PARENT);
                            PdfReader.killIndirect(wdref);
                            if (parentRef == null) { // reached AcroForm
                                for (int fr = 0; fr < acroFds.size(); ++fr) {
                                    PdfObject h = acroFds.getPdfObject(fr);
                                    if (h.isIndirect() && ((PRIndirectReference) h).getNumber() == wdref.getNumber()) {
                                        acroFds.remove(fr);
                                        --fr;
                                    }
                                }
                                break;
                            }
                            PdfDictionary parent = (PdfDictionary) PdfReader.getPdfObject(parentRef);
                            PdfArray kids = parent.getAsArray(PdfName.KIDS);
                            for (int fr = 0; fr < kids.size(); ++fr) {
                                PdfObject h = kids.getPdfObject(fr);
                                if (h.isIndirect() && ((PRIndirectReference) h).getNumber() == wdref.getNumber()) {
                                    kids.remove(fr);
                                    --fr;
                                }
                            }
                            if (!kids.isEmpty())
                                break;
                            wdref = parentRef;
                        }
                    }
                }
                if (annots.isEmpty()) {
                    PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
                    pageDic.remove(PdfName.ANNOTS);
                }
            }
        }
        if (!fieldsAdded && partialFlattening.isEmpty()) {
            for (int page = 1; page <= reader.getNumberOfPages(); ++page) {
                PdfDictionary pageDic = reader.getPageN(page);
                PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
                if (annots == null)
                    continue;
                for (int idx = 0; idx < annots.size(); ++idx) {
                    PdfObject annoto = annots.getDirectObject(idx);
                    if (annoto instanceof PdfIndirectReference && !annoto.isIndirect())
                        continue;
                    if (!annoto.isDictionary() || PdfName.WIDGET.equals(((PdfDictionary) annoto).get(PdfName.SUBTYPE))) {
                        annots.remove(idx);
                        --idx;
                    }
                }
                if (annots.isEmpty()) {
                    PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
                    pageDic.remove(PdfName.ANNOTS);
                }
            }
            eliminateAcroformObjects();
        }
    }

    void eliminateAcroformObjects() {
        PdfObject acro = reader.getCatalog().get(PdfName.ACROFORM);
        if (acro == null)
            return;
        PdfDictionary acrodic = (PdfDictionary) PdfReader.getPdfObject(acro);
        reader.killXref(acrodic.get(PdfName.XFA));
        acrodic.remove(PdfName.XFA);
        PdfObject iFields = acrodic.get(PdfName.FIELDS);
        if (iFields != null) {
            PdfDictionary kids = new PdfDictionary();
            kids.put(PdfName.KIDS, iFields);
            sweepKids(kids);
            PdfReader.killIndirect(iFields);
            acrodic.put(PdfName.FIELDS, new PdfArray());
        }
        acrodic.remove(PdfName.SIGFLAGS);
        acrodic.remove(PdfName.NEEDAPPEARANCES);
        acrodic.remove(PdfName.DR);
//        PdfReader.killIndirect(acro);
//        reader.getCatalog().remove(PdfName.ACROFORM);
    }

    private AffineTransform calculateTemplateTransformationMatrix(AffineTransform currentMatrix, double fieldRotation, Rectangle box) {
        AffineTransform templateTransform = new AffineTransform(currentMatrix);
        //Move to new origin
        double x = box.getLeft();
        double y = box.getBottom();
        if (fieldRotation % (Math.PI / 2) == 0 && fieldRotation % (3 * Math.PI / 2) != 0 && fieldRotation != 0) {
            x += box.getWidth();
        }
        if((fieldRotation%(3*Math.PI/2)==0 || fieldRotation%(Math.PI)==0) && fieldRotation != 0){
            y+=box.getHeight();
        }
        templateTransform.translate(x,y);
        //Apply fieldrotation
        templateTransform.rotate(fieldRotation);
        return templateTransform;
    }

    void sweepKids(PdfObject obj) {
        PdfObject oo = PdfReader.killIndirect(obj);
        if (oo == null || !oo.isDictionary())
            return;
        PdfDictionary dic = (PdfDictionary) oo;
        PdfArray kids = (PdfArray) PdfReader.killIndirect(dic.get(PdfName.KIDS));
        if (kids == null)
            return;
        for (int k = 0; k < kids.size(); ++k) {
            sweepKids(kids.getPdfObject(k));
        }
    }

    /**
     * If true, annotations with an appearance stream will be flattened.
     *
     * @param flatAnnotations boolean
     * @since 5.5.3
     */
    public void setFlatAnnotations(boolean flatAnnotations) {
        this.flatannotations = flatAnnotations;
    }

    /**
     * If setFlatAnnotations is set to true, iText will flatten all annotations with an appearance stream
     *
     * @since 5.5.3
     */
    protected void flattenAnnotations() {
        flattenAnnotations(false);
    }

    private void flattenAnnotations(boolean flattenFreeTextAnnotations) {
        if (append) {
            if (flattenFreeTextAnnotations) {
                throw new IllegalArgumentException(MessageLocalization.getComposedMessage("freetext.flattening.is.not.supported.in.append.mode"));
            } else {
                throw new IllegalArgumentException(MessageLocalization.getComposedMessage("annotation.flattening.is.not.supported.in.append.mode"));
            }
        }

        for (int page = 1; page <= reader.getNumberOfPages(); ++page) {
            PdfDictionary pageDic = reader.getPageN(page);
            PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);

            if (annots == null) {
                continue;
            }

            for (int idx = 0; idx < annots.size(); ++idx) {
                PdfObject annoto = annots.getDirectObject(idx);
                if (annoto instanceof PdfIndirectReference && !annoto.isIndirect())
                    continue;
                if (!(annoto instanceof PdfDictionary))
                    continue;
                PdfDictionary annDic = (PdfDictionary) annoto;
                final PdfObject subType = annDic.get(PdfName.SUBTYPE);
                if (flattenFreeTextAnnotations) {
                    if (! PdfName.FREETEXT.equals(subType)) {
                        continue;
                    }
                } else {
                    if (PdfName.WIDGET.equals(subType)) {
                        // skip widgets
                        continue;
                    }
                }

                PdfNumber ff = annDic.getAsNumber(PdfName.F);
                int flags = ff != null ? ff.intValue() : 0;

                if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) == 0) {
                    PdfObject obj1 = annDic.get(PdfName.AP);
                    if (obj1 == null)
                        continue;
                    PdfDictionary appDic = obj1 instanceof PdfIndirectReference ?
                            (PdfDictionary) PdfReader.getPdfObject(obj1) : (PdfDictionary) obj1;
                    PdfObject obj = appDic.get(PdfName.N);
                    PdfDictionary objDict = appDic.getAsStream(PdfName.N);
                    PdfAppearance app = null;
                    PdfObject objReal = PdfReader.getPdfObject(obj);

                    if (obj instanceof PdfIndirectReference && !obj.isIndirect()) {
                        app = new PdfAppearance((PdfIndirectReference) obj);
                    } else if (objReal instanceof PdfStream) {
                        ((PdfDictionary) objReal).put(PdfName.SUBTYPE, PdfName.FORM);
                        app = new PdfAppearance((PdfIndirectReference) obj);
                    } else {
                        if (objReal != null ) {
                            if (objReal.isDictionary()) {
                                PdfName as_p = appDic.getAsName(PdfName.AS);
                                if (as_p != null) {
                                    PdfIndirectReference iref = (PdfIndirectReference) ( (PdfDictionary) objReal ).get(as_p);
                                    if (iref != null) {
                                        app = new PdfAppearance(iref);
                                        if (iref.isIndirect()) {
                                            objReal = PdfReader.getPdfObject(iref);
                                            ( (PdfDictionary) objReal ).put(PdfName.SUBTYPE, PdfName.FORM);
                                        }
                                    }
                                }
                            }
                        } else {
                            if ( PdfName.FREETEXT.equals(subType) ) {
                                final PdfString defaultAppearancePdfString = annDic.getAsString(PdfName.DA);
                                if (defaultAppearancePdfString != null) {
                                    final PdfString freeTextContent = annDic.getAsString(PdfName.CONTENTS);
                                    final String defaultAppearanceString = defaultAppearancePdfString.toString();

                                    //It is not stated in spec, but acrobat seems to support standard font names in DA
                                    //So we need to check if the font is built-in and specify it explicitly.
                                    PdfIndirectReference fontReference = null;
                                    PdfName pdfFontName = null;
                                    try {
                                        RandomAccessSource source = new RandomAccessSourceFactory().createSource(defaultAppearancePdfString.getBytes());
                                        PdfContentParser ps = new PdfContentParser(new PRTokeniser(new RandomAccessFileOrArray(source)));
                                        ArrayList<PdfObject> operands = new ArrayList<PdfObject>();
                                        while (ps.parse(operands).size() > 0) {
                                            PdfLiteral operator = (PdfLiteral)operands.get(operands.size()-1);
                                            if (operator.toString().equals("Tf")) {
                                                pdfFontName = (PdfName) operands.get(0);
                                                String fontName = pdfFontName.toString().substring(1);
                                                String fullName = fromShortToFullAnnotationFontNames.get(fontName);
                                                if (fullName == null) {
                                                    fullName = fontName;
                                                }
                                                fontReference = builtInAnnotationFonts.get(fullName);
                                                if (fontReference == null) {
                                                    PdfDictionary dic = BaseFont.createBuiltInFontDictionary(fullName);
                                                    if (dic != null) {
                                                        fontReference = addToBody(dic).getIndirectReference();
                                                        builtInAnnotationFonts.put(fullName, fontReference);
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Exception any) {
                                        logger.warn(MessageLocalization.getComposedMessage("error.resolving.freetext.font"));
                                        break;
                                    }

                                    app = new PdfAppearance(this);
                                    // it is unclear from spec were referenced from DA font should be (since annotations doesn't have DR), so in case it not built-in
                                    // quickly and naively flattening the freetext annotation
                                    if (fontReference != null) {
                                        app.getPageResources().addFont(pdfFontName, fontReference);
                                    }
                                    app.saveState();
                                    app.beginText();
                                    app.setLiteral(defaultAppearanceString);
                                    app.setLiteral("(" + freeTextContent.toString() + ") Tj\n");
                                    app.endText();
                                    app.restoreState();
                                } else {
                                    // The DA entry is required for free text annotations
                                    // Not throwing an exception as we don't want to stop the flow, result is that this annotation won't be flattened.
                                    this.logger.warn(MessageLocalization.getComposedMessage("freetext.annotation.doesnt.contain.da"));
                                }
                            } else {
                                this.logger.warn(MessageLocalization.getComposedMessage("annotation.type.not.supported.flattening"));
                            }
                        }
                    }
                    if (app != null) {
                        Rectangle rect = PdfReader.getNormalizedRectangle(annDic.getAsArray(PdfName.RECT));
                        Rectangle bBox = null;

                        if ( objDict != null ) {
                            bBox = PdfReader.getNormalizedRectangle((objDict.getAsArray(PdfName.BBOX)));
                        } else {
                            bBox = new Rectangle(0,0, rect.getWidth(), rect.getHeight());
                            app.setBoundingBox(bBox);
                        }

                        PdfContentByte cb = getOverContent(page);
                        cb.setLiteral("Q ");
                        if (objDict != null && objDict.getAsArray(PdfName.MATRIX) != null &&
                                !Arrays.equals(DEFAULT_MATRIX, objDict.getAsArray(PdfName.MATRIX).asDoubleArray())) {
                            double[] matrix = objDict.getAsArray(PdfName.MATRIX).asDoubleArray();
                            Rectangle transformBBox = transformBBoxByMatrix(bBox, matrix);
                            cb.addTemplate(app, (rect.getWidth() / transformBBox.getWidth()), 0, 0, (rect.getHeight() / transformBBox.getHeight()), rect.getLeft(), rect.getBottom());
                        } else {
                            //Correct for offset origins in the BBox, similar to how Adobe will flatten.
                            float heightCorrection = - bBox.getBottom();
                            float widthCorrection = - bBox.getLeft();
                            //Changed so that when the annotation has a difference scale than the xObject in the appearance dictionary, the image is consistent between
                            //the input and the flattened document.  When the annotation is rotated or skewed, it will still be flattened incorrectly.
                            cb.addTemplate(app, (rect.getWidth() / bBox.getWidth()), 0, 0, (rect.getHeight() / bBox.getHeight()), rect.getLeft() + widthCorrection, rect.getBottom()+heightCorrection);
                        }
                        cb.setLiteral("q ");

                        annots.remove(idx);
                        --idx;
                    }
                }
            }

            if (annots.isEmpty()) {
                PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
                pageDic.remove(PdfName.ANNOTS);
            }
        }
    }

    /*
    * The transformation BBOX between two coordinate systems can be
    * represented by a 3-by-3 transformation matrix and create new BBOX based min(x,y) and
     * max(x,y) coordinate pairs
    * */
    private Rectangle transformBBoxByMatrix(Rectangle bBox, double[] matrix) {
        List xArr = new ArrayList();
        List yArr = new ArrayList();
        Point p1 = transformPoint(bBox.getLeft(), bBox.getBottom(), matrix);
        xArr.add(p1.x);
        yArr.add(p1.y);
        Point p2 = transformPoint(bBox.getRight(), bBox.getTop(), matrix);
        xArr.add(p2.x);
        yArr.add(p2.y);
        Point p3 = transformPoint(bBox.getLeft(), bBox.getTop(), matrix);
        xArr.add(p3.x);
        yArr.add(p3.y);
        Point p4 = transformPoint(bBox.getRight(), bBox.getBottom(), matrix);
        xArr.add(p4.x);
        yArr.add(p4.y);

        return new Rectangle(((Double) Collections.min(xArr)).floatValue(),
                ((Double) Collections.min(yArr)).floatValue(),
                ((Double) Collections.max(xArr)).floatValue(),
                ((Double) Collections.max(yArr)).floatValue());
    }

    /*
    *  transform point by algorithm
    *  x′ = a*x + c×y + e
    *  y' = b*x + d*y + f
    *  [ a b c d e f ] transformation matrix values
    * */
    private Point transformPoint(double x, double y, double[] matrix) {
        Point point = new Point();
        point.x = matrix[0] * x + matrix[2] * y + matrix[4];
        point.y = matrix[1] * x + matrix[3] * y + matrix[5];
        return point;
    }

    protected void flatFreeTextFields() {
        flattenAnnotations(true);
    }

    /**
     * @see com.itextpdf.text.pdf.PdfWriter#getPageReference(int)
     */
    @Override
    public PdfIndirectReference getPageReference(int page) {
        PdfIndirectReference ref = reader.getPageOrigRef(page);
        if (ref == null)
            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("invalid.page.number.1", page));
        return ref;
    }

    /**
     * @see com.itextpdf.text.pdf.PdfWriter#addAnnotation(com.itextpdf.text.pdf.PdfAnnotation)
     */
    @Override
    public void addAnnotation(PdfAnnotation annot) {
        throw new RuntimeException(MessageLocalization.getComposedMessage("unsupported.in.this.context.use.pdfstamper.addannotation"));
    }

    void addDocumentField(PdfIndirectReference ref) {
        PdfDictionary catalog = reader.getCatalog();
        PdfDictionary acroForm = (PdfDictionary) PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), catalog);
        if (acroForm == null) {
            acroForm = new PdfDictionary();
            catalog.put(PdfName.ACROFORM, acroForm);
            markUsed(catalog);
        }
        PdfArray fields = (PdfArray) PdfReader.getPdfObject(acroForm.get(PdfName.FIELDS), acroForm);
        if (fields == null) {
            fields = new PdfArray();
            acroForm.put(PdfName.FIELDS, fields);
            markUsed(acroForm);
        }
        if (!acroForm.contains(PdfName.DA)) {
            acroForm.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
            markUsed(acroForm);
        }
        fields.add(ref);
        markUsed(fields);
    }

    protected void addFieldResources() throws IOException {
        if (fieldTemplates.isEmpty())
            return;
        PdfDictionary catalog = reader.getCatalog();
        PdfDictionary acroForm = (PdfDictionary) PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), catalog);
        if (acroForm == null) {
            acroForm = new PdfDictionary();
            catalog.put(PdfName.ACROFORM, acroForm);
            markUsed(catalog);
        }
        PdfDictionary dr = (PdfDictionary) PdfReader.getPdfObject(acroForm.get(PdfName.DR), acroForm);
        if (dr == null) {
            dr = new PdfDictionary();
            acroForm.put(PdfName.DR, dr);
            markUsed(acroForm);
        }
        markUsed(dr);
        for (PdfTemplate template : fieldTemplates) {
            PdfFormField.mergeResources(dr, (PdfDictionary) template.getResources(), this);
        }
        // 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());
        }
        if (acroForm.get(PdfName.DA) == null) {
            acroForm.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
            markUsed(acroForm);
        }
    }

    void expandFields(PdfFormField field, ArrayList<PdfAnnotation> allAnnots) {
        allAnnots.add(field);
        ArrayList<PdfFormField> kids = field.getKids();
        if (kids != null) {
            for (int k = 0; k < kids.size(); ++k)
                expandFields(kids.get(k), allAnnots);
        }
    }

    void addAnnotation(PdfAnnotation annot, PdfDictionary pageN) {
        try {
            ArrayList<PdfAnnotation> allAnnots = new ArrayList<PdfAnnotation>();
            if (annot.isForm()) {
                fieldsAdded = true;
                getAcroFields();
                PdfFormField field = (PdfFormField) annot;
                if (field.getParent() != null)
                    return;
                expandFields(field, allAnnots);
            } else
                allAnnots.add(annot);
            for (int k = 0; k < allAnnots.size(); ++k) {
                annot = allAnnots.get(k);
                if (annot.getPlaceInPage() > 0)
                    pageN = reader.getPageN(annot.getPlaceInPage());
                if (annot.isForm()) {
                    if (!annot.isUsed()) {
                        HashSet<PdfTemplate> templates = annot.getTemplates();
                        if (templates != null)
                            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);
                        markUsed(pageN);
                    } else
                        annots = (PdfArray) pdfobj;
                    annots.add(annot.getIndirectReference());
                    markUsed(annots);
                    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.top(),
                                            rect.right(),
                                            pageSize.getTop() - rect.bottom(),
                                            rect.left()));
                                    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();
                    addToBody(annot, annot.getIndirectReference());
                }
            }
        } catch (IOException e) {
            throw new ExceptionConverter(e);
        }
    }

    @Override
    void addAnnotation(PdfAnnotation annot, int page) {
        if (annot.isAnnotation())
            annot.setPage(page);
        addAnnotation(annot, reader.getPageN(page));
    }

    private void outlineTravel(PRIndirectReference outline) {
        while (outline != null) {
            PdfDictionary outlineR = (PdfDictionary) PdfReader.getPdfObjectRelease(outline);
            PRIndirectReference first = (PRIndirectReference) outlineR.get(PdfName.FIRST);
            if (first != null) {
                outlineTravel(first);
            }
            PdfReader.killIndirect(outlineR.get(PdfName.DEST));
            PdfReader.killIndirect(outlineR.get(PdfName.A));
            PdfReader.killIndirect(outline);
            outline = (PRIndirectReference) outlineR.get(PdfName.NEXT);
        }
    }

    void deleteOutlines() {
        PdfDictionary catalog = reader.getCatalog();
        PdfObject obj = catalog.get(PdfName.OUTLINES);
        if (obj == null)
            return;
        if (obj instanceof PRIndirectReference) {
            PRIndirectReference outlines = (PRIndirectReference) obj;
            outlineTravel(outlines);
            PdfReader.killIndirect(outlines);
        }
        catalog.remove(PdfName.OUTLINES);
        markUsed(catalog);
    }

    protected void setJavaScript() throws IOException {
        HashMap<String, PdfObject> djs = pdf.getDocumentLevelJS();
        if (djs.isEmpty())
            return;
        PdfDictionary catalog = reader.getCatalog();
        PdfDictionary names = (PdfDictionary) PdfReader.getPdfObject(catalog.get(PdfName.NAMES), catalog);
        if (names == null) {
            names = new PdfDictionary();
            catalog.put(PdfName.NAMES, names);
            markUsed(catalog);
        }
        markUsed(names);
        PdfDictionary tree = PdfNameTree.writeTree(djs, this);
        names.put(PdfName.JAVASCRIPT, addToBody(tree).getIndirectReference());
    }

    protected void addFileAttachments() throws IOException {
        HashMap<String, PdfObject> fs = pdf.getDocumentFileAttachment();
        if (fs.isEmpty())
            return;
        PdfDictionary catalog = reader.getCatalog();
        PdfDictionary names = (PdfDictionary) PdfReader.getPdfObject(catalog.get(PdfName.NAMES), catalog);
        if (names == null) {
            names = new PdfDictionary();
            catalog.put(PdfName.NAMES, names);
            markUsed(catalog);
        }
        markUsed(names);
        HashMap<String, PdfObject> old = PdfNameTree.readTree((PdfDictionary) PdfReader.getPdfObjectRelease(names.get(PdfName.EMBEDDEDFILES)));
        for (Map.Entry<String, PdfObject> entry : fs.entrySet()) {
            String name = entry.getKey();
            int k = 0;
            StringBuilder nn = new StringBuilder(name);
            while (old.containsKey(nn.toString())) {
                ++k;
                nn.append(" ").append(k);
            }
            old.put(nn.toString(), entry.getValue());
        }
        PdfDictionary tree = PdfNameTree.writeTree(old, this);
        // Remove old EmbeddedFiles object if preset
        PdfObject oldEmbeddedFiles = names.get(PdfName.EMBEDDEDFILES);
        if (oldEmbeddedFiles != null) {
            PdfReader.killIndirect(oldEmbeddedFiles);
        }

        // Add new EmbeddedFiles object
        names.put(PdfName.EMBEDDEDFILES, addToBody(tree).getIndirectReference());
    }

    /**
     * Adds or replaces the Collection Dictionary in the Catalog.
     *
     * @param collection the new collection dictionary.
     */
    void makePackage(PdfCollection collection) {
        PdfDictionary catalog = reader.getCatalog();
        catalog.put(PdfName.COLLECTION, collection);
    }

    protected void setOutlines() throws IOException {
        if (newBookmarks == null)
            return;
        deleteOutlines();
        if (newBookmarks.isEmpty())
            return;
        PdfDictionary catalog = reader.getCatalog();
        boolean namedAsNames = catalog.get(PdfName.DESTS) != null;
        writeOutlines(catalog, namedAsNames);
        markUsed(catalog);
    }

    /**
     * Sets the viewer preferences.
     *
     * @param preferences the viewer preferences
     * @see PdfWriter#setViewerPreferences(int)
     */
    @Override
    public void setViewerPreferences(int preferences) {
        useVp = true;
        this.viewerPreferences.setViewerPreferences(preferences);
    }

    /**
     * Adds a viewer preference
     *
     * @param key   a key for a viewer preference
     * @param value the value for the viewer preference
     * @see PdfViewerPreferences#addViewerPreference
     */
    @Override
    public void addViewerPreference(PdfName key, PdfObject value) {
        useVp = true;
        this.viewerPreferences.addViewerPreference(key, value);
    }

    /**
     * Set the signature flags.
     *
     * @param f the flags. This flags are ORed with current ones
     */
    @Override
    public void setSigFlags(int f) {
        sigFlags |= f;
    }

    /**
     * Always throws an <code>UnsupportedOperationException</code>.
     *
     * @param actionType ignore
     * @param action     ignore
     * @throws PdfException ignore
     * @see PdfStamper#setPageAction(PdfName, PdfAction, int)
     */
    @Override
    public void setPageAction(PdfName actionType, PdfAction action) throws PdfException {
        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.setpageaction.pdfname.actiontype.pdfaction.action.int.page"));
    }

    /**
     * Sets the open and close page additional action.
     *
     * @param actionType the action type. It can be <CODE>PdfWriter.PAGE_OPEN</CODE>
     *                   or <CODE>PdfWriter.PAGE_CLOSE</CODE>
     * @param action     the action to perform
     * @param page       the page where the action will be applied. The first page is 1
     * @throws PdfException if the action type is invalid
     */
    void setPageAction(PdfName actionType, PdfAction action, int page) throws PdfException {
        if (!actionType.equals(PAGE_OPEN) && !actionType.equals(PAGE_CLOSE))
            throw new PdfException(MessageLocalization.getComposedMessage("invalid.page.additional.action.type.1", actionType.toString()));
        PdfDictionary pg = reader.getPageN(page);
        PdfDictionary aa = (PdfDictionary) PdfReader.getPdfObject(pg.get(PdfName.AA), pg);
        if (aa == null) {
            aa = new PdfDictionary();
            pg.put(PdfName.AA, aa);
            markUsed(pg);
        }
        aa.put(actionType, action);
        markUsed(aa);
    }

    /**
     * Always throws an <code>UnsupportedOperationException</code>.
     *
     * @param seconds ignore
     */
    @Override
    public void setDuration(int seconds) {
        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.setpageaction.pdfname.actiontype.pdfaction.action.int.page"));
    }

    /**
     * Always throws an <code>UnsupportedOperationException</code>.
     *
     * @param transition ignore
     */
    @Override
    public void setTransition(PdfTransition transition) {
        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.setpageaction.pdfname.actiontype.pdfaction.action.int.page"));
    }

    /**
     * Sets the display duration for the page (for presentations)
     *
     * @param seconds the number of seconds to display the page. A negative value removes the entry
     * @param page    the page where the duration will be applied. The first page is 1
     */
    void setDuration(int seconds, int page) {
        PdfDictionary pg = reader.getPageN(page);
        if (seconds < 0)
            pg.remove(PdfName.DUR);
        else
            pg.put(PdfName.DUR, new PdfNumber(seconds));
        markUsed(pg);
    }

    /**
     * Sets the transition for the page
     *
     * @param transition the transition object. A <code>null</code> removes the transition
     * @param page       the page where the transition will be applied. The first page is 1
     */
    void setTransition(PdfTransition transition, int page) {
        PdfDictionary pg = reader.getPageN(page);
        if (transition == null)
            pg.remove(PdfName.TRANS);
        else
            pg.put(PdfName.TRANS, transition.getTransitionDictionary());
        markUsed(pg);
    }

    protected void markUsed(PdfObject obj) {
        if (append && obj != null) {
            PRIndirectReference ref = null;
            if (obj.type() == PdfObject.INDIRECT)
                ref = (PRIndirectReference) obj;
            else
                ref = obj.getIndRef();
            if (ref != null)
                marked.put(ref.getNumber(), 1);
        }
    }

    protected void markUsed(int num) {
        if (append)
            marked.put(num, 1);
    }

    /**
     * Getter for property append.
     *
     * @return Value of property append.
     */
    boolean isAppend() {
        return append;
    }

    public PdfReader getPdfReader() {
        return this.reader;
    }

    /**
     * Additional-actions defining the actions to be taken in
     * response to various trigger events affecting the document
     * as a whole. The actions types allowed are: <CODE>DOCUMENT_CLOSE</CODE>,
     * <CODE>WILL_SAVE</CODE>, <CODE>DID_SAVE</CODE>, <CODE>WILL_PRINT</CODE>
     * and <CODE>DID_PRINT</CODE>.
     *
     * @param actionType the action type
     * @param action     the action to execute in response to the trigger
     * @throws PdfException on invalid action type
     */
    @Override
    public void setAdditionalAction(PdfName actionType, PdfAction action) throws PdfException {
        if (!(actionType.equals(DOCUMENT_CLOSE) ||
                actionType.equals(WILL_SAVE) ||
                actionType.equals(DID_SAVE) ||
                actionType.equals(WILL_PRINT) ||
                actionType.equals(DID_PRINT))) {
            throw new PdfException(MessageLocalization.getComposedMessage("invalid.additional.action.type.1", actionType.toString()));
        }
        PdfDictionary aa = reader.getCatalog().getAsDict(PdfName.AA);
        if (aa == null) {
            if (action == null)
                return;
            aa = new PdfDictionary();
            reader.getCatalog().put(PdfName.AA, aa);
        }
        markUsed(aa);
        if (action == null)
            aa.remove(actionType);
        else
            aa.put(actionType, action);
    }

    /**
     * @see com.itextpdf.text.pdf.PdfWriter#setOpenAction(com.itextpdf.text.pdf.PdfAction)
     */
    @Override
    public void setOpenAction(PdfAction action) {
        openAction = action;
    }

    /**
     * @see com.itextpdf.text.pdf.PdfWriter#setOpenAction(java.lang.String)
     */
    @Override
    public void setOpenAction(String name) {
        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("open.actions.by.name.are.not.supported"));
    }

    /**
     * @see com.itextpdf.text.pdf.PdfWriter#setThumbnail(com.itextpdf.text.Image)
     */
    @Override
    public void setThumbnail(com.itextpdf.text.Image image) {
        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.pdfstamper.setthumbnail"));
    }

    void setThumbnail(Image image, int page) throws PdfException, DocumentException {
        PdfIndirectReference thumb = getImageReference(addDirectImageSimple(image));
        reader.resetReleasePage();
        PdfDictionary dic = reader.getPageN(page);
        dic.put(PdfName.THUMB, thumb);
        reader.resetReleasePage();
    }

    @Override
    public PdfContentByte getDirectContentUnder() {
        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.pdfstamper.getundercontent.or.pdfstamper.getovercontent"));
    }

    @Override
    public PdfContentByte getDirectContent() {
        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.pdfstamper.getundercontent.or.pdfstamper.getovercontent"));
    }

    /**
     * Reads the OCProperties dictionary from the catalog of the existing document
     * and fills the documentOCG, documentOCGorder and OCGRadioGroup variables in PdfWriter.
     * Note that the original OCProperties of the existing document can contain more information.
     *
     * @since 2.1.2
     */
    protected void readOCProperties() {
        if (!documentOCG.isEmpty()) {
            return;
        }
        PdfDictionary dict = reader.getCatalog().getAsDict(PdfName.OCPROPERTIES);
        if (dict == null) {
            return;
        }
        PdfArray ocgs = dict.getAsArray(PdfName.OCGS);
        if (ocgs == null) {
            ocgs = new PdfArray();
            dict.put(PdfName.OCGS, ocgs);
        }
        PdfIndirectReference ref;
        PdfLayer layer;
        HashMap<String, PdfLayer> ocgmap = new HashMap<String, PdfLayer>();
        for (Iterator<PdfObject> i = ocgs.listIterator(); i.hasNext(); ) {
            ref = (PdfIndirectReference) i.next();
            layer = new PdfLayer(null);
            layer.setRef(ref);
            layer.setOnPanel(false);
            layer.merge((PdfDictionary) PdfReader.getPdfObject(ref));
            ocgmap.put(ref.toString(), layer);
        }
        PdfDictionary d = dict.getAsDict(PdfName.D);
        PdfArray off = d.getAsArray(PdfName.OFF);
        if (off != null) {
            for (Iterator<PdfObject> i = off.listIterator(); i.hasNext(); ) {
                ref = (PdfIndirectReference) i.next();
                layer = ocgmap.get(ref.toString());
                layer.setOn(false);
            }
        }
        PdfArray order = d.getAsArray(PdfName.ORDER);
        if (order != null) {
            addOrder(null, order, ocgmap);
        }
        documentOCG.addAll(ocgmap.values());
        OCGRadioGroup = d.getAsArray(PdfName.RBGROUPS);
        if (OCGRadioGroup == null)
            OCGRadioGroup = new PdfArray();
        OCGLocked = d.getAsArray(PdfName.LOCKED);
        if (OCGLocked == null)
            OCGLocked = new PdfArray();
    }

    /**
     * Recursive method to reconstruct the documentOCGorder variable in the writer.
     *
     * @param parent a parent PdfLayer (can be null)
     * @param arr    an array possibly containing children for the parent PdfLayer
     * @param ocgmap a HashMap with indirect reference Strings as keys and PdfLayer objects as values.
     * @since 2.1.2
     */
    private void addOrder(PdfLayer parent, PdfArray arr, Map<String, PdfLayer> ocgmap) {
        PdfObject obj;
        PdfLayer layer;
        for (int i = 0; i < arr.size(); i++) {
            obj = arr.getPdfObject(i);
            if (obj.isIndirect()) {
                layer = ocgmap.get(obj.toString());
                if (layer != null) {
                    layer.setOnPanel(true);
                    registerLayer(layer);
                    if (parent != null) {
                        parent.addChild(layer);
                    }
                    if (arr.size() > i + 1 && arr.getPdfObject(i + 1).isArray()) {
                        i++;
                        addOrder(layer, (PdfArray) arr.getPdfObject(i), ocgmap);
                    }
                }
            } else if (obj.isArray()) {
                PdfArray sub = (PdfArray) obj;
                if (sub.isEmpty()) return;
                obj = sub.getPdfObject(0);
                if (obj.isString()) {
                    layer = new PdfLayer(obj.toString());
                    layer.setOnPanel(true);
                    registerLayer(layer);
                    if (parent != null) {
                        parent.addChild(layer);
                    }
                    PdfArray array = new PdfArray();
                    for (Iterator<PdfObject> j = sub.listIterator(); j.hasNext(); ) {
                        array.add(j.next());
                    }
                    addOrder(layer, array, ocgmap);
                } else {
                    addOrder(parent, (PdfArray) obj, ocgmap);
                }
            }
        }
    }

    /**
     * Gets the PdfLayer objects in an existing document as a Map
     * with the names/titles of the layers as keys.
     *
     * @return a Map with all the PdfLayers in the document (and the name/title of the layer as key)
     * @since 2.1.2
     */
    public Map<String, PdfLayer> getPdfLayers() {
        if (!originalLayersAreRead) {
            originalLayersAreRead = true;
            readOCProperties();
        }
        HashMap<String, PdfLayer> map = new HashMap<String, PdfLayer>();
        PdfLayer layer;
        String key;
        for (PdfOCG pdfOCG : documentOCG) {
            layer = (PdfLayer) pdfOCG;
            if (layer.getTitle() == null) {
                key = layer.getAsString(PdfName.NAME).toString();
            } else {
                key = layer.getTitle();
            }
            if (map.containsKey(key)) {
                int seq = 2;
                String tmp = key + "(" + seq + ")";
                while (map.containsKey(tmp)) {
                    seq++;
                    tmp = key + "(" + seq + ")";
                }
                key = tmp;
            }
            map.put(key, layer);
        }
        return map;
    }

    @Override
    void registerLayer(final PdfOCG layer) {
        if (!originalLayersAreRead) {
            originalLayersAreRead = true;
            readOCProperties();
        }
        super.registerLayer(layer);
    }

    public void createXmpMetadata() {
        try {
            xmpWriter = createXmpWriter(null, reader.getInfo());
            xmpMetadata = null;
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    protected HashMap<Object, PdfObject> getNamedDestinations() {
        return namedDestinations;
    }

    protected void updateNamedDestinations() throws IOException {

        PdfDictionary dic = reader.getCatalog().getAsDict(PdfName.NAMES);
        if (dic != null)
            dic = dic.getAsDict(PdfName.DESTS);
        if (dic == null) {
            dic = reader.getCatalog().getAsDict(PdfName.DESTS);
        }
        if (dic == null) {
            dic = new PdfDictionary();
            PdfDictionary dests = new PdfDictionary();
            dic.put(PdfName.NAMES, new PdfArray());
            dests.put(PdfName.DESTS, dic);
            reader.getCatalog().put(PdfName.NAMES, dests);
        }

        PdfArray names = getLastChildInNameTree(dic);

        for (Object name : namedDestinations.keySet()) {
            names.add(new PdfString(name.toString()));
            names.add(addToBody(namedDestinations.get(name), getPdfIndirectReference()).getIndirectReference());
        }
    }

    private PdfArray getLastChildInNameTree(PdfDictionary dic) {

        PdfArray names;

        PdfArray childNode = dic.getAsArray(PdfName.KIDS);
        if (childNode != null) {
            PdfDictionary lastKid = childNode.getAsDict(childNode.size() - 1);
            names = getLastChildInNameTree(lastKid);
        } else {
            names = dic.getAsArray(PdfName.NAMES);
        }

        return names;
    }

    static class PageStamp {

        PdfDictionary pageN;
        StampContent under;
        StampContent over;
        PageResources pageResources;
        int replacePoint = 0;

        PageStamp(PdfStamperImp stamper, PdfReader reader, PdfDictionary pageN) {
            this.pageN = pageN;
            pageResources = new PageResources();
            PdfDictionary resources = pageN.getAsDict(PdfName.RESOURCES);
            pageResources.setOriginalResources(resources, stamper.namePtr);
        }
    }
}

com/itextpdf/text/pdf/PdfStamperImp.java

 

⇒ iText-2.1.6.jar - iText, a JAVA-PDF library

⇐ iText layout.jar Source Code

⇑ Download and Install iText Java Library

⇑⇑ iText for PDF Generation

2021-07-03, 47761👍, 0💬