/*
 *
 * 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.text.Chunk;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.Version;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.io.RASInputStream;
import com.itextpdf.text.io.RandomAccessSource;
import com.itextpdf.text.io.RandomAccessSourceFactory;
import com.itextpdf.text.pdf.AcroFields.Item;
import com.itextpdf.text.pdf.interfaces.PdfVersion;
import com.itextpdf.text.pdf.security.CertificateInfo;
import com.itextpdf.text.pdf.security.CertificateInfo.X500Name;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;

/**
 * Class that takes care of the cryptographic options
 * and appearances that form a signature.
 */
public class PdfSignatureAppearance {

	/**
	 * Constructs a PdfSignatureAppearance object.
     * @param writer	the writer to which the signature will be written.
     */
    PdfSignatureAppearance(PdfStamperImp writer) {
        this.writer = writer;
        signDate = new GregorianCalendar();
        fieldName = getNewSigName();
        signatureCreator = Version.getInstance().getVersion();
    }

	/*
	 * SIGNATURE
	 */

    // signature types

    /** Approval signature */
    public static final int NOT_CERTIFIED = 0;

    /** Author signature, no changes allowed */
    public static final int CERTIFIED_NO_CHANGES_ALLOWED = 1;

    /** Author signature, form filling allowed */
    public static final int CERTIFIED_FORM_FILLING = 2;

    /** Author signature, form filling and annotations allowed */
    public static final int CERTIFIED_FORM_FILLING_AND_ANNOTATIONS = 3;

    /** The certification level */
    private int certificationLevel = NOT_CERTIFIED;

    /**
     * Sets the document type to certified instead of simply signed.
     * @param certificationLevel the values can be: <code>NOT_CERTIFIED</code>, <code>CERTIFIED_NO_CHANGES_ALLOWED</code>,
     * <code>CERTIFIED_FORM_FILLING</code> and <code>CERTIFIED_FORM_FILLING_AND_ANNOTATIONS</code>
     */
    public void setCertificationLevel(int certificationLevel) {
        this.certificationLevel = certificationLevel;
    }

    /**
     * Gets the certified status of this document.
     * @return the certified status
     */
    public int getCertificationLevel() {
        return this.certificationLevel;
    }

    // signature info

    /** The caption for the reason for signing. */
    private String reasonCaption = "Reason: ";

    /** The caption for the location of signing. */
    private String locationCaption = "Location: ";

    /** The reason for signing. */
    private String reason;

    /** Holds value of property location. */
    private String location;

    /** Holds value of property signDate. */
    private Calendar signDate;

    /**
     * Gets the signing reason.
     * @return the signing reason
     */
    public String getReason() {
        return this.reason;
    }

    /**
     * Sets the signing reason.
     * @param reason the signing reason
     */
    public void setReason(String reason) {
        this.reason = reason;
    }

    /**
     * Sets the caption for signing reason.
     * @param reasonCaption the signing reason caption
     */
    public void setReasonCaption(String reasonCaption) {
        this.reasonCaption = reasonCaption;
    }

    /**
     * Gets the signing location.
     * @return the signing location
     */
    public String getLocation() {
        return this.location;
    }

    /**
     * Sets the signing location.
     * @param location the signing location
     */
    public void setLocation(String location) {
        this.location = location;
    }

    /**
     * Sets the caption for the signing location.
     * @param locationCaption the signing location caption
     */
    public void setLocationCaption(String locationCaption) {
        this.locationCaption = locationCaption;
    }

    /** Holds value of the application that creates the signature */
    private String signatureCreator;

    /**
     * Gets the signature creator.
     * @return the signature creator
     */
    public String getSignatureCreator(){
    	return signatureCreator;
    }

    /**
     * Sets the name of the application used to create the signature.
     * @param signatureCreator the name of the signature creating application
     */
    public void setSignatureCreator(String signatureCreator){
    	this.signatureCreator = signatureCreator;
    }

    /** The contact name of the signer. */
    private String contact;

    /**
     * Gets the signing contact.
     * @return the signing contact
     */
    public String getContact() {
        return this.contact;
    }

    /**
     * Sets the signing contact.
     * @param contact the signing contact
     */
    public void setContact(String contact) {
        this.contact = contact;
    }

    /**
     * Gets the signature date.
     * @return the signature date
     */
    public java.util.Calendar getSignDate() {
        return signDate;
    }

    /**
     * Sets the signature date.
     * @param signDate the signature date
     */
    public void setSignDate(java.util.Calendar signDate) {
        this.signDate = signDate;
    }

    // the PDF file

    /** The file right before the signature is added (can be null). */
    private RandomAccessFile raf;
    /** The bytes of the file right before the signature is added (if raf is null) */
    private byte[] bout;
    /** Array containing the byte positions of the bytes that need to be hashed. */
    private long[] range;

    /**
     * Gets the document bytes that are hashable when using external signatures. The general sequence is:
     * preClose(), getRangeStream() and close().
     * <p>
     * @return the document bytes that are hashable
     */
    public InputStream getRangeStream() throws IOException {
    	RandomAccessSourceFactory fac = new RandomAccessSourceFactory();
        return new RASInputStream(fac.createRanged(getUnderlyingSource(), range));
    }

    /**
     * @return the underlying source
     * @throws IOException
     */
    private RandomAccessSource getUnderlyingSource() throws IOException {
    	//TODO: get rid of separate byte[] and RandomAccessFile objects and just store a RandomAccessSource
    	RandomAccessSourceFactory fac = new RandomAccessSourceFactory();
    	return raf == null ? fac.createSource(bout) : fac.createSource(raf);
    }

    /** The signing certificate */
    private Certificate signCertificate;

    // Developer extenstion

    /**
     * Adds the appropriate developer extension.
     */
	public void addDeveloperExtension(final PdfDeveloperExtension de) {
		writer.addDeveloperExtension(de);
	}

    // Crypto dictionary

    /** The crypto dictionary */
    private PdfDictionary cryptoDictionary;
    /**
     * Gets the user made signature dictionary. This is the dictionary at the /V key.
     * @return the user made signature dictionary
     */
    public com.itextpdf.text.pdf.PdfDictionary getCryptoDictionary() {
        return cryptoDictionary;
    }

