All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aowagie.text.pdf.PdfStamperImp Maven / Gradle / Ivy

/*
 * Copyright 2003 by Paulo Soares.
 *
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the License.
 *
 * The Original Code is 'iText, a free JAVA-PDF library'.
 *
 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
 * All Rights Reserved.
 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
 *
 * Contributor(s): all the names of the contributors are added in the source code
 * where applicable.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
 * provisions of LGPL are applicable instead of those above.  If you wish to
 * allow use of your version of this file only under the terms of the LGPL
 * License and not to allow others to use your version of this file under
 * the MPL, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the LGPL.
 * If you do not delete the provisions above, a recipient may use your version
 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the MPL as stated above or under the terms of the GNU
 * Library General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or any later version.
 *
 * This library 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 Library general Public License for more
 * details.
 *
 * If you didn't download this code from the following link, you should check if
 * you aren't using an obsolete version:
 * http://www.lowagie.com/iText/
 */
package com.aowagie.text.pdf;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import org.xml.sax.SAXException;

import com.aowagie.text.Document;
import com.aowagie.text.DocumentException;
import com.aowagie.text.ExceptionConverter;
import com.aowagie.text.Rectangle;
import com.aowagie.text.exceptions.BadPasswordException;
import com.aowagie.text.pdf.interfaces.PdfViewerPreferences;
import com.aowagie.text.pdf.internal.PdfViewerPreferencesImp;
import com.aowagie.text.xml.xmp.XmpReader;

public class PdfStamperImp extends PdfWriter {

    private PdfObject pdfFileID = null;

    private final HashMap readers2intrefs = new LinkedHashMap();
    private final HashMap readers2file = new LinkedHashMap();
    private final RandomAccessFileOrArray file;
    PdfReader reader;
    private final IntHashtable myXref = new IntHashtable();
    /** Integer(page number) -> PageStamp */
    private final HashMap pagesToContent = new LinkedHashMap();
    private boolean closed = false;
    /** Holds value of property rotateContents. */
    private boolean rotateContents = true;
    private AcroFields acroFields;
    private boolean flat = false;
    private boolean flatFreeText = false;
    private final int namePtr[] = {0};
    private final HashSet partialFlattening = new HashSet();
    private boolean useVp = false;
    private final PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
    private final HashMap fieldTemplates = new LinkedHashMap();
    private boolean fieldsAdded = false;
    private int sigFlags = 0;
    private final boolean append;
    private IntHashtable marked;
    private final int initialXrefSize;
    private PdfAction openAction;

    /** 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
     */
    PdfStamperImp(final PdfReader reader, final OutputStream os, final char pdfVersion, final boolean append, final Calendar globalDate) throws DocumentException, IOException {
        super(new PdfDocument(globalDate), os);
        if (!reader.isOpenedWithFullPermissions()) {
			throw new BadPasswordException("PdfReader not opened with owner password");
		}
        if (reader.isTampered()) {
			throw new DocumentException("The original document was reused. Read it again from file.");
		}
        reader.setTampered(true);
        this.reader = reader;
        this.file = reader.getSafeFile();
        this.append = append;
        if (append) {
            if (reader.isRebuilt()) {
				throw new DocumentException("Append mode requires a document without errors even if recovery was possible.");
			}
            if (reader.isEncrypted()) {
				this.crypto = new PdfEncryption(reader.getDecrypt());
			}
            this.pdf_version.setAppendmode(true);
            this.file.reOpen();
            final byte buf[] = new byte[8192];
            int n;
            while ((n = this.file.read(buf)) > 0) {
				this.os.write(buf, 0, n);
			}
            this.file.close();
            this.prevxref = reader.getLastXref();
            reader.setAppendable(true);
        }
        else {
            if (pdfVersion == 0) {
				super.setPdfVersion(reader.getPdfVersion());
			} else {
				super.setPdfVersion(pdfVersion);
			}
        }
        super.open();
        this.pdf.addWriter(this);
        if (append) {
            this.body.setRefnum(reader.getXrefSize());
            this.marked = new IntHashtable();
            if (reader.isNewXrefType()) {
				this.fullCompression = true;
			}
            if (reader.isHybridXref()) {
				this.fullCompression = false;
			}
        }
        this.initialXrefSize = reader.getXrefSize();
    }