    /**
     * Sets a user made signature dictionary. This is the dictionary at the /V key.
     * @param cryptoDictionary a user made signature dictionary
     */
    public void setCryptoDictionary(com.itextpdf.text.pdf.PdfDictionary cryptoDictionary) {
        this.cryptoDictionary = cryptoDictionary;
    }

    /**
     * Sets the certificate used to provide the text in the appearance.
     * This certificate doesn't take part in the actual signing process.
     * @param signCertificate the certificate
     */
    public void setCertificate(Certificate signCertificate) {
        this.signCertificate = signCertificate;
    }

    public Certificate getCertificate() {
        return signCertificate;
    }

    // Signature event

    /**
     * An interface to retrieve the signature dictionary for modification.
     */
    public interface SignatureEvent {
        /**
         * Allows modification of the signature dictionary.
         * @param sig the signature dictionary
         */
        public void getSignatureDictionary(PdfDictionary sig);
    }

    /**
     * Holds value of property signatureEvent.
     */
    private SignatureEvent signatureEvent;

    /**
     * Getter for property signatureEvent.
     * @return Value of property signatureEvent.
     */
    public SignatureEvent getSignatureEvent() {
        return this.signatureEvent;
    }

    /**
     * Sets the signature event to allow modification of the signature dictionary.
     * @param signatureEvent the signature event
     */
    public void setSignatureEvent(SignatureEvent signatureEvent) {
        this.signatureEvent = signatureEvent;
    }

	/*
	 * SIGNATURE FIELD
	 */

    /** The name of the field */
    private String fieldName;

    /**
     * Gets the field name.
     * @return the field name
     */
    public java.lang.String getFieldName() {
        return fieldName;
    }

    /**
     * Gets a new signature field name that
     * doesn't clash with any existing name.
     * @return a new signature field name
     */
    public String getNewSigName() {
        AcroFields af = writer.getAcroFields();
        String name = "Signature";
        int step = 0;
        boolean found = false;
        while (!found) {
            ++step;
            String n1 = name + step;
            if (af.getFieldItem(n1) != null)
                continue;
            n1 += ".";
            found = true;
            for (Object element : af.getFields().keySet()) {
                String fn = (String)element;
                if (fn.startsWith(n1)) {
                    found = false;
                    break;
                }
            }
        }
        name += step;
        return name;
    }

    /**
     * The page where the signature will appear.
     */
    private int page = 1;

    /**
     * Gets the page number of the field.
     * @return the page number of the field
     */
    public int getPage() {
        return page;
    }

    /**
     * The coordinates of the rectangle for a visible signature,
     * or a zero-width, zero-height rectangle for an invisible signature.
     */
    private Rectangle rect;

    /**
     * Gets the rectangle representing the signature dimensions.
     * @return the rectangle representing the signature dimensions. It may be <CODE>null</CODE>
     * or have zero width or height for invisible signatures
     */
    public Rectangle getRect() {
        return rect;
    }

    /** rectangle that represent the position and dimension of the signature in the page. */
    private Rectangle pageRect;

    /**
     * Gets the rectangle that represent the position and dimension of the signature in the page.
     * @return the rectangle that represent the position and dimension of the signature in the page
     */
    public Rectangle getPageRect() {
        return pageRect;
    }

    /**
     * Gets the visibility status of the signature.
     * @return the visibility status of the signature
     */
    public boolean isInvisible() {
        return rect == null || rect.getWidth() == 0 || rect.getHeight() == 0;
    }