    void close(final Map moreInfo, final Calendar globalDate) throws IOException {
        if (this.closed) {
			return;
		}
        if (this.useVp) {
            this.reader.setViewerPreferences(this.viewerPreferences);
            markUsed(this.reader.getTrailer().get(PdfName.ROOT));
        }
        if (this.flat) {
			flatFields();
		}
        if (this.flatFreeText) {
			flatFreeTextFields();
		}
        addFieldResources();
        final PdfDictionary catalog = this.reader.getCatalog();
        final PdfDictionary pages = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.PAGES));
        pages.put(PdfName.ITXT, new PdfString(Document.getRelease()));
        markUsed(pages);
        final PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), this.reader.getCatalog());
        if (this.acroFields != null && this.acroFields.getXfa().isChanged()) {
            markUsed(acroForm);
            if (!this.flat) {
				this.acroFields.getXfa().setXfa(this);
			}
        }
        if (this.sigFlags != 0) {
            if (acroForm != null) {
                acroForm.put(PdfName.SIGFLAGS, new PdfNumber(this.sigFlags));
                markUsed(acroForm);
                markUsed(catalog);
            }
        }
        this.closed = true;
        addSharedObjectsToBody();
        setOutlines();
        setJavaScript();
        addFileAttachments();
        if (this.openAction != null) {
            catalog.put(PdfName.OPENACTION, this.openAction);
        }
        if (this.pdf.pageLabels != null) {
			catalog.put(PdfName.PAGELABELS, this.pdf.pageLabels.getDictionary(this));
		}
        // OCG
        if (!this.documentOCG.isEmpty()) {
        	fillOCProperties(false);
        	final PdfDictionary ocdict = catalog.getAsDict(PdfName.OCPROPERTIES);
        	if (ocdict == null) {
        		this.reader.getCatalog().put(PdfName.OCPROPERTIES, this.OCProperties);
        	}
        	else {
        		ocdict.put(PdfName.OCGS, this.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, this.OCProperties.getAsDict(PdfName.D).get(PdfName.ORDER));
        		ddict.put(PdfName.RBGROUPS, this.OCProperties.getAsDict(PdfName.D).get(PdfName.RBGROUPS));
        		ddict.put(PdfName.OFF, this.OCProperties.getAsDict(PdfName.D).get(PdfName.OFF));
        		ddict.put(PdfName.AS, this.OCProperties.getAsDict(PdfName.D).get(PdfName.AS));
            }
        }
        // metadata
        int skipInfo = -1;
        final PRIndirectReference iInfo = (PRIndirectReference)this.reader.getTrailer().get(PdfName.INFO);
        final PdfDictionary oldInfo = (PdfDictionary)PdfReader.getPdfObject(iInfo);

        if (iInfo != null) {
			skipInfo = iInfo.getNumber();
		}