    /**
     * Sets the signature to be visible. It creates a new visible signature field.
     * @param pageRect the position and dimension of the field in the page
     * @param page the page to place the field. The fist page is 1
     * @param fieldName the field name or <CODE>null</CODE> to generate automatically a new field name
     */
    public void setVisibleSignature(Rectangle pageRect, int page, String fieldName) {
        if (fieldName != null) {
            if (fieldName.indexOf('.') >= 0)
                throw new IllegalArgumentException(MessageLocalization.getComposedMessage("field.names.cannot.contain.a.dot"));
            AcroFields af = writer.getAcroFields();
            AcroFields.Item item = af.getFieldItem(fieldName);
            if (item != null)
                throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.field.1.already.exists", fieldName));
            this.fieldName = fieldName;
        }
        if (page < 1 || page > writer.reader.getNumberOfPages())
            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("invalid.page.number.1", page));
        this.pageRect = new Rectangle(pageRect);
        this.pageRect.normalize();
        rect = new Rectangle(this.pageRect.getWidth(), this.pageRect.getHeight());
        this.page = page;
    }

    /**
     * Sets the signature to be visible. An empty signature field with the same name must already exist.
     * @param fieldName the existing empty signature field name
     */
    public void setVisibleSignature(String fieldName) {
        AcroFields af = writer.getAcroFields();
        AcroFields.Item item = af.getFieldItem(fieldName);
        if (item == null)
            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.field.1.does.not.exist", fieldName));
        PdfDictionary merged = item.getMerged(0);
        if (!PdfName.SIG.equals(PdfReader.getPdfObject(merged.get(PdfName.FT))))
            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.field.1.is.not.a.signature.field", fieldName));
        this.fieldName = fieldName;
        PdfArray r = merged.getAsArray(PdfName.RECT);
        float llx = r.getAsNumber(0).floatValue();
        float lly = r.getAsNumber(1).floatValue();
        float urx = r.getAsNumber(2).floatValue();
        float ury = r.getAsNumber(3).floatValue();
        pageRect = new Rectangle(llx, lly, urx, ury);
        pageRect.normalize();
        page = item.getPage(0).intValue();
        int rotation = writer.reader.getPageRotation(page);
        Rectangle pageSize = writer.reader.getPageSizeWithRotation(page);
        switch (rotation) {
            case 90:
                pageRect = new Rectangle(
                pageRect.getBottom(),
                pageSize.getTop() - pageRect.getLeft(),
                pageRect.getTop(),
                pageSize.getTop() - pageRect.getRight());
                break;
            case 180:
                pageRect = new Rectangle(
                pageSize.getRight() - pageRect.getLeft(),
                pageSize.getTop() - pageRect.getBottom(),
                pageSize.getRight() - pageRect.getRight(),
                pageSize.getTop() - pageRect.getTop());
                break;
            case 270:
                pageRect = new Rectangle(
                pageSize.getRight() - pageRect.getBottom(),
                pageRect.getLeft(),
                pageSize.getRight() - pageRect.getTop(),
                pageRect.getRight());
                break;
        }
        if (rotation != 0)
            pageRect.normalize();
        rect = new Rectangle(this.pageRect.getWidth(), this.pageRect.getHeight());
    }

	/*
	 * SIGNATURE APPEARANCE
	 */

    /**
     * Signature rendering modes
     * @since 5.0.1
     */
    public enum RenderingMode {
        /**
         * The rendering mode is just the description.
         */
        DESCRIPTION,
        /**
         * The rendering mode is the name of the signer and the description.
         */
        NAME_AND_DESCRIPTION,
        /**
         * The rendering mode is an image and the description.
         */
        GRAPHIC_AND_DESCRIPTION,
        /**
         * The rendering mode is just an image.
         */
        GRAPHIC
    }

    /** The rendering mode chosen for visible signatures */
    private RenderingMode renderingMode = RenderingMode.DESCRIPTION;

    /**
    * Gets the rendering mode for this signature.
    * @return the rendering mode for this signature
    * @since 5.0.1
    */
    public RenderingMode getRenderingMode() {
        return renderingMode;
    }

    /**
     * Sets the rendering mode for this signature.
     * @param renderingMode the rendering mode
     * @since 5.0.1
     */
    public void setRenderingMode(RenderingMode renderingMode) {
        this.renderingMode = renderingMode;
    }

    /** The image that needs to be used for a visible signature */
    private Image signatureGraphic = null;

    /**
    * Gets the Image object to render.
    * @return the image
    */
    public Image getSignatureGraphic() {
        return signatureGraphic;
    }

    /**
     * Sets the Image object to render when Render is set to <CODE>RenderingMode.GRAPHIC</CODE>
     * or <CODE>RenderingMode.GRAPHIC_AND_DESCRIPTION</CODE>.
     * @param signatureGraphic image rendered. If <CODE>null</CODE> the mode is defaulted
     * to <CODE>RenderingMode.DESCRIPTION</CODE>
     */
    public void setSignatureGraphic(Image signatureGraphic) {
        this.signatureGraphic = signatureGraphic;
    }

    /** Appearance compliant with the recommendations introduced in Acrobat 6? */
    private boolean acro6Layers = true;

    /**
     * Gets the Acrobat 6.0 layer mode.
     * @return the Acrobat 6.0 layer mode
     */
    public boolean isAcro6Layers() {
        return this.acro6Layers;
    }

    /**
     * Acrobat 6.0 and higher recommends that only layer n0 and n2 be present.
     * Use this method with value <code>false</code> if you want to ignore this recommendation.
     * @param acro6Layers if <code>true</code> only the layers n0 and n2 will be present
     * @deprecated Adobe no longer supports Adobe Acrobat / Reader versions older than 9
     */
    public void setAcro6Layers(boolean acro6Layers) {
        this.acro6Layers = acro6Layers;
    }

    /** Layers for a visible signature. */
    private PdfTemplate app[] = new PdfTemplate[5];

    /**
     * Gets a template layer to create a signature appearance. The layers can go from 0 to 4,
     * but only layer 0 and 2 will be used if acro6Layers is true.
     * <p>
     * Consult <A HREF="http://partners.adobe.com/asn/developer/pdfs/tn/PPKAppearances.pdf">PPKAppearances.pdf</A>
     * for further details.
     * @param layer the layer
     * @return a template
     */
    public PdfTemplate getLayer(int layer) {
        if (layer < 0 || layer >= app.length)
            return null;
        PdfTemplate t = app[layer];
        if (t == null) {
            t = app[layer] = new PdfTemplate(writer);
            t.setBoundingBox(rect);
            writer.addDirectTemplateSimple(t, new PdfName("n" + layer));
        }
        return t;
    }

    /** Indicates if we need to reuse the existing appearance as layer 0. */
    private boolean reuseAppearance = false;

    /**
     * Indicates that the existing appearances needs to be reused as layer 0.
     */
    public void setReuseAppearance(boolean reuseAppearance) {
    	this.reuseAppearance = reuseAppearance;
    }

    // layer 1

    /** An appearance that can be used for layer 1 (if acro6Layers is false). */
    public static final String questionMark =
        "% DSUnknown\n" +
        "q\n" +
        "1 G\n" +
        "1 g\n" +
        "0.1 0 0 0.1 9 0 cm\n" +
        "0 J 0 j 4 M []0 d\n" +
        "1 i \n" +
        "0 g\n" +
        "313 292 m\n" +
        "313 404 325 453 432 529 c\n" +
        "478 561 504 597 504 645 c\n" +
        "504 736 440 760 391 760 c\n" +
        "286 760 271 681 265 626 c\n" +
        "265 625 l\n" +
        "100 625 l\n" +
        "100 828 253 898 381 898 c\n" +
        "451 898 679 878 679 650 c\n" +
        "679 555 628 499 538 435 c\n" +
        "488 399 467 376 467 292 c\n" +
        "313 292 l\n" +
        "h\n" +
        "308 214 170 -164 re\n" +
        "f\n" +
        "0.44 G\n" +
        "1.2 w\n" +
        "1 1 0.4 rg\n" +
        "287 318 m\n" +
        "287 430 299 479 406 555 c\n" +
        "451 587 478 623 478 671 c\n" +
        "478 762 414 786 365 786 c\n" +
        "260 786 245 707 239 652 c\n" +
        "239 651 l\n" +
        "74 651 l\n" +
        "74 854 227 924 355 924 c\n" +
        "425 924 653 904 653 676 c\n" +
        "653 581 602 525 512 461 c\n" +
        "462 425 441 402 441 318 c\n" +
        "287 318 l\n" +
        "h\n" +
        "282 240 170 -164 re\n" +
        "B\n" +
        "Q\n";

    // layer 2

    /** A background image for the text in layer 2. */
    private Image image;

    /**
     * Gets the background image for the layer 2.
     * @return the background image for the layer 2
     */
    public Image getImage() {
        return this.image;
    }

    /**
     * Sets the background image for the layer 2.
     * @param image the background image for the layer 2
     */
    public void setImage(Image image) {
        this.image = image;
    }

    /** the scaling to be applied to the background image.t  */
    private float imageScale;

    /**
     * Gets the scaling to be applied to the background image.
     * @return the scaling to be applied to the background image
     */
    public float getImageScale() {
        return this.imageScale;
    }

    /**
     * Sets the scaling to be applied to the background image. If it's zero the image
     * will fully fill the rectangle. If it's less than zero the image will fill the rectangle but
     * will keep the proportions. If it's greater than zero that scaling will be applied.
     * In any of the cases the image will always be centered. It's zero by default.
     * @param imageScale the scaling to be applied to the background image
     */
    public void setImageScale(float imageScale) {
        this.imageScale = imageScale;
    }

    /** The text that goes in Layer 2 of the signature appearance. */
    private String layer2Text;

    /**
     * Sets the signature text identifying the signer.
     * @param text the signature text identifying the signer. If <CODE>null</CODE> or not set
     * a standard description will be used
     */
    public void setLayer2Text(String text) {
        layer2Text = text;
    }

    /**
     * Gets the signature text identifying the signer if set by setLayer2Text().
     * @return the signature text identifying the signer
     */
    public String getLayer2Text() {
        return layer2Text;
    }

    /** Font for the text in Layer 2. */
    private Font layer2Font;

    /**
     * Gets the n2 and n4 layer font.
     * @return the n2 and n4 layer font
     */
    public Font getLayer2Font() {
        return this.layer2Font;
    }

    /**
     * Sets the n2 and n4 layer font. If the font size is zero, auto-fit will be used.
     * @param layer2Font the n2 and n4 font
     */
    public void setLayer2Font(Font layer2Font) {
        this.layer2Font = layer2Font;
    }

    /** Run direction for the text in layers 2 and 4. */
    private int runDirection = PdfWriter.RUN_DIRECTION_NO_BIDI;

    /** Sets the run direction in the n2 and n4 layer.
     * @param runDirection the run direction
     */
    public void setRunDirection(int runDirection) {
        if (runDirection < PdfWriter.RUN_DIRECTION_DEFAULT || runDirection > PdfWriter.RUN_DIRECTION_RTL)
            throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.run.direction.1", runDirection));
        this.runDirection = runDirection;
    }

    /** Gets the run direction.
     * @return the run direction
     */
    public int getRunDirection() {
        return runDirection;
    }

    // layer 4

    /** The text that goes in Layer 4 of the appearance. */
    private String layer4Text;

    /**
     * Sets the text identifying the signature status. Will be ignored if acro6Layers is true.
     * @param text the text identifying the signature status. If <CODE>null</CODE> or not set
     * the description "Signature Not Verified" will be used
     */
    public void setLayer4Text(String text) {
        layer4Text = text;
    }

    /**
     * Gets the text identifying the signature status if set by setLayer4Text().
     * @return the text identifying the signature status
     */
    public String getLayer4Text() {
        return layer4Text;
    }

    // all layers

    /** Template containing all layers drawn on top of each other. */
    private PdfTemplate frm;

    /**
     * Gets the template that aggregates all appearance layers. This corresponds to the /FRM resource.
     * <p>
     * Consult <A HREF="http://partners.adobe.com/asn/developer/pdfs/tn/PPKAppearances.pdf">PPKAppearances.pdf</A>
     * for further details.
     * @return the template that aggregates all appearance layers
     */
    public PdfTemplate getTopLayer() {
        if (frm == null) {
            frm = new PdfTemplate(writer);
            frm.setBoundingBox(rect);
            writer.addDirectTemplateSimple(frm, new PdfName("FRM"));
        }
        return frm;
    }

    // creating the appearance

    /** extra space at the top. */
    private static final float TOP_SECTION = 0.3f;

    /** margin for the content inside the signature rectangle. */
    private static final float MARGIN = 2;

    /**
     * Gets the main appearance layer.
     * <p>
     * Consult <A HREF="http://partners.adobe.com/asn/developer/pdfs/tn/PPKAppearances.pdf">PPKAppearances.pdf</A>
     * for further details.
     * @return the main appearance layer
     * @throws DocumentException on error
     */
    public PdfTemplate getAppearance() throws DocumentException {
        if (isInvisible()) {
            PdfTemplate t = new PdfTemplate(writer);
            t.setBoundingBox(new Rectangle(0, 0));
            writer.addDirectTemplateSimple(t, null);
            return t;
        }

        if (app[0] == null && !reuseAppearance) {
            createBlankN0();
        }
        if (app[1] == null && !acro6Layers) {
            PdfTemplate t = app[1] = new PdfTemplate(writer);
            t.setBoundingBox(new Rectangle(100, 100));
            writer.addDirectTemplateSimple(t, new PdfName("n1"));
            t.setLiteral(questionMark);
        }
        if (app[2] == null) {
            String text;
            if (layer2Text == null) {
                StringBuilder buf = new StringBuilder();
                buf.append("Digitally signed by ");
                String name = null;
                X500Name x500name = CertificateInfo.getSubjectFields((X509Certificate)signCertificate);
                if (x500name != null) {
                	name = x500name.getField("CN");
                	if (name == null)
                		name = x500name.getField("E");
                }
                if (name == null)
                    name = "";
                buf.append(name).append('\n');
                SimpleDateFormat sd = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
                buf.append("Date: ").append(sd.format(signDate.getTime()));
                if (reason != null)
                    buf.append('\n').append(reasonCaption).append(reason);
                if (location != null)
                    buf.append('\n').append(locationCaption).append(location);
                text = buf.toString();
            }
            else
                text = layer2Text;
            PdfTemplate t = app[2] = new PdfTemplate(writer);
            t.setBoundingBox(rect);
            writer.addDirectTemplateSimple(t, new PdfName("n2"));
            if (image != null) {
                if (imageScale == 0) {
                    t.addImage(image, rect.getWidth(), 0, 0, rect.getHeight(), 0, 0);
                }
                else {
                    float usableScale = imageScale;
                    if (imageScale < 0)
                        usableScale = Math.min(rect.getWidth() / image.getWidth(), rect.getHeight() / image.getHeight());
                    float w = image.getWidth() * usableScale;
                    float h = image.getHeight() * usableScale;
                    float x = (rect.getWidth() - w) / 2;
                    float y = (rect.getHeight() - h) / 2;
                    t.addImage(image, w, 0, 0, h, x, y);
                }
            }
            Font font;
            if (layer2Font == null)
                font = new Font();
            else
                font = new Font(layer2Font);
            float size = font.getSize();

            Rectangle dataRect = null;
            Rectangle signatureRect = null;

            if (renderingMode == RenderingMode.NAME_AND_DESCRIPTION ||
                renderingMode == RenderingMode.GRAPHIC_AND_DESCRIPTION && this.signatureGraphic != null) {
                // origin is the bottom-left
                signatureRect = new Rectangle(
                    MARGIN,
                    MARGIN,
                    rect.getWidth() / 2 - MARGIN,
                    rect.getHeight() - MARGIN);
                dataRect = new Rectangle(
                    rect.getWidth() / 2 +  MARGIN / 2,
                    MARGIN,
                    rect.getWidth() - MARGIN / 2,
                    rect.getHeight() - MARGIN);

                if (rect.getHeight() > rect.getWidth()) {
                    signatureRect = new Rectangle(
                        MARGIN,
                        rect.getHeight() / 2,
                        rect.getWidth() - MARGIN,
                        rect.getHeight());
                    dataRect = new Rectangle(
                        MARGIN,
                        MARGIN,
                        rect.getWidth() - MARGIN,
                        rect.getHeight() / 2 - MARGIN);
                }
            }
            else if (renderingMode == RenderingMode.GRAPHIC) {
                if (signatureGraphic == null) {
                    throw new IllegalStateException(MessageLocalization.getComposedMessage("a.signature.image.should.be.present.when.rendering.mode.is.graphic.only"));
                }
                signatureRect = new Rectangle(
                        MARGIN,
                        MARGIN,
                        rect.getWidth() - MARGIN, // take all space available
                        rect.getHeight() - MARGIN);
            }
            else {
                dataRect = new Rectangle(
                        MARGIN,
                        MARGIN,
                        rect.getWidth() - MARGIN,
                        rect.getHeight() * (1 - TOP_SECTION) - MARGIN);
            }

            switch (renderingMode) {
            case NAME_AND_DESCRIPTION:
                String signedBy = CertificateInfo.getSubjectFields((X509Certificate)signCertificate).getField("CN");
                if (signedBy == null)
                    signedBy = CertificateInfo.getSubjectFields((X509Certificate)signCertificate).getField("E");
                if (signedBy == null)
                    signedBy = "";
                Rectangle sr2 = new Rectangle(signatureRect.getWidth() - MARGIN, signatureRect.getHeight() - MARGIN );
                float signedSize = ColumnText.fitText(font, signedBy, sr2, -1, runDirection);

                ColumnText ct2 = new ColumnText(t);
                ct2.setRunDirection(runDirection);
                ct2.setSimpleColumn(new Phrase(signedBy, font), signatureRect.getLeft(), signatureRect.getBottom(), signatureRect.getRight(), signatureRect.getTop(), signedSize, Element.ALIGN_LEFT);

                ct2.go();
                break;
            case GRAPHIC_AND_DESCRIPTION:
                if (signatureGraphic == null) {
                    throw new IllegalStateException(MessageLocalization.getComposedMessage("a.signature.image.should.be.present.when.rendering.mode.is.graphic.and.description"));
                }
                ct2 = new ColumnText(t);
                ct2.setRunDirection(runDirection);
                ct2.setSimpleColumn(signatureRect.getLeft(), signatureRect.getBottom(), signatureRect.getRight(), signatureRect.getTop(), 0, Element.ALIGN_RIGHT);

                Image im = Image.getInstance(signatureGraphic);
                im.scaleToFit(signatureRect.getWidth(), signatureRect.getHeight());

                Paragraph p = new Paragraph();
                // must calculate the point to draw from to make image appear in middle of column
                float x = 0;
                // experimentation found this magic number to counteract Adobe's signature graphic, which
                // offsets the y co-ordinate by 15 units
                float y = -im.getScaledHeight() + 15;

                x = x + (signatureRect.getWidth() - im.getScaledWidth()) / 2;
                y = y - (signatureRect.getHeight() - im.getScaledHeight()) / 2;
                p.add(new Chunk(im, x + (signatureRect.getWidth() - im.getScaledWidth()) / 2, y, false));
                ct2.addElement(p);
                ct2.go();
                break;
            case GRAPHIC:
                ct2 = new ColumnText(t);
                ct2.setRunDirection(runDirection);
                ct2.setSimpleColumn(signatureRect.getLeft(), signatureRect.getBottom(), signatureRect.getRight(), signatureRect.getTop(), 0, Element.ALIGN_RIGHT);

                im = Image.getInstance(signatureGraphic);
                im.scaleToFit(signatureRect.getWidth(), signatureRect.getHeight());

                p = new Paragraph(signatureRect.getHeight());
                // must calculate the point to draw from to make image appear in middle of column
                x = (signatureRect.getWidth() - im.getScaledWidth()) / 2;
                y = (signatureRect.getHeight() - im.getScaledHeight()) / 2;
                p.add(new Chunk(im, x, y, false));
                ct2.addElement(p);
                ct2.go();
                break;
            default:
            }

            if(renderingMode != RenderingMode.GRAPHIC) {
            	if (size <= 0) {
                    Rectangle sr = new Rectangle(dataRect.getWidth(), dataRect.getHeight());
                    size = ColumnText.fitText(font, text, sr, 12, runDirection);
                }
                ColumnText ct = new ColumnText(t);
                ct.setRunDirection(runDirection);
                ct.setSimpleColumn(new Phrase(text, font), dataRect.getLeft(), dataRect.getBottom(), dataRect.getRight(), dataRect.getTop(), size, Element.ALIGN_LEFT);
                ct.go();
            }
        }
        if (app[3] == null && !acro6Layers) {
            PdfTemplate t = app[3] = new PdfTemplate(writer);
            t.setBoundingBox(new Rectangle(100, 100));
            writer.addDirectTemplateSimple(t, new PdfName("n3"));
            t.setLiteral("% DSBlank\n");
        }
        if (app[4] == null && !acro6Layers) {
            PdfTemplate t = app[4] = new PdfTemplate(writer);
            t.setBoundingBox(new Rectangle(0, rect.getHeight() * (1 - TOP_SECTION), rect.getRight(), rect.getTop()));
            writer.addDirectTemplateSimple(t, new PdfName("n4"));
            Font font;
            if (layer2Font == null)
                font = new Font();
            else
                font = new Font(layer2Font);
            //float size = font.getSize();
            String text = "Signature Not Verified";
            if (layer4Text != null)
                text = layer4Text;
            Rectangle sr = new Rectangle(rect.getWidth() - 2 * MARGIN, rect.getHeight() * TOP_SECTION - 2 * MARGIN);
            float size = ColumnText.fitText(font, text, sr, 15, runDirection);
            ColumnText ct = new ColumnText(t);
            ct.setRunDirection(runDirection);
            ct.setSimpleColumn(new Phrase(text, font), MARGIN, 0, rect.getWidth() - MARGIN, rect.getHeight() - MARGIN, size, Element.ALIGN_LEFT);
            ct.go();
        }
        int rotation = writer.reader.getPageRotation(page);
        Rectangle rotated = new Rectangle(rect);
        int n = rotation;
        while (n > 0) {
            rotated = rotated.rotate();
            n -= 90;
        }
        if (frm == null) {
            frm = new PdfTemplate(writer);
            frm.setBoundingBox(rotated);
            writer.addDirectTemplateSimple(frm, new PdfName("FRM"));
            float scale = Math.min(rect.getWidth(), rect.getHeight()) * 0.9f;
            float x = (rect.getWidth() - scale) / 2;
            float y = (rect.getHeight() - scale) / 2;
            scale /= 100;
            if (rotation == 90)
                frm.concatCTM(0, 1, -1, 0, rect.getHeight(), 0);
            else if (rotation == 180)
                frm.concatCTM(-1, 0, 0, -1, rect.getWidth(), rect.getHeight());
            else if (rotation == 270)
                frm.concatCTM(0, -1, 1, 0, 0, rect.getWidth());
            if (reuseAppearance) {
                AcroFields af = writer.getAcroFields();
                PdfIndirectReference ref = af.getNormalAppearance(getFieldName());
                if (ref != null) {
                	frm.addTemplateReference(ref, new PdfName("n0"), 1, 0, 0, 1, 0, 0);
                }
                else {
                	reuseAppearance = false;
                    if (app[0] == null) {
                        createBlankN0();
                    }
                }
            }
            if (!reuseAppearance) {
            	frm.addTemplate(app[0], 0, 0);
            }
            if (!acro6Layers)
                frm.addTemplate(app[1], scale, 0, 0, scale, x, y);
            frm.addTemplate(app[2], 0, 0);
            if (!acro6Layers) {
                frm.addTemplate(app[3], scale, 0, 0, scale, x, y);
                frm.addTemplate(app[4], 0, 0);
            }
        }
        PdfTemplate napp = new PdfTemplate(writer);
        napp.setBoundingBox(rotated);
        writer.addDirectTemplateSimple(napp, null);
        napp.addTemplate(frm, 0, 0);
        return napp;
    }

    private void createBlankN0() {
        PdfTemplate t = app[0] = new PdfTemplate(writer);
        t.setBoundingBox(new Rectangle(100, 100));
        writer.addDirectTemplateSimple(t, new PdfName("n0"));
        t.setLiteral("% DSBlank\n");
    }

    /*
     * Creating the signed file.
     */

    /** The PdfStamper that creates the signed PDF. */
    private PdfStamper stamper;

    /**
     * Gets the <CODE>PdfStamper</CODE> associated with this instance.
     * @return the <CODE>PdfStamper</CODE> associated with this instance
     */
    public PdfStamper getStamper() {
        return stamper;
    }

    /**
     * Sets the PdfStamper
     * @param stamper PdfStamper
     */
    void setStamper(PdfStamper stamper) {
        this.stamper = stamper;
    }

    /** The PdfStamperImp object corresponding with the stamper. */
    private PdfStamperImp writer;

    /** A byte buffer containing the bytes of the Stamper. */
    private ByteBuffer sigout;

    /**
     * Getter for the byte buffer.
     */
    ByteBuffer getSigout() {
        return sigout;
    }

    /**
     * Setter for the byte buffer.
     */
    void setSigout(ByteBuffer sigout) {
        this.sigout = sigout;
    }

    /** OutputStream for the bytes of the stamper. */
    private OutputStream originalout;

    /**
     * Getter for the OutputStream.
     */
    OutputStream getOriginalout() {
        return originalout;
    }

    /**
     * Setter for the OutputStream.
     */
    void setOriginalout(OutputStream originalout) {
        this.originalout = originalout;
    }

    /** Temporary file in case you don't want to sign in memory. */
    private File tempFile;

    /**
     * Gets the temporary file.
     * @return the temporary file or <CODE>null</CODE> is the document is created in memory
     */
    public File getTempFile() {
        return tempFile;
    }

    /**
     * Setter for the temporary file.
     * @param tempFile
     */
    void setTempFile(File tempFile) {
        this.tempFile = tempFile;
    }

    /** Name and content of keys that can only be added in the close() method. */
    private HashMap<PdfName, PdfLiteral> exclusionLocations;

    /** Length of the output. */
    private int boutLen;

    /** Indicates if the stamper has already been pre-closed. */
    private boolean preClosed = false;

    /** Signature field lock dictionary */
    private PdfSigLockDictionary fieldLock;

    /**
     * Getter for the field lock dictionary.
     * @return Field lock dictionary.
     */
    public PdfSigLockDictionary getFieldLockDict() {
        return fieldLock;
    }

    /**
     * Setter for the field lock dictionary.
     * <p><strong>Be aware:</strong> if a signature is created on an existing signature field,
     * then its /Lock dictionary takes the precedence (if it exists).</p>
     *
     * @param fieldLock Field lock dictionary.
     */
    public void setFieldLockDict(PdfSigLockDictionary fieldLock) {
        this.fieldLock = fieldLock;
    }

    /**
     * Checks if the document is in the process of closing.
     * @return <CODE>true</CODE> if the document is in the process of closing,
     * <CODE>false</CODE> otherwise
     */
    public boolean isPreClosed() {
        return preClosed;
    }

    /**
     * This is the first method to be called when using external signatures. The general sequence is:
     * preClose(), getDocumentBytes() and close().
     * <p>
     * If calling preClose() <B>dont't</B> call PdfStamper.close().
     * <p>
     * <CODE>exclusionSizes</CODE> must contain at least
     * the <CODE>PdfName.CONTENTS</CODE> key with the size that it will take in the
     * document. Note that due to the hex string coding this size should be
     * byte_size*2+2.
     * @param exclusionSizes a <CODE>HashMap</CODE> with names and sizes to be excluded in the signature
     * calculation. The key is a <CODE>PdfName</CODE> and the value an
     * <CODE>Integer</CODE>. At least the <CODE>PdfName.CONTENTS</CODE> must be present
     * @throws IOException on error
     * @throws DocumentException on error
     */
	public void preClose(HashMap<PdfName, Integer> exclusionSizes) throws IOException, DocumentException {
        if (preClosed)
            throw new DocumentException(MessageLocalization.getComposedMessage("document.already.pre.closed"));
        stamper.mergeVerification();
        preClosed = true;
        AcroFields af = writer.getAcroFields();
        String name = getFieldName();
        boolean fieldExists = af.doesSignatureFieldExist(name);
        PdfIndirectReference refSig = writer.getPdfIndirectReference();
        writer.setSigFlags(3);
        PdfDictionary fieldLock = null;
        if (fieldExists) {
            PdfDictionary widget = af.getFieldItem(name).getWidget(0);
            writer.markUsed(widget);
            fieldLock = widget.getAsDict(PdfName.LOCK);

            if (fieldLock == null && this.fieldLock != null) {
                widget.put(PdfName.LOCK, writer.addToBody(this.fieldLock).getIndirectReference());
                fieldLock = this.fieldLock;
            }

            widget.put(PdfName.P, writer.getPageReference(getPage()));
            widget.put(PdfName.V, refSig);
            PdfObject obj = PdfReader.getPdfObjectRelease(widget.get(PdfName.F));
            int flags = 0;
            if (obj != null && obj.isNumber())
                flags = ((PdfNumber)obj).intValue();
            flags |= PdfAnnotation.FLAGS_LOCKED;
            widget.put(PdfName.F, new PdfNumber(flags));
            PdfDictionary ap = new PdfDictionary();
            ap.put(PdfName.N, getAppearance().getIndirectReference());
            widget.put(PdfName.AP, ap);
        }
        else {
            PdfFormField sigField = PdfFormField.createSignature(writer);
            sigField.setFieldName(name);
            sigField.put(PdfName.V, refSig);
            sigField.setFlags(PdfAnnotation.FLAGS_PRINT | PdfAnnotation.FLAGS_LOCKED);

            if (this.fieldLock != null) {
                sigField.put(PdfName.LOCK, writer.addToBody(this.fieldLock).getIndirectReference());
                fieldLock = this.fieldLock;
            }

            int pagen = getPage();
            if (!isInvisible())
                sigField.setWidget(getPageRect(), null);
            else
                sigField.setWidget(new Rectangle(0, 0), null);
            sigField.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, getAppearance());
            sigField.setPage(pagen);
            writer.addAnnotation(sigField, pagen);
        }

        exclusionLocations = new HashMap<PdfName, PdfLiteral>();
        if (cryptoDictionary == null) {
            throw new DocumentException("No crypto dictionary defined.");
        }
        else {
            PdfLiteral lit = new PdfLiteral(80);
            exclusionLocations.put(PdfName.BYTERANGE, lit);
            cryptoDictionary.put(PdfName.BYTERANGE, lit);
            for (Map.Entry<PdfName, Integer> entry: exclusionSizes.entrySet()) {
                PdfName key = entry.getKey();
                Integer v = entry.getValue();
                lit = new PdfLiteral(v.intValue());
                exclusionLocations.put(key, lit);
                cryptoDictionary.put(key, lit);
            }
            if (certificationLevel > 0)
                addDocMDP(cryptoDictionary);
            if (fieldLock != null)
            	addFieldMDP(cryptoDictionary, fieldLock);
            if (signatureEvent != null)
                signatureEvent.getSignatureDictionary(cryptoDictionary);
            writer.addToBody(cryptoDictionary, refSig, false);
        }
        if (certificationLevel > 0) {
          // add DocMDP entry to root
             PdfDictionary docmdp = new PdfDictionary();
             docmdp.put(new PdfName("DocMDP"), refSig);
             writer.reader.getCatalog().put(new PdfName("Perms"), docmdp);
        }
        writer.close(stamper.getMoreInfo());

        range = new long[exclusionLocations.size() * 2];
        long byteRangePosition = exclusionLocations.get(PdfName.BYTERANGE).getPosition();
        exclusionLocations.remove(PdfName.BYTERANGE);
        int idx = 1;
        for (PdfLiteral lit: exclusionLocations.values()) {
            long n = lit.getPosition();
            range[idx++] = n;
            range[idx++] = lit.getPosLength() + n;
        }
        Arrays.sort(range, 1, range.length - 1);
        for (int k = 3; k < range.length - 2; k += 2)
            range[k] -= range[k - 1];

        if (tempFile == null) {
            bout = sigout.getBuffer();
            boutLen = sigout.size();
            range[range.length - 1] = boutLen - range[range.length - 2];
            ByteBuffer bf = new ByteBuffer();
            bf.append('[');
            for (int k = 0; k < range.length; ++k)
                bf.append(range[k]).append(' ');
            bf.append(']');
            System.arraycopy(bf.getBuffer(), 0, bout, (int)byteRangePosition, bf.size());
        }
        else {
            try {
                raf = new RandomAccessFile(tempFile, "rw");
                long len = raf.length();
                range[range.length - 1] = len - range[range.length - 2];
                ByteBuffer bf = new ByteBuffer();
                bf.append('[');
                for (int k = 0; k < range.length; ++k)
                    bf.append(range[k]).append(' ');
                bf.append(']');
                raf.seek(byteRangePosition);
                raf.write(bf.getBuffer(), 0, bf.size());
            }
            catch (IOException e) {
                try{raf.close();}catch(Exception ee){}
                try{tempFile.delete();}catch(Exception ee){}
                throw e;
            }
        }
    }

    /**
     * Adds keys to the signature dictionary that define
     * the certification level and the permissions.
     * This method is only used for Certifying signatures.
     * @param crypto the signature dictionary
     */
    private void addDocMDP(PdfDictionary crypto) {
        PdfDictionary reference = new PdfDictionary();
        PdfDictionary transformParams = new PdfDictionary();
        transformParams.put(PdfName.P, new PdfNumber(certificationLevel));
        transformParams.put(PdfName.V, new PdfName("1.2"));
        transformParams.put(PdfName.TYPE, PdfName.TRANSFORMPARAMS);
        reference.put(PdfName.TRANSFORMMETHOD, PdfName.DOCMDP);
        reference.put(PdfName.TYPE, PdfName.SIGREF);
        reference.put(PdfName.TRANSFORMPARAMS, transformParams);
        if (writer.getPdfVersion().getVersion() < PdfWriter.VERSION_1_6) {
            reference.put(new PdfName("DigestValue"), new PdfString("aa"));
            PdfArray loc = new PdfArray();
            loc.add(new PdfNumber(0));
            loc.add(new PdfNumber(0));
            reference.put(new PdfName("DigestLocation"), loc);
            reference.put(new PdfName("DigestMethod"), new PdfName("MD5"));
        }
        reference.put(PdfName.DATA, writer.reader.getTrailer().get(PdfName.ROOT));
        PdfArray types = new PdfArray();
        types.add(reference);
        crypto.put(PdfName.REFERENCE, types);
    }

    /**
     * Adds keys to the signature dictionary that define
     * the field permissions.
     * This method is only used for signatures that lock fields.
     * @param crypto the signature dictionary
     */
    private void addFieldMDP(PdfDictionary crypto, PdfDictionary fieldLock) {
        PdfDictionary reference = new PdfDictionary();
        PdfDictionary transformParams = new PdfDictionary();
        transformParams.putAll(fieldLock);
        transformParams.put(PdfName.TYPE, PdfName.TRANSFORMPARAMS);
        transformParams.put(PdfName.V, new PdfName("1.2"));
        reference.put(PdfName.TRANSFORMMETHOD, PdfName.FIELDMDP);
        reference.put(PdfName.TYPE, PdfName.SIGREF);
        reference.put(PdfName.TRANSFORMPARAMS, transformParams);
        reference.put(new PdfName("DigestValue"), new PdfString("aa"));
        PdfArray loc = new PdfArray();
        loc.add(new PdfNumber(0));
        loc.add(new PdfNumber(0));
        reference.put(new PdfName("DigestLocation"), loc);
        reference.put(new PdfName("DigestMethod"), new PdfName("MD5"));
        reference.put(PdfName.DATA, writer.reader.getTrailer().get(PdfName.ROOT));
        PdfArray types = crypto.getAsArray(PdfName.REFERENCE);
        if (types == null)
        	types = new PdfArray();
        types.add(reference);
        crypto.put(PdfName.REFERENCE, types);
    }

    /**
     * This is the last method to be called when using external signatures. The general sequence is:
     * preClose(), getDocumentBytes() and close().
     * <p>
     * <CODE>update</CODE> is a <CODE>PdfDictionary</CODE> that must have exactly the
     * same keys as the ones provided in {@link #preClose(HashMap)}.
     * @param update a <CODE>PdfDictionary</CODE> with the key/value that will fill the holes defined
     * in {@link #preClose(HashMap)}
     * @throws DocumentException on error
     * @throws IOException on error
     */
    public void close(PdfDictionary update) throws IOException, DocumentException {
        try {
            if (!preClosed)
                throw new DocumentException(MessageLocalization.getComposedMessage("preclose.must.be.called.first"));
            ByteBuffer bf = new ByteBuffer();
            for (PdfName key: update.getKeys()) {
                PdfObject obj = update.get(key);
                PdfLiteral lit = exclusionLocations.get(key);
                if (lit == null)
                    throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.key.1.didn.t.reserve.space.in.preclose", key.toString()));
                bf.reset();
                obj.toPdf(null, bf);
                if (bf.size() > lit.getPosLength())
                    throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.key.1.is.too.big.is.2.reserved.3", key.toString(), String.valueOf(bf.size()), String.valueOf(lit.getPosLength())));
                if (tempFile == null)
                    System.arraycopy(bf.getBuffer(), 0, bout, (int)lit.getPosition(), bf.size());
                else {
                    raf.seek(lit.getPosition());
                    raf.write(bf.getBuffer(), 0, bf.size());
                }
            }
            if (update.size() != exclusionLocations.size())
                throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.update.dictionary.has.less.keys.than.required"));
            if (tempFile == null) {
                originalout.write(bout, 0, boutLen);
            }
            else {
                if (originalout != null) {
                    raf.seek(0);
                    long length = raf.length();
                    byte buf[] = new byte[8192];
                    while (length > 0) {
                        int r = raf.read(buf, 0, (int)Math.min((long)buf.length, length));
                        if (r < 0)
                            throw new EOFException(MessageLocalization.getComposedMessage("unexpected.eof"));
                        originalout.write(buf, 0, r);
                        length -= r;
                    }
                }
            }
        }
        finally {
        	writer.reader.close();
            if (tempFile != null) {
                try{raf.close();}catch(Exception ee){}
                if (originalout != null)
                    try{tempFile.delete();}catch(Exception ee){}
            }
            if (originalout != null)
                try{originalout.close();}catch(Exception e){}
        }
    }
}