//        final String producer ="Cliente @firma";

        // XMP
        byte[] altMetadata = null;
        final PdfObject xmpo = PdfReader.getPdfObject(catalog.get(PdfName.METADATA));
        if (xmpo != null && xmpo.isStream()) {
            altMetadata = PdfReader.getStreamBytesRaw((PRStream)xmpo);
            PdfReader.killIndirect(catalog.get(PdfName.METADATA));
        }
        if (this.xmpMetadata != null) {
        	altMetadata = this.xmpMetadata;
        }
        // if there is XMP data to add: add it
        final PdfDate date = new PdfDate(globalDate);
        if (altMetadata != null) {
        	PdfStream xmp;
        	try {
        		final XmpReader xmpr = new XmpReader(altMetadata);
//        		if (!xmpr.replace("rdf:Description", "http://ns.adobe.com/pdf/1.3/", "Producer", producer)) {
//					xmpr.add("rdf:Description", "http://ns.adobe.com/pdf/1.3/", "pdf:Producer", producer);
//				}
        		if (!xmpr.replace("rdf:Description", "http://ns.adobe.com/xap/1.0/", "ModifyDate", date.getW3CDate())) {
					xmpr.add("rdf:Description", "http://ns.adobe.com/xap/1.0/", "xmp:ModifyDate", date.getW3CDate());
				}
        		xmpr.replace("rdf:Description", "http://ns.adobe.com/xap/1.0/", "MetadataDate", date.getW3CDate());
            	xmp = new PdfStream(xmpr.serializeDoc());
        	}
        	catch(final SAXException e) {
        		xmp = new PdfStream(altMetadata);
        	}
        	catch(final IOException e) {
        		xmp = new PdfStream(altMetadata);
        	}
        	xmp.put(PdfName.TYPE, PdfName.METADATA);
        	xmp.put(PdfName.SUBTYPE, PdfName.XML);
        	if (this.crypto != null && !this.crypto.isMetadataEncrypted()) {
        		final PdfArray ar = new PdfArray();
        		ar.add(PdfName.CRYPT);
        		xmp.put(PdfName.FILTER, ar);
        	}
        	if (this.append && xmpo != null) {
        		this.body.add(xmp, xmpo.getIndRef());
        	}
        	else {
        		catalog.put(PdfName.METADATA, this.body.add(xmp).getIndirectReference());
        		markUsed(catalog);
        	}
        }
        try {
            this.file.reOpen();
            alterContents();
            final int rootN = ((PRIndirectReference)this.reader.trailer.get(PdfName.ROOT)).getNumber();
            if (this.append) {
                final int keys[] = this.marked.getKeys();
                for (final int j : keys) {
                    final PdfObject obj = this.reader.getPdfObjectRelease(j);
                    if (obj != null && skipInfo != j && j < this.initialXrefSize) {
                        addToBody(obj, j, j != rootN);
                    }
                }
                for (int k = this.initialXrefSize; k < this.reader.getXrefSize(); ++k) {
                    final PdfObject obj = this.reader.getPdfObject(k);
                    if (obj != null) {
                        addToBody(obj, getNewObjectNumber(this.reader, k, 0));
                    }
                }
            }
            else {
                for (int k = 1; k < this.reader.getXrefSize(); ++k) {
                    final PdfObject obj = this.reader.getPdfObjectRelease(k);
                    if (obj != null && skipInfo != k) {
                        addToBody(obj, getNewObjectNumber(this.reader, k, 0), k != rootN);
                    }
                }
            }
        }
        finally {
            try {
                this.file.close();
            }
            catch (final Exception e) {
                // empty on purpose
            }
        }
        PdfIndirectReference encryption = null;
        PdfObject fileID = null;
        if (this.crypto != null) {
            if (this.append) {
                encryption = this.reader.getCryptoRef();
            }
            else {
                final PdfIndirectObject encryptionObject = addToBody(this.crypto.getEncryptionDictionary(), false);
                encryption = encryptionObject.getIndirectReference();
            }
            fileID = this.crypto.getFileID();
        }
        else {
            fileID = PdfEncryption.createInfoId(PdfEncryption.createDocumentId());
        }

        this.pdfFileID = fileID;

        final PRIndirectReference iRoot = (PRIndirectReference)this.reader.trailer.get(PdfName.ROOT);
        final PdfIndirectReference root = new PdfIndirectReference(0, getNewObjectNumber(this.reader, iRoot.getNumber(), 0));
        PdfIndirectReference info = null;
        final PdfDictionary newInfo = new PdfDictionary();
        // Copia el viejo diccionario en el nuevo
        if (oldInfo != null) {
            for (final Object element : oldInfo.getKeys()) {
                final PdfName key = (PdfName)element;
                final PdfObject value = PdfReader.getPdfObject(oldInfo.get(key));
                newInfo.put(key, value);
            }
        }
        // Introduce los nuevos valores en el dicionario
        if (moreInfo != null) {
            for (final Iterator i = moreInfo.entrySet().iterator(); i.hasNext();) {
                final Map.Entry entry = (Map.Entry) i.next();
                final String key = (String) entry.getKey();
                final PdfName keyName = new PdfName(key);
                final String value = (String) entry.getValue();
                // Si el valor estaba, pero vacio, es que queremos borrar la entrada
                if (value == null) {
					newInfo.remove(keyName);
				}
                // Si la entrada tiene valor, la introducimos
                else {
					newInfo.put(keyName, new PdfString(value, PdfObject.TEXT_UNICODE));
				}
            }
        }
        // "ModDate" y "Producer" se sobreescriben
        newInfo.put(PdfName.MODDATE, date);
//        newInfo.put(PdfName.PRODUCER, new PdfString(producer));
        if (this.append) {
            if (iInfo == null) {
				info = addToBody(newInfo, false).getIndirectReference();
			}
            else {
				info = addToBody(newInfo, iInfo.getNumber(), false).getIndirectReference();
			}
        }
        else {
            info = addToBody(newInfo, false).getIndirectReference();
        }
        // write the cross-reference table of the body
        this.body.writeCrossReferenceTable(this.os, root, info, encryption, fileID, this.prevxref);
        if (this.fullCompression) {
            this.os.write(getISOBytes("startxref\n"));
            this.os.write(getISOBytes(String.valueOf(this.body.offset())));
            this.os.write(getISOBytes("\n%%EOF\n"));
        }
        else {
            final PdfTrailer trailer = new PdfTrailer(this.body.size(),
            this.body.offset(),
            root,
            info,
            encryption,
            fileID, this.prevxref);
            trailer.toPdf(this, this.os);
        }
        this.os.flush();
        if (isCloseStream()) {
			this.os.close();
		}
        this.reader.close();
    }

    private void applyRotation(final PdfDictionary pageN, final ByteBuffer out) {
        if (!this.rotateContents) {
			return;
		}
        final Rectangle page = this.reader.getPageSizeWithRotation(pageN);
        final int rotation = page.getRotation();
        switch (rotation) {
            case 90:
                out.append(PdfContents.ROTATE90);
                out.append(page.getTop());
                out.append(' ').append('0').append(PdfContents.ROTATEFINAL);
                break;
            case 180:
                out.append(PdfContents.ROTATE180);
                out.append(page.getRight());
                out.append(' ');
                out.append(page.getTop());
                out.append(PdfContents.ROTATEFINAL);
                break;
            case 270:
                out.append(PdfContents.ROTATE270);
                out.append('0').append(' ');
                out.append(page.getRight());
                out.append(PdfContents.ROTATEFINAL);
                break;
        }
    }

    private void alterContents() throws IOException {
        for (final Iterator i = this.pagesToContent.values().iterator(); i.hasNext();) {
            final PageStamp ps = (PageStamp)i.next();
            final PdfDictionary pageN = ps.pageN;
            markUsed(pageN);
            PdfArray ar = null;
            final PdfObject content = PdfReader.getPdfObject(pageN.get(PdfName.CONTENTS), pageN);
            if (content == null) {
                ar = new PdfArray();
                pageN.put(PdfName.CONTENTS, ar);
            }
            else if (content.isArray()) {
                ar = (PdfArray)content;
                markUsed(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);
            }
            final 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(this.compressionLevel);
            ar.addFirst(addToBody(stream).getIndirectReference());
            out.reset();
            if (ps.over != null) {
                out.append(' ');
                out.append(PdfContents.RESTORESTATE);
                final 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(this.compressionLevel);
                ar.add(addToBody(stream).getIndirectReference());
            }
            alterResources(ps);
        }
    }

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

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

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

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



    private static void findAllObjects(final PdfReader reader, final PdfObject obj, final IntHashtable hits) {
        if (obj == null) {
			return;
		}
        switch (obj.type()) {
            case PdfObject.INDIRECT:
                final 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:
                final 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:
                final PdfDictionary dic = (PdfDictionary)obj;
			for (final Object element : dic.getKeys()) {
			    final PdfName name = (PdfName)element;
			    findAllObjects(reader, dic.get(name), hits);
			}
                return;
        }
    }



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



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

    private void correctAcroFieldPages(final int page) {
        if (this.acroFields == null) {
			return;
		}
        if (page > this.reader.getNumberOfPages()) {
			return;
		}
        final HashMap fields = this.acroFields.getFields();
        for (final Iterator it = fields.values().iterator(); it.hasNext();) {
            final AcroFields.Item item = (AcroFields.Item)it.next();
            for (int k = 0; k < item.size(); ++k) {
                final int p = item.getPage(k).intValue();
                if (p >= page) {
					item.forcePage(k, p + 1);
				}
            }
        }
    }

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

    void insertPage(int pageNumber, final Rectangle mediabox) {
        final Rectangle media = new Rectangle(mediabox);
        final int rotation = media.getRotation() % 360;
        final PdfDictionary page = new PdfDictionary(PdfName.PAGE);
        final PdfDictionary resources = new PdfDictionary();
        final PdfArray procset = new PdfArray();
        procset.add(PdfName.PDF);
        procset.add(PdfName.TEXT);
        procset.add(PdfName.IMAGEB);
        procset.add(PdfName.IMAGEC);
        procset.add(PdfName.IMAGEI);
        resources.put(PdfName.PROCSET, procset);
        page.put(PdfName.RESOURCES, resources);
        page.put(PdfName.ROTATE, new PdfNumber(rotation));
        page.put(PdfName.MEDIABOX, new PdfRectangle(media, rotation));
        final PRIndirectReference pref = this.reader.addPdfObject(page);
        PdfDictionary parent;
        PRIndirectReference parentRef;
        if (pageNumber > this.reader.getNumberOfPages()) {
            final PdfDictionary lastPage = this.reader.getPageNRelease(this.reader.getNumberOfPages());
            parentRef = (PRIndirectReference)lastPage.get(PdfName.PARENT);
            parentRef = new PRIndirectReference(this.reader, parentRef.getNumber());
            parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
            final PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
            kids.add(pref);
            markUsed(kids);
            this.reader.pageRefs.insertPage(pageNumber, pref);
        }
        else {
            if (pageNumber < 1) {
				pageNumber = 1;
			}
            final PdfDictionary firstPage = this.reader.getPageN(pageNumber);
            final PRIndirectReference firstPageRef = this.reader.getPageOrigRef(pageNumber);
            this.reader.releasePage(pageNumber);
            parentRef = (PRIndirectReference)firstPage.get(PdfName.PARENT);
            parentRef = new PRIndirectReference(this.reader, parentRef.getNumber());
            parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
            final PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
            final int len = kids.size();
            final int num = firstPageRef.getNumber();
            for (int k = 0; k < len; ++k) {
                final PRIndirectReference cur = (PRIndirectReference)kids.getPdfObject(k);
                if (num == cur.getNumber()) {
                    kids.add(k, pref);
                    break;
                }
            }
            if (len == kids.size()) {
				throw new RuntimeException("Internal inconsistence.");
			}
            markUsed(kids);
            this.reader.pageRefs.insertPage(pageNumber, pref);
            correctAcroFieldPages(pageNumber);
        }
        page.put(PdfName.PARENT, parentRef);
        while (parent != null) {
            markUsed(parent);
            final 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(final boolean rotateContents) {
        this.rotateContents = rotateContents;
    }

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

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

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

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



    private void flatFields() {
        if (this.append) {
			throw new IllegalArgumentException("Field flattening is not supported in append mode.");
		}
        getAcroFields();
        final HashMap fields = this.acroFields.getFields();
        if (this.fieldsAdded && this.partialFlattening.isEmpty()) {
            for (final Iterator i = fields.keySet().iterator(); i.hasNext();) {
                this.partialFlattening.add(i.next());
            }
        }
        final PdfDictionary acroForm = this.reader.getCatalog().getAsDict(PdfName.ACROFORM);
        PdfArray acroFds = null;
        if (acroForm != null) {
            acroFds = (PdfArray)PdfReader.getPdfObject(acroForm.get(PdfName.FIELDS), acroForm);
        }
        for (final Iterator i = fields.entrySet().iterator(); i.hasNext();) {
            final Map.Entry entry = (Map.Entry) i.next();
            final String name = (String) entry.getKey();
            if (!this.partialFlattening.isEmpty() && !this.partialFlattening.contains(name)) {
				continue;
			}
            final AcroFields.Item item = (AcroFields.Item) entry.getValue();
            for (int k = 0; k < item.size(); ++k) {
                final PdfDictionary merged = item.getMerged(k);
                final PdfNumber ff = merged.getAsNumber(PdfName.F);
                int flags = 0;
                if (ff != null) {
					flags = ff.intValue();
				}
                final int page = item.getPage(k).intValue();
                final PdfDictionary appDic = merged.getAsDict(PdfName.AP);
                if (appDic != null && (flags & PdfAnnotation.FLAGS_PRINT) != 0 && (flags & PdfAnnotation.FLAGS_HIDDEN) == 0) {
                    final PdfObject obj = appDic.get(PdfName.N);
                    PdfAppearance app = null;
                    if (obj != 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 && objReal.isDictionary()) {
                                final PdfName as = merged.getAsName(PdfName.AS);
                                if (as != null) {
                                    final 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) {
                        final Rectangle box = PdfReader.getNormalizedRectangle(merged.getAsArray(PdfName.RECT));
                        final PdfContentByte cb = getOverContent(page);
                        cb.setLiteral("Q ");
                        cb.addTemplate(app, box.getLeft(), box.getBottom());
                        cb.setLiteral("q ");
                    }
                }
                if (this.partialFlattening.isEmpty()) {
					continue;
				}
                final PdfDictionary pageDic = this.reader.getPageN(page);
                final PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
                if (annots == null) {
					continue;
				}
                for (int idx = 0; idx < annots.size(); ++idx) {
                    final PdfObject ran = annots.getPdfObject(idx);
                    if (!ran.isIndirect()) {
						continue;
					}
                    final 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) {
                            final PdfDictionary wd = (PdfDictionary)PdfReader.getPdfObject(wdref);
                            final PRIndirectReference parentRef = (PRIndirectReference)wd.get(PdfName.PARENT);
                            PdfReader.killIndirect(wdref);
                            if (parentRef == null) { // reached AcroForm
                                for (int fr = 0; fr < acroFds.size(); ++fr) {
                                    final PdfObject h = acroFds.getPdfObject(fr);
                                    if (h.isIndirect() && ((PRIndirectReference)h).getNumber() == wdref.getNumber()) {
                                        acroFds.remove(fr);
                                        --fr;
                                    }
                                }
                                break;
                            }
                            final PdfDictionary parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
                            final PdfArray kids = parent.getAsArray(PdfName.KIDS);
                            for (int fr = 0; fr < kids.size(); ++fr) {
                                final 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 (!this.fieldsAdded && this.partialFlattening.isEmpty()) {
            for (int page = 1; page <= this.reader.getNumberOfPages(); ++page) {
                final PdfDictionary pageDic = this.reader.getPageN(page);
                final PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
                if (annots == null) {
					continue;
				}
                for (int idx = 0; idx < annots.size(); ++idx) {
                    final 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();
        }
    }

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

    private void sweepKids(final PdfObject obj) {
        final PdfObject oo = PdfReader.killIndirect(obj);
        if (oo == null || !oo.isDictionary()) {
			return;
		}
        final PdfDictionary dic = (PdfDictionary)oo;
        final 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));
        }
    }

    private void flatFreeTextFields()
	{
		if (this.append) {
			throw new IllegalArgumentException("FreeText flattening is not supported in append mode.");
		}

		for (int page = 1; page <= this.reader.getNumberOfPages(); ++page)
		{
			final PdfDictionary pageDic = this.reader.getPageN(page);
			final PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
			if (annots == null) {
				continue;
			}
			for (int idx = 0; idx < annots.size(); ++idx)
			{
				final PdfObject annoto = annots.getDirectObject(idx);
				if (annoto instanceof PdfIndirectReference && !annoto.isIndirect()) {
					continue;
				}

				final PdfDictionary annDic = (PdfDictionary)annoto;
 				if (!((PdfName)annDic.get(PdfName.SUBTYPE)).equals(PdfName.FREETEXT)) {
					continue;
				}
				final PdfNumber ff = annDic.getAsNumber(PdfName.F);
                final int flags = ff != null ? ff.intValue() : 0;

				if ( (flags & PdfAnnotation.FLAGS_PRINT) != 0 && (flags & PdfAnnotation.FLAGS_HIDDEN) == 0)
				{
					final PdfObject obj1 = annDic.get(PdfName.AP);
					if (obj1 == null) {
						continue;
					}
					final PdfDictionary appDic = obj1 instanceof PdfIndirectReference ?
							(PdfDictionary) PdfReader.getPdfObject(obj1) : (PdfDictionary) obj1;
					final PdfObject obj = appDic.get(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.isDictionary())
						{
							final PdfName as_p = appDic.getAsName(PdfName.AS);
							if (as_p != null)
							{
								final 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);
									}
								}
							}
						}
					}
					if (app != null)
					{
						final Rectangle box = PdfReader.getNormalizedRectangle(annDic.getAsArray(PdfName.RECT));
						final PdfContentByte cb = getOverContent(page);
						cb.setLiteral("Q ");
						cb.addTemplate(app, box.getLeft(), box.getBottom());
						cb.setLiteral("q ");
					}
				}
			}
			for (int idx = 0; idx < annots.size(); ++idx)
			{
			    final PdfDictionary annot = annots.getAsDict(idx);
				if (annot != null)
				{
					if (PdfName.FREETEXT.equals(annot.get(PdfName.SUBTYPE)))
					{
					    annots.remove(idx);
						--idx;
					}
				}
			}
			if (annots.isEmpty())
			{
				PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
				pageDic.remove(PdfName.ANNOTS);
			}
		}
	}

    /**
     * @see com.aowagie.text.pdf.PdfWriter#getPageReference(int)
     */
    @Override
	public PdfIndirectReference getPageReference(final int page) {
        final PdfIndirectReference ref = this.reader.getPageOrigRef(page);
        if (ref == null) {
			throw new IllegalArgumentException("Invalid page number " + page);
		}
        return ref;
    }

    /**
     * @see com.aowagie.text.pdf.PdfWriter#addAnnotation(com.aowagie.text.pdf.PdfAnnotation)
     */
    @Override
	public void addAnnotation(final PdfAnnotation annot) {
        throw new RuntimeException("Unsupported in this context. Use PdfStamper.addAnnotation()");
    }

    private void addDocumentField(final PdfIndirectReference ref) {
        final PdfDictionary catalog = this.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);
    }

    private void addFieldResources() throws IOException {
        if (this.fieldTemplates.isEmpty()) {
			return;
		}
        final PdfDictionary catalog = this.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 (final Iterator it = this.fieldTemplates.keySet().iterator(); it.hasNext();) {
            final PdfTemplate template = (PdfTemplate)it.next();
            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)) {
            final 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)) {
            final 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);
        }
    }

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

    void addAnnotation(PdfAnnotation annot, PdfDictionary pageN) {
        try {
            final ArrayList allAnnots = new ArrayList();
            if (annot.isForm()) {
                this.fieldsAdded = true;
                getAcroFields();
                final 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 = (PdfAnnotation)allAnnots.get(k);
                if (annot.getPlaceInPage() > 0) {
					pageN = this.reader.getPageN(annot.getPlaceInPage());
				}
                if (annot.isForm()) {
                    if (!annot.isUsed()) {
                        final HashMap templates = annot.getTemplates();
                        if (templates != null) {
							this.fieldTemplates.putAll(templates);
						}
                    }
                    final PdfFormField field = (PdfFormField)annot;
                    if (field.getParent() == null) {
						addDocumentField(field.getIndirectReference());
					}
                }
                if (annot.isAnnotation()) {
                    final 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()) {
                        final PdfRectangle rect = (PdfRectangle)annot.get(PdfName.RECT);
                        if (rect != null && (rect.left() != 0 || rect.right() != 0 || rect.top() != 0 || rect.bottom() != 0)) {
                            final int rotation = this.reader.getPageRotation(pageN);
                            final Rectangle pageSize = this.reader.getPageSizeWithRotation(pageN);
                            switch (rotation) {
                                case 90:
                                    annot.put(PdfName.RECT, new PdfRectangle(
                                    pageSize.getTop() - rect.bottom(),
                                    rect.left(),
                                    pageSize.getTop() - rect.top(),
                                    rect.right()));
                                    break;
                                case 180:
                                    annot.put(PdfName.RECT, new PdfRectangle(
                                    pageSize.getRight() - rect.left(),
                                    pageSize.getTop() - rect.bottom(),
                                    pageSize.getRight() - rect.right(),
                                    pageSize.getTop() - rect.top()));
                                    break;
                                case 270:
                                    annot.put(PdfName.RECT, new PdfRectangle(
                                    rect.bottom(),
                                    pageSize.getRight() - rect.left(),
                                    rect.top(),
                                    pageSize.getRight() - rect.right()));
                                    break;
                            }
                        }
                    }
                }
                if (!annot.isUsed()) {
                    annot.setUsed();
                    addToBody(annot, annot.getIndirectReference());
                }
            }
        }
        catch (final IOException e) {
            throw new ExceptionConverter(e);
        }
    }

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

    private void outlineTravel(PRIndirectReference outline) {
        while (outline != null) {
            final PdfDictionary outlineR = (PdfDictionary)PdfReader.getPdfObjectRelease(outline);
            final 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);
        }
    }

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

    private void setJavaScript() throws IOException {
        final HashMap djs = this.pdf.getDocumentLevelJS();
        if (djs.isEmpty()) {
			return;
		}
        final PdfDictionary catalog = this.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);
        final PdfDictionary tree = PdfNameTree.writeTree(djs, this);
        names.put(PdfName.JAVASCRIPT, addToBody(tree).getIndirectReference());
    }

    private void addFileAttachments() throws IOException {
        final HashMap fs = this.pdf.getDocumentFileAttachment();
        if (fs.isEmpty()) {
			return;
		}
        final PdfDictionary catalog = this.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);
        final HashMap old = PdfNameTree.readTree((PdfDictionary)PdfReader.getPdfObjectRelease(names.get(PdfName.EMBEDDEDFILES)));
        for (final Iterator it = fs.entrySet().iterator(); it.hasNext();) {
            final Map.Entry entry = (Map.Entry) it.next();
            final String name = (String) entry.getKey();
            int k = 0;
            String nn = name;
            while (old.containsKey(nn)) {
                ++k;
                nn += " " + k;
            }
            old.put(nn, entry.getValue());
        }
        final PdfDictionary tree = PdfNameTree.writeTree(old, this);
        names.put(PdfName.EMBEDDEDFILES, addToBody(tree).getIndirectReference());
    }



    private void setOutlines() throws IOException {
        if (this.newBookmarks == null) {
			return;
		}
        deleteOutlines();
        if (this.newBookmarks.isEmpty()) {
			return;
		}
        final PdfDictionary catalog = this.reader.getCatalog();
        final 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(final int preferences) {
        this.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(final PdfName key, final PdfObject value) {
    	this.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(final int f) {
        this.sigFlags |= f;
    }

    /** Always throws an UnsupportedOperationException.
     * @param actionType ignore
     * @param action ignore
     * @throws PdfException ignore
     */
    @Override
	public void setPageAction(final PdfName actionType, final PdfAction action) throws PdfException {
        throw new UnsupportedOperationException("Use setPageAction(PdfName actionType, PdfAction action, int page)");
    }



    /**
     * Always throws an UnsupportedOperationException.
     * @param seconds ignore
     */
    @Override
	public void setDuration(final int seconds) {
        throw new UnsupportedOperationException("Use setPageAction(PdfName actionType, PdfAction action, int page)");
    }

    /**
     * Always throws an UnsupportedOperationException.
     * @param transition ignore
     */
    @Override
	public void setTransition(final PdfTransition transition) {
        throw new UnsupportedOperationException("Use setPageAction(PdfName actionType, PdfAction action, int page)");
    }





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



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

    /** 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: DOCUMENT_CLOSE,
     * WILL_SAVE, DID_SAVE, WILL_PRINT
     * and DID_PRINT.
     *
     * @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(final PdfName actionType, final 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("Invalid additional action type: " + actionType.toString());
        }
        PdfDictionary aa = this.reader.getCatalog().getAsDict(PdfName.AA);
        if (aa == null) {
            if (action == null) {
				return;
			}
            aa = new PdfDictionary();
            this.reader.getCatalog().put(PdfName.AA, aa);
        }
        markUsed(aa);
        if (action == null) {
			aa.remove(actionType);
		} else {
			aa.put(actionType, action);
		}
    }

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

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

    /**
     * @see com.aowagie.text.pdf.PdfWriter#setThumbnail(com.aowagie.text.Image)
     */
    @Override
	public void setThumbnail(final com.aowagie.text.Image image) {
        throw new UnsupportedOperationException("Use PdfStamper.setThumbnail().");
    }



    @Override
	public PdfContentByte getDirectContentUnder() {
        throw new UnsupportedOperationException("Use PdfStamper.getUnderContent() or PdfStamper.getOverContent()");
    }

    @Override
	public PdfContentByte getDirectContent() {
        throw new UnsupportedOperationException("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
     */
    private void readOCProperties() {
    	if (!this.documentOCG.isEmpty()) {
    		return;
    	}
    	final PdfDictionary dict = this.reader.getCatalog().getAsDict(PdfName.OCPROPERTIES);
    	if (dict == null) {
    		return;
    	}
    	final PdfArray ocgs = dict.getAsArray(PdfName.OCGS);
    	PdfIndirectReference ref;
    	PdfLayer layer;
    	final HashMap ocgmap = new LinkedHashMap();
    	for (final Iterator 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);
    	}
    	final PdfDictionary d = dict.getAsDict(PdfName.D);
    	final PdfArray off = d.getAsArray(PdfName.OFF);
    	if (off != null) {
    		for (final Iterator i = off.listIterator(); i.hasNext(); ) {
    			ref = (PdfIndirectReference)i.next();
    			layer = (PdfLayer)ocgmap.get(ref.toString());
    			layer.setOn(false);
    		}
    	}
    	final PdfArray order = d.getAsArray(PdfName.ORDER);
    	if (order != null) {
    		addOrder(null, order, ocgmap);
    	}
    	this.documentOCG.addAll(ocgmap.values());
    	this.OCGRadioGroup = d.getAsArray(PdfName.RBGROUPS);
    	this.OCGLocked = d.getAsArray(PdfName.LOCKED);
    	if (this.OCGLocked == null) {
			this.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(final PdfLayer parent, final PdfArray arr, final Map ocgmap) {
    	PdfObject obj;
    	PdfLayer layer;
    	for (int i = 0; i < arr.size(); i++) {
    		obj = arr.getPdfObject(i);
    		if (obj.isIndirect()) {
    			layer = (PdfLayer)ocgmap.get(obj.toString());
    			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()) {
    		    final 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);
    				}
    				final PdfArray array = new PdfArray();
    				for (final Iterator j = sub.listIterator(); j.hasNext(); ) {
    					array.add((PdfObject)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 getPdfLayers() {
    	if (this.documentOCG.isEmpty()) {
    		readOCProperties();
    	}
    	final HashMap map = new LinkedHashMap();
    	PdfLayer layer;
    	String key;
    	for (final Iterator i = this.documentOCG.iterator(); i.hasNext(); ) {
    		layer = (PdfLayer)i.next();
    		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;
    }

    static class PageStamp {

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

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

    public PdfObject getFileID() {
        return this.pdfFileID;
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy