iText 5 itextpdf.jar Source Code

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

The Source Code files are provided at iText GitHub site.

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

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

✍: FYIcenter.com

com/itextpdf/text/pdf/AcroFields.java

/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2020 iText Group NV
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 */
package com.itextpdf.text.pdf;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.io.RASInputStream;
import com.itextpdf.text.io.RandomAccessSourceFactory;
import com.itextpdf.text.io.WindowRandomAccessSource;
import com.itextpdf.text.pdf.PRTokeniser.TokenType;
import com.itextpdf.text.pdf.codec.Base64;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import com.itextpdf.text.xml.XmlToTxt;
import org.w3c.dom.Node;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

/**
 * Query and change fields in existing documents either by method
 * calls or by FDF merging.
 *
 * @author Paulo Soares
 */
public class AcroFields {

    public static final int DA_FONT = 0;
    public static final int DA_SIZE = 1;
    public static final int DA_COLOR = 2;
    /**
     * A field type invalid or not found.
     */
    public static final int FIELD_TYPE_NONE = 0;
    /**
     * A field type.
     */
    public static final int FIELD_TYPE_PUSHBUTTON = 1;
    /**
     * A field type.
     */
    public static final int FIELD_TYPE_CHECKBOX = 2;
    /**
     * A field type.
     */
    public static final int FIELD_TYPE_RADIOBUTTON = 3;
    /**
     * A field type.
     */
    public static final int FIELD_TYPE_TEXT = 4;
    /**
     * A field type.
     */
    public static final int FIELD_TYPE_LIST = 5;
    /**
     * A field type.
     */
    public static final int FIELD_TYPE_COMBO = 6;
    /**
     * A field type.
     */
    public static final int FIELD_TYPE_SIGNATURE = 7;
    private static final HashMap<String, String[]> stdFieldFontNames = new HashMap<String, String[]>();
    private static final PdfName[] buttonRemove = {PdfName.MK, PdfName.F, PdfName.FF, PdfName.Q, PdfName.BS, PdfName.BORDER};

    static {
        stdFieldFontNames.put("CoBO", new String[]{"Courier-BoldOblique"});
        stdFieldFontNames.put("CoBo", new String[]{"Courier-Bold"});
        stdFieldFontNames.put("CoOb", new String[]{"Courier-Oblique"});
        stdFieldFontNames.put("Cour", new String[]{"Courier"});
        stdFieldFontNames.put("HeBO", new String[]{"Helvetica-BoldOblique"});
        stdFieldFontNames.put("HeBo", new String[]{"Helvetica-Bold"});
        stdFieldFontNames.put("HeOb", new String[]{"Helvetica-Oblique"});
        stdFieldFontNames.put("Helv", new String[]{"Helvetica"});
        stdFieldFontNames.put("Symb", new String[]{"Symbol"});
        stdFieldFontNames.put("TiBI", new String[]{"Times-BoldItalic"});
        stdFieldFontNames.put("TiBo", new String[]{"Times-Bold"});
        stdFieldFontNames.put("TiIt", new String[]{"Times-Italic"});
        stdFieldFontNames.put("TiRo", new String[]{"Times-Roman"});
        stdFieldFontNames.put("ZaDb", new String[]{"ZapfDingbats"});
        stdFieldFontNames.put("HySm", new String[]{"HYSMyeongJo-Medium", "UniKS-UCS2-H"});
        stdFieldFontNames.put("HyGo", new String[]{"HYGoThic-Medium", "UniKS-UCS2-H"});
        stdFieldFontNames.put("KaGo", new String[]{"HeiseiKakuGo-W5", "UniKS-UCS2-H"});
        stdFieldFontNames.put("KaMi", new String[]{"HeiseiMin-W3", "UniJIS-UCS2-H"});
        stdFieldFontNames.put("MHei", new String[]{"MHei-Medium", "UniCNS-UCS2-H"});
        stdFieldFontNames.put("MSun", new String[]{"MSung-Light", "UniCNS-UCS2-H"});
        stdFieldFontNames.put("STSo", new String[]{"STSong-Light", "UniGB-UCS2-H"});
    }

    PdfReader reader;
    PdfWriter writer;
    Map<String, Item> fields;
    private int topFirst;
    private HashMap<String, int[]> sigNames;
    private boolean append;
    private HashMap<Integer, BaseFont> extensionFonts = new HashMap<Integer, BaseFont>();
    private XfaForm xfa;
    private boolean lastWasString;
    /**
     * Holds value of property generateAppearances.
     */
    private boolean generateAppearances = true;
    private HashMap<String, BaseFont> localFonts = new HashMap<String, BaseFont>();
    private float extraMarginLeft;
    private float extraMarginTop;
    private ArrayList<BaseFont> substitutionFonts;
    private ArrayList<String> orderedSignatureNames;
    /**
     * Holds value of property totalRevisions.
     */
    private int totalRevisions;
    /**
     * Holds value of property fieldCache.
     *
     * @since 2.1.5    this used to be a HashMap
     */
    private Map<String, TextField> fieldCache;

    public static Object[] splitDAelements(String da) {
        try {
            PRTokeniser tk = new PRTokeniser(new RandomAccessFileOrArray(new RandomAccessSourceFactory().createSource(PdfEncodings.convertToBytes(da, null))));
            ArrayList<String> stack = new ArrayList<String>();
            Object ret[] = new Object[3];
            while (tk.nextToken()) {
                if (tk.getTokenType() == TokenType.COMMENT)
                    continue;
                if (tk.getTokenType() == TokenType.OTHER) {
                    String operator = tk.getStringValue();
                    if (operator.equals("Tf")) {
                        if (stack.size() >= 2) {
                            ret[DA_FONT] = stack.get(stack.size() - 2);
                            ret[DA_SIZE] = new Float(stack.get(stack.size() - 1));
                        }
                    } else if (operator.equals("g")) {
                        if (stack.size() >= 1) {
                            float gray = new Float(stack.get(stack.size() - 1)).floatValue();
                            if (gray != 0)
                                ret[DA_COLOR] = new GrayColor(gray);
                        }
                    } else if (operator.equals("rg")) {
                        if (stack.size() >= 3) {
                            float red = new Float(stack.get(stack.size() - 3)).floatValue();
                            float green = new Float(stack.get(stack.size() - 2)).floatValue();
                            float blue = new Float(stack.get(stack.size() - 1)).floatValue();
                            ret[DA_COLOR] = new BaseColor(red, green, blue);
                        }
                    } else if (operator.equals("k")) {
                        if (stack.size() >= 4) {
                            float cyan = new Float(stack.get(stack.size() - 4)).floatValue();
                            float magenta = new Float(stack.get(stack.size() - 3)).floatValue();
                            float yellow = new Float(stack.get(stack.size() - 2)).floatValue();
                            float black = new Float(stack.get(stack.size() - 1)).floatValue();
                            ret[DA_COLOR] = new CMYKColor(cyan, magenta, yellow, black);
                        }
                    }
                    stack.clear();
                } else
                    stack.add(tk.getStringValue());
            }
            return ret;
        } catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
    }

    private static void clearSigDic(PdfDictionary dic) {
        dic.remove(PdfName.AP);
        dic.remove(PdfName.AS);
        dic.remove(PdfName.V);
        dic.remove(PdfName.DV);
        dic.remove(PdfName.SV);
        dic.remove(PdfName.FF);
        dic.put(PdfName.F, new PdfNumber(PdfAnnotation.FLAGS_PRINT));
    }

    AcroFields(PdfReader reader, PdfWriter writer) {
        this.reader = reader;
        this.writer = writer;
        try {
            xfa = new XfaForm(reader);
        } catch (Exception e) {
            throw new ExceptionConverter(e);
        }
        if (writer instanceof PdfStamperImp) {
            append = ((PdfStamperImp) writer).isAppend();
        }
        fill();
    }

    void fill() {
        fields = new LinkedHashMap<String, Item>();
        PdfDictionary top = (PdfDictionary) PdfReader.getPdfObjectRelease(reader.getCatalog().get(PdfName.ACROFORM));
        if (top == null)
            return;
        PdfBoolean needappearances = top.getAsBoolean(PdfName.NEEDAPPEARANCES);
        if (needappearances == null || !needappearances.booleanValue())
            setGenerateAppearances(true);
        else
            setGenerateAppearances(false);
        PdfArray arrfds = (PdfArray) PdfReader.getPdfObjectRelease(top.get(PdfName.FIELDS));
        if (arrfds == null || arrfds.size() == 0)
            return;
        for (int k = 1; k <= reader.getNumberOfPages(); ++k) {
            PdfDictionary page = reader.getPageNRelease(k);
            PdfArray annots = (PdfArray) PdfReader.getPdfObjectRelease(page.get(PdfName.ANNOTS), page);
            if (annots == null)
                continue;
            for (int j = 0; j < annots.size(); ++j) {
                PdfDictionary annot = annots.getAsDict(j);
                if (annot == null) {
                    PdfReader.releaseLastXrefPartial(annots.getAsIndirectObject(j));
                    continue;
                }
                if (!PdfName.WIDGET.equals(annot.getAsName(PdfName.SUBTYPE))) {
                    PdfReader.releaseLastXrefPartial(annots.getAsIndirectObject(j));
                    continue;
                }
                PdfDictionary widget = annot;
                PdfDictionary dic = new PdfDictionary();
                dic.putAll(annot);
                String name = "";
                PdfDictionary value = null;
                PdfObject lastV = null;
                while (annot != null) {
                    dic.mergeDifferent(annot);
                    PdfString t = annot.getAsString(PdfName.T);
                    if (t != null)
                        name = t.toUnicodeString() + "." + name;
                    if (lastV == null && annot.get(PdfName.V) != null)
                        lastV = PdfReader.getPdfObjectRelease(annot.get(PdfName.V));
                    if (value == null && t != null) {
                        value = annot;
                        if (annot.get(PdfName.V) == null && lastV != null)
                            value.put(PdfName.V, lastV); // TODO: seems to be bug (we are going up the hierarchy and setting parent's V entry to child's V value)
                    }
                    annot = annot.getAsDict(PdfName.PARENT);
                }
                if (name.length() > 0)
                    name = name.substring(0, name.length() - 1);
                Item item = fields.get(name);
                if (item == null) {
                    item = new Item();
                    fields.put(name, item);
                }
                if (value == null)
                    item.addValue(widget);
                else
                    item.addValue(value);
                item.addWidget(widget);
                item.addWidgetRef(annots.getAsIndirectObject(j)); // must be a reference
                if (top != null)
                    dic.mergeDifferent(top);
                item.addMerged(dic);
                item.addPage(k);
                item.addTabOrder(j);
            }
        }
        // some tools produce invisible signatures without an entry in the page annotation array
        // look for a single level annotation
        PdfNumber sigFlags = top.getAsNumber(PdfName.SIGFLAGS);
        if (sigFlags == null || (sigFlags.intValue() & 1) != 1)
            return;
        for (int j = 0; j < arrfds.size(); ++j) {
            PdfDictionary annot = arrfds.getAsDict(j);
            if (annot == null) {
                PdfReader.releaseLastXrefPartial(arrfds.getAsIndirectObject(j));
                continue;
            }
            if (!PdfName.WIDGET.equals(annot.getAsName(PdfName.SUBTYPE))) {
                PdfReader.releaseLastXrefPartial(arrfds.getAsIndirectObject(j));
                continue;
            }
            PdfArray kids = (PdfArray) PdfReader.getPdfObjectRelease(annot.get(PdfName.KIDS));
            if (kids != null)
                continue;
            PdfDictionary dic = new PdfDictionary();
            dic.putAll(annot);
            PdfString t = annot.getAsString(PdfName.T);
            if (t == null)
                continue;
            String name = t.toUnicodeString();
            if (fields.containsKey(name))
                continue;
            Item item = new Item();
            fields.put(name, item);
            item.addValue(dic);
            item.addWidget(dic);
            item.addWidgetRef(arrfds.getAsIndirectObject(j)); // must be a reference
            item.addMerged(dic);
            item.addPage(-1);
            item.addTabOrder(-1);
        }
    }

    /**
     * Gets the list of appearance names. Use it to get the names allowed
     * with radio and checkbox fields. If the /Opt key exists the values will
     * also be included. The name 'Off' may also be valid
     * even if not returned in the list.
     * <p>
     * For Comboboxes it will return an array of display values. To extract the
     * export values of a Combobox, please refer to {@link AcroFields#getListOptionExport(String)}
     *
     * @param fieldName the fully qualified field name
     * @return the list of names or <CODE>null</CODE> if the field does not exist
     */
    public String[] getAppearanceStates(String fieldName) {
        Item fd = fields.get(fieldName);
        if (fd == null)
            return null;
        HashSet<String> names = new LinkedHashSet<String>();
        PdfDictionary vals = fd.getValue(0);
        PdfString stringOpt = vals.getAsString(PdfName.OPT);

        // should not happen according to specs
        if (stringOpt != null) {
            names.add(stringOpt.toUnicodeString());
        } else {
            PdfArray arrayOpt = vals.getAsArray(PdfName.OPT);
            if (arrayOpt != null) {
                for (int k = 0; k < arrayOpt.size(); ++k) {
                    PdfObject pdfObject = arrayOpt.getDirectObject(k);
                    PdfString valStr = null;

                    switch (pdfObject.type()) {
                        case PdfObject.ARRAY:
                            PdfArray pdfArray = (PdfArray) pdfObject;
                            valStr = pdfArray.getAsString(1);
                            break;
                        case PdfObject.STRING:
                            valStr = (PdfString) pdfObject;
                            break;
                    }

                    if (valStr != null)
                        names.add(valStr.toUnicodeString());
                }
            }
        }
        for (int k = 0; k < fd.size(); ++k) {
            PdfDictionary dic = fd.getWidget(k);
            dic = dic.getAsDict(PdfName.AP);
            if (dic == null)
                continue;
            dic = dic.getAsDict(PdfName.N);
            if (dic == null)
                continue;
            for (Object element : dic.getKeys()) {
                String name = PdfName.decodeName(((PdfName) element).toString());
                names.add(name);
            }
        }
        String out[] = new String[names.size()];
        return names.toArray(out);
    }

    private String[] getListOption(String fieldName, int idx) {
        Item fd = getFieldItem(fieldName);
        if (fd == null)
            return null;
        PdfArray ar = fd.getMerged(0).getAsArray(PdfName.OPT);
        if (ar == null)
            return null;
        String[] ret = new String[ar.size()];
        for (int k = 0; k < ar.size(); ++k) {
            PdfObject obj = ar.getDirectObject(k);
            try {
                if (obj.isArray()) {
                    obj = ((PdfArray) obj).getDirectObject(idx);
                }
                if (obj.isString())
                    ret[k] = ((PdfString) obj).toUnicodeString();
                else
                    ret[k] = obj.toString();
            } catch (Exception e) {
                ret[k] = "";
            }
        }
        return ret;
    }

    /**
     * Gets the list of export option values from fields of type list or combo.
     * If the field doesn't exist or the field type is not list or combo it will return
     * <CODE>null</CODE>.
     *
     * @param fieldName the field name
     * @return the list of export option values from fields of type list or combo
     */
    public String[] getListOptionExport(String fieldName) {
        return getListOption(fieldName, 0);
    }

    /**
     * Gets the list of display option values from fields of type list or combo.
     * If the field doesn't exist or the field type is not list or combo it will return
     * <CODE>null</CODE>.
     *
     * @param fieldName the field name
     * @return the list of export option values from fields of type list or combo
     */
    public String[] getListOptionDisplay(String fieldName) {
        return getListOption(fieldName, 1);
    }

    /**
     * Sets the option list for fields of type list or combo. One of <CODE>exportValues</CODE>
     * or <CODE>displayValues</CODE> may be <CODE>null</CODE> but not both. This method will only
     * set the list but will not set the value or appearance. For that, calling <CODE>setField()</CODE>
     * is required.
     * <p>
     * An example:
     * <p>
     * <PRE>
     * PdfReader pdf = new PdfReader("input.pdf");
     * PdfStamper stp = new PdfStamper(pdf, new FileOutputStream("output.pdf"));
     * AcroFields af = stp.getAcroFields();
     * af.setListOption("ComboBox", new String[]{"a", "b", "c"}, new String[]{"first", "second", "third"});
     * af.setField("ComboBox", "b");
     * stp.close();
     * </PRE>
     *
     * @param fieldName     the field name
     * @param exportValues  the export values
     * @param displayValues the display values
     * @return <CODE>true</CODE> if the operation succeeded, <CODE>false</CODE> otherwise
     */
    public boolean setListOption(String fieldName, String[] exportValues, String[] displayValues) {
        if (exportValues == null && displayValues == null)
            return false;
        if (exportValues != null && displayValues != null && exportValues.length != displayValues.length)
            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.export.and.the.display.array.must.have.the.same.size"));
        int ftype = getFieldType(fieldName);
        if (ftype != FIELD_TYPE_COMBO && ftype != FIELD_TYPE_LIST)
            return false;
        Item fd = fields.get(fieldName);
        String[] sing = null;
        if (exportValues == null && displayValues != null)
            sing = displayValues;
        else if (exportValues != null && displayValues == null)
            sing = exportValues;
        PdfArray opt = new PdfArray();
        if (sing != null) {
            for (int k = 0; k < sing.length; ++k)
                opt.add(new PdfString(sing[k], PdfObject.TEXT_UNICODE));
        } else {
            for (int k = 0; k < exportValues.length; ++k) {
                PdfArray a = new PdfArray();
                a.add(new PdfString(exportValues[k], PdfObject.TEXT_UNICODE));
                a.add(new PdfString(displayValues[k], PdfObject.TEXT_UNICODE));
                opt.add(a);
            }
        }
        fd.writeToAll(PdfName.OPT, opt, Item.WRITE_VALUE | Item.WRITE_MERGED);
        return true;
    }

    /**
     * Gets the field type. The type can be one of: <CODE>FIELD_TYPE_PUSHBUTTON</CODE>,
     * <CODE>FIELD_TYPE_CHECKBOX</CODE>, <CODE>FIELD_TYPE_RADIOBUTTON</CODE>,
     * <CODE>FIELD_TYPE_TEXT</CODE>, <CODE>FIELD_TYPE_LIST</CODE>,
     * <CODE>FIELD_TYPE_COMBO</CODE> or <CODE>FIELD_TYPE_SIGNATURE</CODE>.
     * <p>
     * If the field does not exist or is invalid it returns
     * <CODE>FIELD_TYPE_NONE</CODE>.
     *
     * @param fieldName the field name
     * @return the field type
     */
    public int getFieldType(String fieldName) {
        Item fd = getFieldItem(fieldName);
        if (fd == null)
            return FIELD_TYPE_NONE;
        PdfDictionary merged = fd.getMerged(0);
        PdfName type = merged.getAsName(PdfName.FT);
        if (type == null)
            return FIELD_TYPE_NONE;
        int ff = 0;
        PdfNumber ffo = merged.getAsNumber(PdfName.FF);
        if (ffo != null) {
            ff = ffo.intValue();
        }
        if (PdfName.BTN.equals(type)) {
            if ((ff & PdfFormField.FF_PUSHBUTTON) != 0)
                return FIELD_TYPE_PUSHBUTTON;
            if ((ff & PdfFormField.FF_RADIO) != 0)
                return FIELD_TYPE_RADIOBUTTON;
            else
                return FIELD_TYPE_CHECKBOX;
        } else if (PdfName.TX.equals(type)) {
            return FIELD_TYPE_TEXT;
        } else if (PdfName.CH.equals(type)) {
            if ((ff & PdfFormField.FF_COMBO) != 0)
                return FIELD_TYPE_COMBO;
            else
                return FIELD_TYPE_LIST;
        } else if (PdfName.SIG.equals(type)) {
            return FIELD_TYPE_SIGNATURE;
        }
        return FIELD_TYPE_NONE;
    }

    /**
     * Export the fields as a FDF.
     *
     * @param writer the FDF writer
     */
    public void exportAsFdf(FdfWriter writer) {
        for (Map.Entry<String, Item> entry : fields.entrySet()) {
            Item item = entry.getValue();
            String name = entry.getKey();
            PdfObject v = item.getMerged(0).get(PdfName.V);
            if (v == null)
                continue;
            String value = getField(name);
            if (lastWasString)
                writer.setFieldAsString(name, value);
            else
                writer.setFieldAsName(name, value);
        }
    }

    /**
     * Renames a field. Only the last part of the name can be renamed. For example,
     * if the original field is "ab.cd.ef" only the "ef" part can be renamed.
     *
     * @param oldName the old field name
     * @param newName the new field name
     * @return <CODE>true</CODE> if the renaming was successful, <CODE>false</CODE>
     * otherwise
     */
    public boolean renameField(String oldName, String newName) {
        int idx1 = oldName.lastIndexOf('.') + 1;
        int idx2 = newName.lastIndexOf('.') + 1;
        if (idx1 != idx2)
            return false;
        if (!oldName.substring(0, idx1).equals(newName.substring(0, idx2)))
            return false;
        if (fields.containsKey(newName))
            return false;
        Item item = fields.get(oldName);
        if (item == null)
            return false;
        newName = newName.substring(idx2);
        PdfString ss = new PdfString(newName, PdfObject.TEXT_UNICODE);

        item.writeToAll(PdfName.T, ss, Item.WRITE_VALUE | Item.WRITE_MERGED);
        item.markUsed(this, Item.WRITE_VALUE);

        fields.remove(oldName);
        fields.put(newName, item);

        return true;
    }

    public void decodeGenericDictionary(PdfDictionary merged, BaseField tx) throws IOException, DocumentException {
        int flags = 0;
        // the text size and color
        PdfString da = merged.getAsString(PdfName.DA);
        if (da != null) {
            boolean fontfallback = false;
            Object dab[] = splitDAelements(da.toUnicodeString());
            if (dab[DA_SIZE] != null)
                tx.setFontSize(((Float) dab[DA_SIZE]).floatValue());
            if (dab[DA_COLOR] != null)
                tx.setTextColor((BaseColor) dab[DA_COLOR]);
            if (dab[DA_FONT] != null) {
                PdfDictionary dr = merged.getAsDict(PdfName.DR);
                if (dr != null) {
                    PdfDictionary font = dr.getAsDict(PdfName.FONT);
                    if (font != null) {
                        PdfObject po = font.get(new PdfName((String) dab[DA_FONT]));
                        if (po != null && po.type() == PdfObject.INDIRECT) {
                            PRIndirectReference por = (PRIndirectReference) po;
                            BaseFont bp = new DocumentFont((PRIndirectReference) po, dr.getAsDict(PdfName.ENCODING));
                            tx.setFont(bp);
                            Integer porkey = Integer.valueOf(por.getNumber());
                            BaseFont porf = extensionFonts.get(porkey);
                            if (porf == null) {
                                if (!extensionFonts.containsKey(porkey)) {
                                    PdfDictionary fo = (PdfDictionary) PdfReader.getPdfObject(po);
                                    PdfDictionary fd = fo.getAsDict(PdfName.FONTDESCRIPTOR);
                                    if (fd != null) {
                                        PRStream prs = (PRStream) PdfReader.getPdfObject(fd.get(PdfName.FONTFILE2));
                                        if (prs == null)
                                            prs = (PRStream) PdfReader.getPdfObject(fd.get(PdfName.FONTFILE3));
                                        if (prs == null) {
                                            extensionFonts.put(porkey, null);
                                        } else {
                                            try {
                                                porf = BaseFont.createFont("font.ttf", BaseFont.IDENTITY_H, true, false, PdfReader.getStreamBytes(prs), null);
                                            } catch (Exception e) {
                                            }
                                            extensionFonts.put(porkey, porf);
                                        }
                                    }
                                }
                            }
                            if (tx instanceof TextField)
                                ((TextField) tx).setExtensionFont(porf);
                        } else {
                            fontfallback = true;
                        }

                    } else {
                        fontfallback = true;
                    }
                } else {
                    fontfallback = true;
                }
            }
            if (fontfallback) {
                BaseFont bf = localFonts.get(dab[DA_FONT]);
                if (bf == null) {
                    String fn[] = stdFieldFontNames.get(dab[DA_FONT]);
                    if (fn != null) {
                        try {
                            String enc = "winansi";
                            if (fn.length > 1)
                                enc = fn[1];
                            bf = BaseFont.createFont(fn[0], enc, false);
                            tx.setFont(bf);
                        } catch (Exception e) {
                            // empty
                        }
                    }
                } else
                    tx.setFont(bf);
            }
        }
        //rotation, border and background color
        PdfDictionary mk = merged.getAsDict(PdfName.MK);
        if (mk != null) {
            PdfArray ar = mk.getAsArray(PdfName.BC);
            BaseColor border = getMKColor(ar);
            tx.setBorderColor(border);
            if (border != null)
                tx.setBorderWidth(1);
            ar = mk.getAsArray(PdfName.BG);
            tx.setBackgroundColor(getMKColor(ar));
            PdfNumber rotation = mk.getAsNumber(PdfName.R);
            if (rotation != null)
                tx.setRotation(rotation.intValue());
        }
        //flags
        PdfNumber nfl = merged.getAsNumber(PdfName.F);
        flags = 0;
        tx.setVisibility(BaseField.VISIBLE_BUT_DOES_NOT_PRINT);
        if (nfl != null) {
            flags = nfl.intValue();
            if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) != 0)
                tx.setVisibility(BaseField.HIDDEN);
            else if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_NOVIEW) != 0)
                tx.setVisibility(BaseField.HIDDEN_BUT_PRINTABLE);
            else if ((flags & PdfFormField.FLAGS_PRINT) != 0)
                tx.setVisibility(BaseField.VISIBLE);
        }
        //multiline
        nfl = merged.getAsNumber(PdfName.FF);
        flags = 0;
        if (nfl != null)
            flags = nfl.intValue();
        tx.setOptions(flags);
        if ((flags & PdfFormField.FF_COMB) != 0) {
            PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN);
            int len = 0;
            if (maxLen != null)
                len = maxLen.intValue();
            tx.setMaxCharacterLength(len);
        }
        //alignment
        nfl = merged.getAsNumber(PdfName.Q);
        if (nfl != null) {
            if (nfl.intValue() == PdfFormField.Q_CENTER)
                tx.setAlignment(Element.ALIGN_CENTER);
            else if (nfl.intValue() == PdfFormField.Q_RIGHT)
                tx.setAlignment(Element.ALIGN_RIGHT);
        }
        //border styles
        PdfDictionary bs = merged.getAsDict(PdfName.BS);
        if (bs != null) {
            PdfNumber w = bs.getAsNumber(PdfName.W);
            if (w != null)
                tx.setBorderWidth(w.floatValue());
            PdfName s = bs.getAsName(PdfName.S);
            if (PdfName.D.equals(s))
                tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
            else if (PdfName.B.equals(s))
                tx.setBorderStyle(PdfBorderDictionary.STYLE_BEVELED);
            else if (PdfName.I.equals(s))
                tx.setBorderStyle(PdfBorderDictionary.STYLE_INSET);
            else if (PdfName.U.equals(s))
                tx.setBorderStyle(PdfBorderDictionary.STYLE_UNDERLINE);
        } else {
            PdfArray bd = merged.getAsArray(PdfName.BORDER);
            if (bd != null) {
                if (bd.size() >= 3)
                    tx.setBorderWidth(bd.getAsNumber(2).floatValue());
                if (bd.size() >= 4)
                    tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
            }
        }
    }

    PdfAppearance getAppearance(PdfDictionary merged, String values[], String fieldName) throws IOException, DocumentException {
        PdfName fieldType = merged.getAsName(PdfName.FT);

        if (PdfName.BTN.equals(fieldType)) {
            PdfNumber fieldFlags = merged.getAsNumber(PdfName.FF);
            boolean isRadio = fieldFlags != null && (fieldFlags.intValue() & PdfFormField.FF_RADIO) != 0;
            RadioCheckField field = new RadioCheckField(writer, null, null, null);
            decodeGenericDictionary(merged, field);
            //rect
            PdfArray rect = merged.getAsArray(PdfName.RECT);
            Rectangle box = PdfReader.getNormalizedRectangle(rect);
            if (field.getRotation() == 90 || field.getRotation() == 270)
                box = box.rotate();
            field.setBox(box);
            if (!isRadio)
                field.setCheckType(RadioCheckField.TYPE_CROSS);
            return field.getAppearance(isRadio, !(merged.getAsName(PdfName.AS).equals(PdfName.Off)));
        }

        topFirst = 0;
        String text = values.length > 0 ? values[0] : null;

        TextField tx = null;
        if (fieldCache == null || !fieldCache.containsKey(fieldName)) {
            tx = new TextField(writer, null, null);
            tx.setExtraMargin(extraMarginLeft, extraMarginTop);
            tx.setBorderWidth(0);
            tx.setSubstitutionFonts(substitutionFonts);
            decodeGenericDictionary(merged, tx);
            //rect
            PdfArray rect = merged.getAsArray(PdfName.RECT);
            Rectangle box = PdfReader.getNormalizedRectangle(rect);
            if (tx.getRotation() == 90 || tx.getRotation() == 270)
                box = box.rotate();
            tx.setBox(box);
            if (fieldCache != null)
                fieldCache.put(fieldName, tx);
        } else {
            tx = fieldCache.get(fieldName);
            tx.setWriter(writer);
        }
        if (PdfName.TX.equals(fieldType)) {
            if (values.length > 0 && values[0] != null) {
                tx.setText(values[0]);
            }
            return tx.getAppearance();
        }
        if (!PdfName.CH.equals(fieldType))
            throw new DocumentException(MessageLocalization.getComposedMessage("an.appearance.was.requested.without.a.variable.text.field"));
        PdfArray opt = merged.getAsArray(PdfName.OPT);
        int flags = 0;
        PdfNumber nfl = merged.getAsNumber(PdfName.FF);
        if (nfl != null)
            flags = nfl.intValue();
        if ((flags & PdfFormField.FF_COMBO) != 0 && opt == null) {
            tx.setText(text);
            return tx.getAppearance();
        }
        if (opt != null) {
            String choices[] = new String[opt.size()];
            String choicesExp[] = new String[opt.size()];
            for (int k = 0; k < opt.size(); ++k) {
                PdfObject obj = opt.getPdfObject(k);
                if (obj.isString()) {
                    choices[k] = choicesExp[k] = ((PdfString) obj).toUnicodeString();
                } else {
                    PdfArray a = (PdfArray) obj;
                    choicesExp[k] = a.getAsString(0).toUnicodeString();
                    choices[k] = a.getAsString(1).toUnicodeString();
                }
            }
            if ((flags & PdfFormField.FF_COMBO) != 0) {
                for (int k = 0; k < choices.length; ++k) {
                    if (text.equals(choicesExp[k])) {
                        text = choices[k];
                        break;
                    }
                }
                tx.setText(text);
                return tx.getAppearance();
            }
            ArrayList<Integer> indexes = new ArrayList<Integer>();
            for (int k = 0; k < choicesExp.length; ++k) {
                for (int j = 0; j < values.length; ++j) {
                    String val = values[j];
                    if (val != null && val.equals(choicesExp[k])) {
                        indexes.add(Integer.valueOf(k));
                        break;
                    }
                }
            }
            tx.setChoices(choices);
            tx.setChoiceExports(choicesExp);
            tx.setChoiceSelections(indexes);
        }
        PdfAppearance app = tx.getListAppearance();
        topFirst = tx.getTopFirst();
        return app;
    }

    PdfAppearance getAppearance(PdfDictionary merged, String text, String fieldName) throws IOException, DocumentException {
        String valueArr[] = new String[1];
        valueArr[0] = text;
        return getAppearance(merged, valueArr, fieldName);
    }

    BaseColor getMKColor(PdfArray ar) {
        if (ar == null)
            return null;
        switch (ar.size()) {
            case 1:
                return new GrayColor(ar.getAsNumber(0).floatValue());
            case 3:
                return new BaseColor(ExtendedColor.normalize(ar.getAsNumber(0).floatValue()), ExtendedColor.normalize(ar.getAsNumber(1).floatValue()), ExtendedColor.normalize(ar.getAsNumber(2).floatValue()));
            case 4:
                return new CMYKColor(ar.getAsNumber(0).floatValue(), ar.getAsNumber(1).floatValue(), ar.getAsNumber(2).floatValue(), ar.getAsNumber(3).floatValue());
            default:
                return null;
        }
    }

    /**
     * Retrieve the rich value for the given field
     *
     * @param name
     * @return The rich value if present, or null.
     * @since 5.0.6
     */
    public String getFieldRichValue(String name) {
        if (xfa.isXfaPresent()) {
            return null;
        }

        Item item = fields.get(name);
        if (item == null) {
            return null;
        }

        PdfDictionary merged = item.getMerged(0);
        PdfString rich = merged.getAsString(PdfName.RV);

        String markup = null;
        if (rich != null) {
            markup = rich.toString();
        }

        return markup;
    }

    /**
     * Gets the field value.
     *
     * @param name the fully qualified field name
     * @return the field value
     */
    public String getField(String name) {
        if (xfa.isXfaPresent()) {
            name = xfa.findFieldName(name, this);
            if (name == null)
                return null;
            name = XfaForm.Xml2Som.getShortName(name);
            return XfaForm.getNodeText(xfa.findDatasetsNode(name));
        }
        Item item = fields.get(name);
        if (item == null)
            return null;
        lastWasString = false;
        PdfDictionary mergedDict = item.getMerged(0);

        // Jose A. Rodriguez posted a fix to the mailing list (May 11, 2009)
        // explaining that the value can also be a stream value
        // the fix was made against an old iText version. Bruno adapted it.
        PdfObject v = PdfReader.getPdfObject(mergedDict.get(PdfName.V));
        if (v == null)
            return "";
        if (v instanceof PRStream) {
            byte[] valBytes;
            try {
                valBytes = PdfReader.getStreamBytes((PRStream) v);
                return new String(valBytes);
            } catch (IOException e) {
                throw new ExceptionConverter(e);
            }
        }

        PdfName type = mergedDict.getAsName(PdfName.FT);
        if (PdfName.BTN.equals(type)) {
            PdfNumber ff = mergedDict.getAsNumber(PdfName.FF);
            int flags = 0;
            if (ff != null)
                flags = ff.intValue();
            if ((flags & PdfFormField.FF_PUSHBUTTON) != 0)
                return "";
            String value = "";
            if (v instanceof PdfName)
                value = PdfName.decodeName(v.toString());
            else if (v instanceof PdfString)
                value = ((PdfString) v).toUnicodeString();
            PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT);
            if (opts != null) {
                int idx = 0;
                try {
                    idx = Integer.parseInt(value);
                    PdfString ps = opts.getAsString(idx);
                    value = ps.toUnicodeString();
                    lastWasString = true;
                } catch (Exception e) {
                }
            }
            return value;
        }
        if (v instanceof PdfString) {
            lastWasString = true;
            return ((PdfString) v).toUnicodeString();
        } else if (v instanceof PdfName) {
            return PdfName.decodeName(v.toString());
        } else
            return "";
    }

    /**
     * Gets the field values of a Choice field.
     *
     * @param name the fully qualified field name
     * @return the field value
     * @since 2.1.3
     */
    public String[] getListSelection(String name) {
        String[] ret;
        String s = getField(name);
        if (s == null) {
            ret = new String[]{};
        } else {
            ret = new String[]{s};
        }
        Item item = fields.get(name);
        if (item == null)
            return ret;
        //PdfName type = (PdfName)PdfReader.getPdfObject(((PdfDictionary)item.merged.get(0)).get(PdfName.FT));
        //if (!PdfName.CH.equals(type)) {
        //	return ret;
        //}
        PdfArray values = item.getMerged(0).getAsArray(PdfName.I);
        if (values == null)
            return ret;
        ret = new String[values.size()];
        String[] options = getListOptionExport(name);
        PdfNumber n;
        int idx = 0;
        for (Iterator<PdfObject> i = values.listIterator(); i.hasNext(); ) {
            n = (PdfNumber) i.next();
            ret[idx++] = options[n.intValue()];
        }
        return ret;
    }

    /**
     * Sets a field property. Valid property names are:
     * <p>
     * <ul>
     * <li>textfont - sets the text font. The value for this entry is a <CODE>BaseFont</CODE>.<br>
     * <li>textcolor - sets the text color. The value for this entry is a <CODE>BaseColor</CODE>.<br>
     * <li>textsize - sets the text size. The value for this entry is a <CODE>Float</CODE>.
     * <li>bgcolor - sets the background color. The value for this entry is a <CODE>BaseColor</CODE>.
     * If <code>null</code> removes the background.<br>
     * <li>bordercolor - sets the border color. The value for this entry is a <CODE>BaseColor</CODE>.
     * If <code>null</code> removes the border.<br>
     * </ul>
     *
     * @param field the field name
     * @param name  the property name
     * @param value the property value
     * @param inst  an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process.
     *              Set to <CODE>null</CODE> to process all
     * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise
     */
    public boolean setFieldProperty(String field, String name, Object value, int inst[]) {
        if (writer == null)
            throw new RuntimeException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
        try {
            Item item = fields.get(field);
            if (item == null)
                return false;
            InstHit hit = new InstHit(inst);
            PdfDictionary merged;
            PdfString da;
            if (name.equalsIgnoreCase("textfont")) {
                for (int k = 0; k < item.size(); ++k) {
                    if (hit.isHit(k)) {
                        merged = item.getMerged(k);
                        da = merged.getAsString(PdfName.DA);
                        PdfDictionary dr = merged.getAsDict(PdfName.DR);
                        if (da != null) {
                            if (dr == null) {
                                dr = new PdfDictionary();
                                merged.put(PdfName.DR, dr);
                            }
                            Object dao[] = splitDAelements(da.toUnicodeString());
                            PdfAppearance cb = new PdfAppearance();
                            if (dao[DA_FONT] != null) {
                                BaseFont bf = (BaseFont) value;
                                PdfName psn = PdfAppearance.stdFieldFontNames.get(bf.getPostscriptFontName());
                                if (psn == null) {
                                    psn = new PdfName(bf.getPostscriptFontName());
                                }
                                PdfDictionary fonts = dr.getAsDict(PdfName.FONT);
                                if (fonts == null) {
                                    fonts = new PdfDictionary();
                                    dr.put(PdfName.FONT, fonts);
                                }
                                PdfIndirectReference fref = (PdfIndirectReference) fonts.get(psn);
                                PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM);
                                markUsed(top);
                                dr = top.getAsDict(PdfName.DR);
                                if (dr == null) {
                                    dr = new PdfDictionary();
                                    top.put(PdfName.DR, dr);
                                }
                                markUsed(dr);
                                PdfDictionary fontsTop = dr.getAsDict(PdfName.FONT);
                                if (fontsTop == null) {
                                    fontsTop = new PdfDictionary();
                                    dr.put(PdfName.FONT, fontsTop);
                                }
                                markUsed(fontsTop);
                                PdfIndirectReference frefTop = (PdfIndirectReference) fontsTop.get(psn);
                                if (frefTop != null) {
                                    if (fref == null)
                                        fonts.put(psn, frefTop);
                                } else if (fref == null) {
                                    FontDetails fd;
                                    if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) {
                                        fd = new FontDetails(null, ((DocumentFont) bf).getIndirectReference(), bf);
                                    } else {
                                        bf.setSubset(false);
                                        fd = writer.addSimple(bf);
                                        localFonts.put(psn.toString().substring(1), bf);
                                    }
                                    fontsTop.put(psn, fd.getIndirectReference());
                                    fonts.put(psn, fd.getIndirectReference());
                                }
                                ByteBuffer buf = cb.getInternalBuffer();
                                buf.append(psn.getBytes()).append(' ').append(((Float) dao[DA_SIZE]).floatValue()).append(" Tf ");
                                if (dao[DA_COLOR] != null)
                                    cb.setColorFill((BaseColor) dao[DA_COLOR]);
                                PdfString s = new PdfString(cb.toString());
                                item.getMerged(k).put(PdfName.DA, s);
                                item.getWidget(k).put(PdfName.DA, s);
                                markUsed(item.getWidget(k));
                            }
                        }
                    }
                }
            } else if (name.equalsIgnoreCase("textcolor")) {
                for (int k = 0; k < item.size(); ++k) {
                    if (hit.isHit(k)) {
                        merged = item.getMerged(k);
                        da = merged.getAsString(PdfName.DA);
                        if (da != null) {
                            Object dao[] = splitDAelements(da.toUnicodeString());
                            PdfAppearance cb = new PdfAppearance();
                            if (dao[DA_FONT] != null) {
                                ByteBuffer buf = cb.getInternalBuffer();
                                buf.append(new PdfName((String) dao[DA_FONT]).getBytes()).append(' ').append(((Float) dao[DA_SIZE]).floatValue()).append(" Tf ");
                                cb.setColorFill((BaseColor) value);
                                PdfString s = new PdfString(cb.toString());
                                item.getMerged(k).put(PdfName.DA, s);
                                item.getWidget(k).put(PdfName.DA, s);
                                markUsed(item.getWidget(k));
                            }
                        }
                    }
                }
            } else if (name.equalsIgnoreCase("textsize")) {
                for (int k = 0; k < item.size(); ++k) {
                    if (hit.isHit(k)) {
                        merged = item.getMerged(k);
                        da = merged.getAsString(PdfName.DA);
                        if (da != null) {
                            Object dao[] = splitDAelements(da.toUnicodeString());
                            PdfAppearance cb = new PdfAppearance();
                            if (dao[DA_FONT] != null) {
                                ByteBuffer buf = cb.getInternalBuffer();
                                buf.append(new PdfName((String) dao[DA_FONT]).getBytes()).append(' ').append(((Float) value).floatValue()).append(" Tf ");
                                if (dao[DA_COLOR] != null)
                                    cb.setColorFill((BaseColor) dao[DA_COLOR]);
                                PdfString s = new PdfString(cb.toString());
                                item.getMerged(k).put(PdfName.DA, s);
                                item.getWidget(k).put(PdfName.DA, s);
                                markUsed(item.getWidget(k));
                            }
                        }
                    }
                }
            } else if (name.equalsIgnoreCase("bgcolor") || name.equalsIgnoreCase("bordercolor")) {
                PdfName dname = name.equalsIgnoreCase("bgcolor") ? PdfName.BG : PdfName.BC;
                for (int k = 0; k < item.size(); ++k) {
                    if (hit.isHit(k)) {
                        merged = item.getMerged(k);
                        PdfDictionary mk = merged.getAsDict(PdfName.MK);
                        if (mk == null) {
                            if (value == null)
                                return true;
                            mk = new PdfDictionary();
                            item.getMerged(k).put(PdfName.MK, mk);
                            item.getWidget(k).put(PdfName.MK, mk);
                            markUsed(item.getWidget(k));
                        } else {
                            markUsed(mk);
                        }
                        if (value == null)
                            mk.remove(dname);
                        else
                            mk.put(dname, PdfFormField.getMKColor((BaseColor) value));
                    }
                }
            } else
                return false;
            return true;
        } catch (Exception e) {
            throw new ExceptionConverter(e);
        }
    }

    /**
     * Sets a field property. Valid property names are:
     * <p>
     * <ul>
     * <li>flags - a set of flags specifying various characteristics of the field's widget annotation.
     * The value of this entry replaces that of the F entry in the form's corresponding annotation dictionary.<br>
     * <li>setflags - a set of flags to be set (turned on) in the F entry of the form's corresponding
     * widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 1.<br>
     * <li>clrflags - a set of flags to be cleared (turned off) in the F entry of the form's corresponding
     * widget annotation dictionary. Bits equal to 1 cause the corresponding
     * bits in F to be set to 0.<br>
     * <li>fflags - a set of flags specifying various characteristics of the field. The value
     * of this entry replaces that of the Ff entry in the form's corresponding field dictionary.<br>
     * <li>setfflags - a set of flags to be set (turned on) in the Ff entry of the form's corresponding
     * field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 1.<br>
     * <li>clrfflags - a set of flags to be cleared (turned off) in the Ff entry of the form's corresponding
     * field dictionary. Bits equal to 1 cause the corresponding bits in Ff
     * to be set to 0.<br>
     * </ul>
     *
     * @param field the field name
     * @param name  the property name
     * @param value the property value
     * @param inst  an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process.
     *              Set to <CODE>null</CODE> to process all
     * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise
     */
    public boolean setFieldProperty(String field, String name, int value, int inst[]) {
        if (writer == null)
            throw new RuntimeException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
        Item item = fields.get(field);
        if (item == null)
            return false;
        InstHit hit = new InstHit(inst);
        if (name.equalsIgnoreCase("flags")) {
            PdfNumber num = new PdfNumber(value);
            for (int k = 0; k < item.size(); ++k) {
                if (hit.isHit(k)) {
                    item.getMerged(k).put(PdfName.F, num);
                    item.getWidget(k).put(PdfName.F, num);
                    markUsed(item.getWidget(k));
                }
            }
        } else if (name.equalsIgnoreCase("setflags")) {
            for (int k = 0; k < item.size(); ++k) {
                if (hit.isHit(k)) {
                    PdfNumber num = item.getWidget(k).getAsNumber(PdfName.F);
                    int val = 0;
                    if (num != null)
                        val = num.intValue();
                    num = new PdfNumber(val | value);
                    item.getMerged(k).put(PdfName.F, num);
                    item.getWidget(k).put(PdfName.F, num);
                    markUsed(item.getWidget(k));
                }
            }
        } else if (name.equalsIgnoreCase("clrflags")) {
            for (int k = 0; k < item.size(); ++k) {
                if (hit.isHit(k)) {
                    PdfDictionary widget = item.getWidget(k);
                    PdfNumber num = widget.getAsNumber(PdfName.F);
                    int val = 0;
                    if (num != null)
                        val = num.intValue();
                    num = new PdfNumber(val & ~value);
                    item.getMerged(k).put(PdfName.F, num);
                    widget.put(PdfName.F, num);
                    markUsed(widget);
                }
            }
        } else if (name.equalsIgnoreCase("fflags")) {
            PdfNumber num = new PdfNumber(value);
            for (int k = 0; k < item.size(); ++k) {
                if (hit.isHit(k)) {
                    item.getMerged(k).put(PdfName.FF, num);
                    item.getValue(k).put(PdfName.FF, num);
                    markUsed(item.getValue(k));
                }
            }
        } else if (name.equalsIgnoreCase("setfflags")) {
            for (int k = 0; k < item.size(); ++k) {
                if (hit.isHit(k)) {
                    PdfDictionary valDict = item.getValue(k);
                    PdfNumber num = valDict.getAsNumber(PdfName.FF);
                    int val = 0;
                    if (num != null)
                        val = num.intValue();
                    num = new PdfNumber(val | value);
                    item.getMerged(k).put(PdfName.FF, num);
                    valDict.put(PdfName.FF, num);
                    markUsed(valDict);
                }
            }
        } else if (name.equalsIgnoreCase("clrfflags")) {
            for (int k = 0; k < item.size(); ++k) {
                if (hit.isHit(k)) {
                    PdfDictionary valDict = item.getValue(k);
                    PdfNumber num = valDict.getAsNumber(PdfName.FF);
                    int val = 0;
                    if (num != null)
                        val = num.intValue();
                    num = new PdfNumber(val & ~value);
                    item.getMerged(k).put(PdfName.FF, num);
                    valDict.put(PdfName.FF, num);
                    markUsed(valDict);
                }
            }
        } else
            return false;
        return true;
    }

    /**
     * Merges an XML data structure into this form.
     *
     * @param n the top node of the data structure
     * @throws java.io.IOException                 on error
     * @throws com.itextpdf.text.DocumentException o error
     */
    public void mergeXfaData(Node n) throws IOException, DocumentException {
        XfaForm.Xml2SomDatasets data = new XfaForm.Xml2SomDatasets(n);
        for (String string : data.getOrder()) {
            String name = string;
            String text = XfaForm.getNodeText(data.getName2Node().get(name));
            setField(name, text);
        }
    }

    /**
     * Sets the fields by FDF merging.
     *
     * @param fdf the FDF form
     * @throws IOException       on error
     * @throws DocumentException on error
     */
    public void setFields(FdfReader fdf) throws IOException, DocumentException {
        HashMap<String, PdfDictionary> fd = fdf.getFields();
        for (String f : fd.keySet()) {
            String v = fdf.getFieldValue(f);
            if (v != null)
                setField(f, v);
        }
    }

    /**
     * Regenerates the field appearance.
     * This is useful when you change a field property, but not its value,
     * for instance form.setFieldProperty("f", "bgcolor", BaseColor.BLUE, null);
     * This won't have any effect, unless you use regenerateField("f") after changing
     * the property.
     *
     * @param name the fully qualified field name or the partial name in the case of XFA forms
     * @return <CODE>true</CODE> if the field was found and changed,
     * <CODE>false</CODE> otherwise
     * @throws IOException       on error
     * @throws DocumentException on error
     */
    public boolean regenerateField(String name) throws IOException, DocumentException {
        String value = getField(name);
        return setField(name, value, value);
    }

    /**
     * Sets the field value.
     *
     * @param name  the fully qualified field name or the partial name in the case of XFA forms
     * @param value the field value
     * @return <CODE>true</CODE> if the field was found and changed,
     * <CODE>false</CODE> otherwise
     * @throws IOException       on error
     * @throws DocumentException on error
     */
    public boolean setField(String name, String value) throws IOException, DocumentException {
        return setField(name, value, null);
    }

    /**
     * Sets the field value.
     *
     * @param name           the fully qualified field name or the partial name in the case of XFA forms
     * @param value          the field value
     * @param saveAppearance save the current appearance of the field or not
     * @return <CODE>true</CODE> if the field was found and changed,
     * <CODE>false</CODE> otherwise
     * @throws IOException       on error
     * @throws DocumentException on error
     */
    public boolean setField(String name, String value, boolean saveAppearance) throws IOException, DocumentException {
        return setField(name, value, null, saveAppearance);
    }

    /**
     * Sets the rich value for the given field.  See <a href="http://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf">PDF Reference</a> chapter
     * 12.7.3.4 (Rich Text) and 12.7.4.3 (Text Fields) for further details. Note that iText doesn't create an appearance for Rich Text fields.
     * So you either need to use XML Worker to create an appearance (/N entry in the /AP dictionary), or you need to use setGenerateAppearances(false) to tell the viewer
     * that iText didn't create any appearances.
     *
     * @param name      Field name
     * @param richValue html markup
     * @return success/failure (will fail if the field isn't found, isn't a text field, or doesn't support rich text)
     * @throws DocumentException
     * @throws IOException
     * @since 5.0.6
     */
    public boolean setFieldRichValue(String name, String richValue) throws DocumentException, IOException {
        if (writer == null) {
            // can't set field values: fail
            throw new DocumentException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
        }

        AcroFields.Item item = getFieldItem(name);
        if (item == null) {
            // can't find the field: fail.
            return false;
        }

        if (getFieldType(name) != FIELD_TYPE_TEXT) {
            // field isn't a text field: fail
            return false;
        }

        PdfDictionary merged = item.getMerged(0);
        PdfNumber ffNum = merged.getAsNumber(PdfName.FF);
        int flagVal = 0;
        if (ffNum != null) {
            flagVal = ffNum.intValue();
        }
        if ((flagVal & PdfFormField.FF_RICHTEXT) == 0) {
            // text field doesn't support rich text: fail
            return false;
        }

        PdfString richString = new PdfString(richValue);
        item.writeToAll(PdfName.RV, richString, Item.WRITE_MERGED | Item.WRITE_VALUE);

        InputStream is = new ByteArrayInputStream(richValue.getBytes());
        PdfString valueString = new PdfString(XmlToTxt.parse(is));
        item.writeToAll(PdfName.V, valueString, Item.WRITE_MERGED | Item.WRITE_VALUE);
        return true;
    }

    /**
     * Sets the field value and the display string. The display string
     * is used to build the appearance in the cases where the value
     * is modified by Acrobat with JavaScript and the algorithm is
     * known.
     *
     * @param name    the fully qualified field name or the partial name in the case of XFA forms
     * @param value   the field value
     * @param display the string that is used for the appearance. If <CODE>null</CODE>
     *                the <CODE>value</CODE> parameter will be used
     * @return <CODE>true</CODE> if the field was found and changed,
     * <CODE>false</CODE> otherwise
     * @throws IOException       on error
     * @throws DocumentException on error
     */
    public boolean setField(String name, String value, String display) throws IOException, DocumentException {
        return setField(name, value, display, false);
    }

    /**
     * Sets the field value and the display string. The display string
     * is used to build the appearance in the cases where the value
     * is modified by Acrobat with JavaScript and the algorithm is
     * known.
     *
     * @param name           the fully qualified field name or the partial name in the case of XFA forms
     * @param value          the field value
     * @param display        the string that is used for the appearance. If <CODE>null</CODE>
     *                       the <CODE>value</CODE> parameter will be used
     * @param saveAppearance save the current appearance of the field or not
     * @return <CODE>true</CODE> if the field was found and changed,
     * <CODE>false</CODE> otherwise
     * @throws IOException       on error
     * @throws DocumentException on error
     */
    public boolean setField(String name, String value, String display, boolean saveAppearance) throws IOException, DocumentException {
        if (writer == null)
            throw new DocumentException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
        if (xfa.isXfaPresent()) {
            name = xfa.findFieldName(name, this);
            if (name == null)
                return false;
            String shortName = XfaForm.Xml2Som.getShortName(name);
            Node xn = xfa.findDatasetsNode(shortName);
            if (xn == null) {
                xn = xfa.getDatasetsSom().insertNode(xfa.getDatasetsNode(), shortName);
            }
            xfa.setNodeText(xn, value);
        }
        Item item = fields.get(name);
        if (item == null)
            return false;
        PdfDictionary merged = item.getMerged(0);
        PdfName type = merged.getAsName(PdfName.FT);
        if (PdfName.TX.equals(type)) {
            PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN);
            int len = 0;
            if (maxLen != null)
                len = maxLen.intValue();
            if (len > 0)
                value = value.substring(0, Math.min(len, value.length()));
        }
        if (display == null)
            display = value;
        if (PdfName.TX.equals(type) || PdfName.CH.equals(type)) {
            PdfString v = new PdfString(value, PdfObject.TEXT_UNICODE);
            for (int idx = 0; idx < item.size(); ++idx) {
                PdfDictionary valueDic = item.getValue(idx);
                valueDic.put(PdfName.V, v);
                valueDic.remove(PdfName.I);
                markUsed(valueDic);
                merged = item.getMerged(idx);
                merged.remove(PdfName.I);
                merged.put(PdfName.V, v);
                PdfDictionary widget = item.getWidget(idx);
                if (generateAppearances) {
                    PdfAppearance app = getAppearance(merged, display, name);
                    if (PdfName.CH.equals(type)) {
                        PdfNumber n = new PdfNumber(topFirst);
                        widget.put(PdfName.TI, n);
                        merged.put(PdfName.TI, n);
                    }
                    PdfDictionary appDic = widget.getAsDict(PdfName.AP);
                    if (appDic == null) {
                        appDic = new PdfDictionary();
                        widget.put(PdfName.AP, appDic);
                        merged.put(PdfName.AP, appDic);
                    }
                    appDic.put(PdfName.N, app.getIndirectReference());
                    writer.releaseTemplate(app);
                } else {
                    widget.remove(PdfName.AP);
                    merged.remove(PdfName.AP);
                }
                markUsed(widget);
            }
            return true;
        } else if (PdfName.BTN.equals(type)) {
            PdfNumber ff = item.getMerged(0).getAsNumber(PdfName.FF);
            int flags = 0;
            if (ff != null)
                flags = ff.intValue();
            if ((flags & PdfFormField.FF_PUSHBUTTON) != 0) {
                //we'll assume that the value is an image in base64
                Image img;
                try {
                    img = Image.getInstance(Base64.decode(value));
                } catch (Exception e) {
                    return false;
                }
                PushbuttonField pb = getNewPushbuttonFromField(name);
                pb.setImage(img);
                replacePushbuttonField(name, pb.getField());
                return true;
            }
            PdfName v = new PdfName(value);
            ArrayList<String> lopt = new ArrayList<String>();
            PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT);
            if (opts != null) {
                for (int k = 0; k < opts.size(); ++k) {
                    PdfString valStr = opts.getAsString(k);
                    if (valStr != null)
                        lopt.add(valStr.toUnicodeString());
                    else
                        lopt.add(null);
                }
            }
            int vidx = lopt.indexOf(value);
            PdfName vt;
            if (vidx >= 0)
                vt = new PdfName(String.valueOf(vidx));
            else
                vt = v;
            for (int idx = 0; idx < item.size(); ++idx) {
                merged = item.getMerged(idx);
                PdfDictionary widget = item.getWidget(idx);
                PdfDictionary valDict = item.getValue(idx);
                markUsed(item.getValue(idx));
                valDict.put(PdfName.V, vt);
                merged.put(PdfName.V, vt);
                markUsed(widget);
                PdfDictionary appDic = widget.getAsDict(PdfName.AP);
                if (appDic == null)
                    return false;
                PdfDictionary normal = appDic.getAsDict(PdfName.N);
                if (isInAP(normal, vt) || normal == null) {
                    merged.put(PdfName.AS, vt);
                    widget.put(PdfName.AS, vt);
                } else {
                    merged.put(PdfName.AS, PdfName.Off);
                    widget.put(PdfName.AS, PdfName.Off);
                }
                if (generateAppearances && !saveAppearance) {
                    PdfAppearance app = getAppearance(merged, display, name);
                    if (normal != null)
                        normal.put(merged.getAsName(PdfName.AS), app.getIndirectReference());
                    else
                        appDic.put(PdfName.N, app.getIndirectReference());
                    writer.releaseTemplate(app);
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Sets different values in a list selection.
     * No appearance is generated yet; nor does the code check if multiple select is allowed.
     *
     * @param name  the name of the field
     * @param value an array with values that need to be selected
     * @return true only if the field value was changed
     * @since 2.1.4
     */
    public boolean setListSelection(String name, String[] value) throws IOException, DocumentException {
        Item item = getFieldItem(name);
        if (item == null)
            return false;
        PdfDictionary merged = item.getMerged(0);
        PdfName type = merged.getAsName(PdfName.FT);
        if (!PdfName.CH.equals(type)) {
            return false;
        }
        String[] options = getListOptionExport(name);
        PdfArray array = new PdfArray();
        for (String element : value) {
            for (int j = 0; j < options.length; j++) {
                if (options[j].equals(element)) {
                    array.add(new PdfNumber(j));
                    break;
                }
            }
        }
        item.writeToAll(PdfName.I, array, Item.WRITE_MERGED | Item.WRITE_VALUE);

        PdfArray vals = new PdfArray();
        for (int i = 0; i < value.length; ++i) {
            vals.add(new PdfString(value[i]));
        }
        item.writeToAll(PdfName.V, vals, Item.WRITE_MERGED | Item.WRITE_VALUE);

        PdfAppearance app = getAppearance(merged, value, name);

        PdfDictionary apDic = new PdfDictionary();
        apDic.put(PdfName.N, app.getIndirectReference());
        item.writeToAll(PdfName.AP, apDic, Item.WRITE_MERGED | Item.WRITE_WIDGET);

        writer.releaseTemplate(app);

        item.markUsed(this, Item.WRITE_VALUE | Item.WRITE_WIDGET);
        return true;
    }

    boolean isInAP(PdfDictionary nDic, PdfName check) {
        return nDic != null && nDic.get(check) != null;
    }

    /**
     * Gets all the fields. The fields are keyed by the fully qualified field name and
     * the value is an instance of <CODE>AcroFields.Item</CODE>.
     *
     * @return all the fields
     */
    public Map<String, Item> getFields() {
        return fields;
    }

    /**
     * Sets the fields by XFDF merging.
     *
     * @param xfdf the XFDF form
     * @throws IOException       on error
     * @throws DocumentException on error
     */
    public void setFields(XfdfReader xfdf) throws IOException, DocumentException {
        HashMap<String, String> fd = xfdf.getFields();
        for (String f : fd.keySet()) {
            String v = xfdf.getFieldValue(f);
            if (v != null)
                setField(f, v);
            List<String> l = xfdf.getListValues(f);
            if (l != null)
                setListSelection(v, l.toArray(new String[l.size()]));
        }
    }

    /**
     * Gets the field structure.
     *
     * @param name the name of the field
     * @return the field structure or <CODE>null</CODE> if the field
     * does not exist
     */
    public Item getFieldItem(String name) {
        if (xfa.isXfaPresent()) {
            name = xfa.findFieldName(name, this);
            if (name == null)
                return null;
        }
        return fields.get(name);
    }

    /**
     * Gets the long XFA translated name.
     *
     * @param name the name of the field
     * @return the long field name
     */
    public String getTranslatedFieldName(String name) {
        if (xfa.isXfaPresent()) {
            String namex = xfa.findFieldName(name, this);
            if (namex != null)
                name = namex;
        }
        return name;
    }

    /**
     * Gets the field box positions in the document. The return is an array of <CODE>float</CODE>
     * multiple of 5. For each of this groups the values are: [page, llx, lly, urx,
     * ury]. The coordinates have the page rotation in consideration.
     *
     * @param name the field name
     * @return the positions or <CODE>null</CODE> if field does not exist
     */
    public List<FieldPosition> getFieldPositions(String name) {
        Item item = getFieldItem(name);
        if (item == null)
            return null;
        ArrayList<FieldPosition> ret = new ArrayList<FieldPosition>();
        for (int k = 0; k < item.size(); ++k) {
            try {
                PdfDictionary wd = item.getWidget(k);
                PdfArray rect = wd.getAsArray(PdfName.RECT);
                if (rect == null)
                    continue;
                Rectangle r = PdfReader.getNormalizedRectangle(rect);
                int page = item.getPage(k).intValue();
                int rotation = reader.getPageRotation(page);
                FieldPosition fp = new FieldPosition();
                fp.page = page;
                if (rotation != 0) {
                    Rectangle pageSize = reader.getPageSize(page);
                    switch (rotation) {
                        case 270:
                            r = new Rectangle(
                                    pageSize.getTop() - r.getBottom(),
                                    r.getLeft(),
                                    pageSize.getTop() - r.getTop(),
                                    r.getRight());
                            break;
                        case 180:
                            r = new Rectangle(
                                    pageSize.getRight() - r.getLeft(),
                                    pageSize.getTop() - r.getBottom(),
                                    pageSize.getRight() - r.getRight(),
                                    pageSize.getTop() - r.getTop());
                            break;
                        case 90:
                            r = new Rectangle(
                                    r.getBottom(),
                                    pageSize.getRight() - r.getLeft(),
                                    r.getTop(),
                                    pageSize.getRight() - r.getRight());
                            break;
                    }
                    r.normalize();
                }
                fp.position = r;
                ret.add(fp);
            } catch (Exception e) {
                // empty on purpose
            }
        }
        return ret;
    }

    private int removeRefFromArray(PdfArray array, PdfObject refo) {
        if (refo == null || !refo.isIndirect())
            return array.size();
        PdfIndirectReference ref = (PdfIndirectReference) refo;
        for (int j = 0; j < array.size(); ++j) {
            PdfObject obj = array.getPdfObject(j);
            if (!obj.isIndirect())
                continue;
            if (((PdfIndirectReference) obj).getNumber() == ref.getNumber())
                array.remove(j--);
        }
        return array.size();
    }

    /**
     * Removes all the fields from <CODE>page</CODE>.
     *
     * @param page the page to remove the fields from
     * @return <CODE>true</CODE> if any field was removed, <CODE>false otherwise</CODE>
     */
    public boolean removeFieldsFromPage(int page) {
        if (page < 1)
            return false;
        String names[] = new String[fields.size()];
        fields.keySet().toArray(names);
        boolean found = false;
        for (int k = 0; k < names.length; ++k) {
            boolean fr = removeField(names[k], page);
            found = found || fr;
        }
        return found;
    }

    /**
     * Removes a field from the document. If page equals -1 all the fields with this
     * <CODE>name</CODE> are removed from the document otherwise only the fields in
     * that particular page are removed.
     *
     * @param name the field name
     * @param page the page to remove the field from or -1 to remove it from all the pages
     * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE>
     */
    public boolean removeField(String name, int page) {
        Item item = getFieldItem(name);
        if (item == null)
            return false;
        PdfDictionary acroForm = (PdfDictionary) PdfReader.getPdfObject(reader.getCatalog().get(PdfName.ACROFORM), reader.getCatalog());

        if (acroForm == null)
            return false;
        PdfArray arrayf = acroForm.getAsArray(PdfName.FIELDS);
        if (arrayf == null)
            return false;
        for (int k = 0; k < item.size(); ++k) {
            int pageV = item.getPage(k).intValue();
            if (page != -1 && page != pageV)
                continue;
            PdfIndirectReference ref = item.getWidgetRef(k);
            PdfDictionary wd = item.getWidget(k);
            PdfDictionary pageDic = reader.getPageN(pageV); //Returns null for floating, orphaned fields
            PdfArray annots = pageDic != null ? pageDic.getAsArray(PdfName.ANNOTS) : null; //Account for floating, orphaned fields
            if (annots != null) {
                if (removeRefFromArray(annots, ref) == 0) {
                    pageDic.remove(PdfName.ANNOTS);
                    markUsed(pageDic);
                } else
                    markUsed(annots);
            }
            PdfReader.killIndirect(ref);
            PdfIndirectReference kid = ref;
            while ((ref = wd.getAsIndirectObject(PdfName.PARENT)) != null) {
                wd = wd.getAsDict(PdfName.PARENT);
                if (wd == null) break;
                PdfArray kids = wd.getAsArray(PdfName.KIDS);
                if (removeRefFromArray(kids, kid) != 0)
                    break;
                kid = ref;
                PdfReader.killIndirect(ref);
            }
            if (ref == null) {
                removeRefFromArray(arrayf, kid);
                markUsed(arrayf);
            }
            if (page != -1) {
                item.remove(k);
                --k;
            }
        }
        if (page == -1 || item.size() == 0)
            fields.remove(name);
        return true;
    }

    /**
     * Removes a field from the document.
     *
     * @param name the field name
     * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE>
     */
    public boolean removeField(String name) {
        return removeField(name, -1);
    }

    /**
     * Gets the property generateAppearances.
     *
     * @return the property generateAppearances
     */
    public boolean isGenerateAppearances() {
        return generateAppearances;
    }

    /**
     * Sets the option to generate appearances. Not generating appearances
     * will speed-up form filling but the results can be
     * unexpected in Acrobat. Don't use it unless your environment is well
     * controlled. The default is <CODE>true</CODE>.
     *
     * @param generateAppearances the option to generate appearances
     */
    public void setGenerateAppearances(boolean generateAppearances) {
        this.generateAppearances = generateAppearances;
        PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM);
        if (generateAppearances)
            top.remove(PdfName.NEEDAPPEARANCES);
        else
            top.put(PdfName.NEEDAPPEARANCES, PdfBoolean.PDFTRUE);
    }

    /**
     * Clears a signed field.
     *
     * @param name the field name
     * @return true if the field was signed, false if the field was not signed or not found
     * @since 5.0.5
     */
    public boolean clearSignatureField(String name) {
        sigNames = null;
        getSignatureNames();
        if (!sigNames.containsKey(name))
            return false;
        Item sig = fields.get(name);
        sig.markUsed(this, Item.WRITE_VALUE | Item.WRITE_WIDGET);
        int n = sig.size();
        for (int k = 0; k < n; ++k) {
            clearSigDic(sig.getMerged(k));
            clearSigDic(sig.getWidget(k));
            clearSigDic(sig.getValue(k));
        }
        return true;
    }

    /**
     * Gets the field names that have signatures and are signed.
     *
     * @return the field names that have signatures and are signed
     */
    public ArrayList<String> getSignatureNames() {
        if (sigNames != null)
            return new ArrayList<String>(orderedSignatureNames);
        sigNames = new HashMap<String, int[]>();
        orderedSignatureNames = new ArrayList<String>();
        ArrayList<Object[]> sorter = new ArrayList<Object[]>();
        for (Map.Entry<String, Item> entry : fields.entrySet()) {
            Item item = entry.getValue();
            PdfDictionary merged = item.getMerged(0);
            if (!PdfName.SIG.equals(merged.get(PdfName.FT)))
                continue;
            PdfDictionary v = merged.getAsDict(PdfName.V);
            if (v == null)
                continue;
            PdfString contents = v.getAsString(PdfName.CONTENTS);
            if (contents == null)
                continue;
            PdfArray ro = v.getAsArray(PdfName.BYTERANGE);
            if (ro == null)
                continue;
            int rangeSize = ro.size();
            if (rangeSize < 2)
                continue;
            int length = ro.getAsNumber(rangeSize - 1).intValue() + ro.getAsNumber(rangeSize - 2).intValue();
            sorter.add(new Object[]{entry.getKey(), new int[]{length, 0}});
        }
        Collections.sort(sorter, new AcroFields.SorterComparator());
        if (!sorter.isEmpty()) {
            if (((int[]) sorter.get(sorter.size() - 1)[1])[0] == reader.getFileLength())
                totalRevisions = sorter.size();
            else
                totalRevisions = sorter.size() + 1;
            for (int k = 0; k < sorter.size(); ++k) {
                Object objs[] = sorter.get(k);
                String name = (String) objs[0];
                int p[] = (int[]) objs[1];
                p[1] = k + 1;
                sigNames.put(name, p);
                orderedSignatureNames.add(name);
            }
        }
        return new ArrayList<String>(orderedSignatureNames);
    }

    /**
     * Gets the field names that have blank signatures.
     *
     * @return the field names that have blank signatures
     */
    public ArrayList<String> getBlankSignatureNames() {
        getSignatureNames();
        ArrayList<String> sigs = new ArrayList<String>();
        for (Map.Entry<String, Item> entry : fields.entrySet()) {
            Item item = entry.getValue();
            PdfDictionary merged = item.getMerged(0);
            if (!PdfName.SIG.equals(merged.getAsName(PdfName.FT)))
                continue;
            if (sigNames.containsKey(entry.getKey()))
                continue;
            sigs.add(entry.getKey());
        }
        return sigs;
    }

    /**
     * Gets the signature dictionary, the one keyed by /V.
     *
     * @param name the field name
     * @return the signature dictionary keyed by /V or <CODE>null</CODE> if the field is not
     * a signature
     */
    public PdfDictionary getSignatureDictionary(String name) {
        getSignatureNames();
        name = getTranslatedFieldName(name);
        if (!sigNames.containsKey(name))
            return null;
        Item item = fields.get(name);
        PdfDictionary merged = item.getMerged(0);
        return merged.getAsDict(PdfName.V);
    }

    /**
     * Gets a reference to the normal appearance of a field.
     *
     * @param name the field name
     * @return a reference to the /N entry of the /AP dictionary or <CODE>null</CODE> if the field is not found
     */
    public PdfIndirectReference getNormalAppearance(String name) {
        getSignatureNames();
        name = getTranslatedFieldName(name);
        Item item = fields.get(name);
        if (item == null)
            return null;
        PdfDictionary merged = item.getMerged(0);
        PdfDictionary ap = merged.getAsDict(PdfName.AP);
        if (ap == null)
            return null;
        PdfIndirectReference ref = ap.getAsIndirectObject(PdfName.N);
        if (ref == null)
            return null;
        return ref;
    }

    /**
     * Checks is the signature covers the entire document or just part of it.
     *
     * @param name the signature field name
     * @return <CODE>true</CODE> if the signature covers the entire document,
     * <CODE>false</CODE> otherwise
     */
    public boolean signatureCoversWholeDocument(String name) {
        getSignatureNames();
        name = getTranslatedFieldName(name);
        if (!sigNames.containsKey(name))
            return false;
        try {
            ContentsChecker signatureReader = new ContentsChecker(reader.getSafeFile());
            return signatureReader.checkWhetherSignatureCoversWholeDocument(reader.getAcroFields().getFieldItem(name));
        } catch (IOException e) {
            // That's not expected because if the signature is invalid, it should have already failed
            return false;
        }
    }

    /**
     * Verifies a signature. An example usage is:
     * <p>
     * <pre>
     * KeyStore kall = PdfPKCS7.loadCacertsKeyStore();
     * PdfReader reader = new PdfReader("my_signed_doc.pdf");
     * AcroFields af = reader.getAcroFields();
     * ArrayList names = af.getSignatureNames();
     * for (int k = 0; k &lt; names.size(); ++k) {
     *    String name = (String)names.get(k);
     *    System.out.println("Signature name: " + name);
     *    System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name));
     *    PdfPKCS7 pk = af.verifySignature(name);
     *    Calendar cal = pk.getSignDate();
     *    Certificate pkc[] = pk.getCertificates();
     *    System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate()));
     *    System.out.println("Document modified: " + !pk.verify());
     *    Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal);
     *    if (fails == null)
     *        System.out.println("Certificates verified against the KeyStore");
     *    else
     *        System.out.println("Certificate failed: " + fails[1]);
     * }
     * </pre>
     *
     * @param name the signature field name
     * @return a <CODE>PdfPKCS7</CODE> class to continue the verification
     */
    public PdfPKCS7 verifySignature(String name) {
        return verifySignature(name, null);
    }

    /**
     * Verifies a signature. An example usage is:
     * <p>
     * <pre>
     * KeyStore kall = PdfPKCS7.loadCacertsKeyStore();
     * PdfReader reader = new PdfReader("my_signed_doc.pdf");
     * AcroFields af = reader.getAcroFields();
     * ArrayList names = af.getSignatureNames();
     * for (int k = 0; k &lt; names.size(); ++k) {
     *    String name = (String)names.get(k);
     *    System.out.println("Signature name: " + name);
     *    System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name));
     *    PdfPKCS7 pk = af.verifySignature(name);
     *    Calendar cal = pk.getSignDate();
     *    Certificate pkc[] = pk.getCertificates();
     *    System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate()));
     *    System.out.println("Document modified: " + !pk.verify());
     *    Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal);
     *    if (fails == null)
     *        System.out.println("Certificates verified against the KeyStore");
     *    else
     *        System.out.println("Certificate failed: " + fails[1]);
     * }
     * </pre>
     *
     * @param name     the signature field name
     * @param provider the provider or <code>null</code> for the default provider
     * @return a <CODE>PdfPKCS7</CODE> class to continue the verification
     */
    public PdfPKCS7 verifySignature(String name, String provider) {
        PdfDictionary v = getSignatureDictionary(name);
        if (v == null)
            return null;
        try {
            PdfName sub = v.getAsName(PdfName.SUBFILTER);
            PdfString contents = v.getAsString(PdfName.CONTENTS);
            PdfPKCS7 pk = null;
            if (sub.equals(PdfName.ADBE_X509_RSA_SHA1)) {
                PdfString cert = v.getAsString(PdfName.CERT);
                if (cert == null)
                    cert = v.getAsArray(PdfName.CERT).getAsString(0);
                if (!reader.isEncrypted()) {
                    pk = new PdfPKCS7(contents.getOriginalBytes(), cert.getBytes(), provider);
                } else {
                    pk = new PdfPKCS7(contents.getBytes(), cert.getBytes(), provider);
                }
            } else {
                if (!reader.isEncrypted()) {
                    pk = new PdfPKCS7(contents.getOriginalBytes(), sub, provider);
                } else {
                    pk = new PdfPKCS7(contents.getBytes(), sub, provider);
                }
            }

            updateByteRange(pk, v);
            PdfString str = v.getAsString(PdfName.M);
            if (str != null)
                pk.setSignDate(PdfDate.decode(str.toString()));
            PdfObject obj = PdfReader.getPdfObject(v.get(PdfName.NAME));
            if (obj != null) {
                if (obj.isString())
                    pk.setSignName(((PdfString) obj).toUnicodeString());
                else if (obj.isName())
                    pk.setSignName(PdfName.decodeName(obj.toString()));
            }
            str = v.getAsString(PdfName.REASON);
            if (str != null)
                pk.setReason(str.toUnicodeString());
            str = v.getAsString(PdfName.LOCATION);
            if (str != null)
                pk.setLocation(str.toUnicodeString());
            return pk;
        } catch (Exception e) {
            throw new ExceptionConverter(e);
        }
    }

    private void updateByteRange(PdfPKCS7 pkcs7, PdfDictionary v) {
        PdfArray b = v.getAsArray(PdfName.BYTERANGE);
        RandomAccessFileOrArray rf = reader.getSafeFile();
        InputStream rg = null;
        try {
            rg = new RASInputStream(new RandomAccessSourceFactory().createRanged(rf.createSourceView(), b.asLongArray()));
            byte buf[] = new byte[8192];
            int rd;
            while ((rd = rg.read(buf, 0, buf.length)) > 0) {
                pkcs7.update(buf, 0, rd);
            }
        } catch (Exception e) {
            throw new ExceptionConverter(e);
        } finally {
            try {
                if (rg != null) rg.close();
            } catch (IOException e) {
                // this really shouldn't ever happen - the source view we use is based on a Safe view, which is a no-op anyway
                throw new ExceptionConverter(e);
            }
        }
    }

    private void markUsed(PdfObject obj) {
        if (!append)
            return;
        ((PdfStamperImp) writer).markUsed(obj);
    }

    /**
     * Gets the total number of revisions this document has.
     *
     * @return the total number of revisions
     */
    public int getTotalRevisions() {
        getSignatureNames();
        return this.totalRevisions;
    }

    /**
     * Gets this <CODE>field</CODE> revision.
     *
     * @param field the signature field name
     * @return the revision or zero if it's not a signature field
     */
    public int getRevision(String field) {
        getSignatureNames();
        field = getTranslatedFieldName(field);
        if (!sigNames.containsKey(field))
            return 0;
        return sigNames.get(field)[1];
    }

    /**
     * Extracts a revision from the document.
     *
     * @param field the signature field name
     * @return an <CODE>InputStream</CODE> covering the revision. Returns <CODE>null</CODE> if
     * it's not a signature field
     * @throws IOException on error
     */
    public InputStream extractRevision(String field) throws IOException {
        getSignatureNames();
        field = getTranslatedFieldName(field);
        if (!sigNames.containsKey(field))
            return null;
        int length = sigNames.get(field)[0];
        RandomAccessFileOrArray raf = reader.getSafeFile();
        return new RASInputStream(new WindowRandomAccessSource(raf.createSourceView(), 0, length));
    }

    /**
     * Gets the appearances cache.
     *
     * @return the appearances cache
     * @since 2.1.5    this method used to return a HashMap
     */
    public Map<String, TextField> getFieldCache() {
        return this.fieldCache;
    }

    /**
     * Sets a cache for field appearances. Parsing the existing PDF to
     * create a new TextField is time expensive. For those tasks that repeatedly
     * fill the same PDF with different field values the use of the cache has dramatic
     * speed advantages. An example usage:
     * <p>
     * <pre>
     * String pdfFile = ...;// the pdf file used as template
     * ArrayList xfdfFiles = ...;// the xfdf file names
     * ArrayList pdfOutFiles = ...;// the output file names, one for each element in xpdfFiles
     * HashMap cache = new HashMap();// the appearances cache
     * PdfReader originalReader = new PdfReader(pdfFile);
     * for (int k = 0; k &lt; xfdfFiles.size(); ++k) {
     *    PdfReader reader = new PdfReader(originalReader);
     *    XfdfReader xfdf = new XfdfReader((String)xfdfFiles.get(k));
     *    PdfStamper stp = new PdfStamper(reader, new FileOutputStream((String)pdfOutFiles.get(k)));
     *    AcroFields af = stp.getAcroFields();
     *    af.setFieldCache(cache);
     *    af.setFields(xfdf);
     *    stp.close();
     * }
     * </pre>
     *
     * @param fieldCache a Map that will carry the cached appearances
     * @since 2.1.5    this method used to take a HashMap as parameter
     */
    public void setFieldCache(Map<String, TextField> fieldCache) {
        this.fieldCache = fieldCache;
    }

    /**
     * Sets extra margins in text fields to better mimic the Acrobat layout.
     *
     * @param extraMarginLeft the extra margin left
     * @param extraMarginTop  the extra margin top
     */
    public void setExtraMargin(float extraMarginLeft, float extraMarginTop) {
        this.extraMarginLeft = extraMarginLeft;
        this.extraMarginTop = extraMarginTop;
    }

    /**
     * Adds a substitution font to the list. The fonts in this list will be used if the original
     * font doesn't contain the needed glyphs.
     *
     * @param font the font
     */
    public void addSubstitutionFont(BaseFont font) {
        if (substitutionFonts == null)
            substitutionFonts = new ArrayList<BaseFont>();
        substitutionFonts.add(font);
    }

    /**
     * Gets the list of substitution fonts. The list is composed of <CODE>BaseFont</CODE> and can be <CODE>null</CODE>. The fonts in this list will be used if the original
     * font doesn't contain the needed glyphs.
     *
     * @return the list
     */
    public ArrayList<BaseFont> getSubstitutionFonts() {
        return substitutionFonts;
    }

    /**
     * Sets a list of substitution fonts. The list is composed of <CODE>BaseFont</CODE> and can also be <CODE>null</CODE>. The fonts in this list will be used if the original
     * font doesn't contain the needed glyphs.
     *
     * @param substitutionFonts the list
     */
    public void setSubstitutionFonts(ArrayList<BaseFont> substitutionFonts) {
        this.substitutionFonts = substitutionFonts;
    }

    /**
     * Gets the XFA form processor.
     *
     * @return the XFA form processor
     */
    public XfaForm getXfa() {
        return xfa;
    }

    /**
     * Removes the XFA stream from the document.
     */
    public void removeXfa() {
        PdfDictionary root = reader.getCatalog();
        PdfDictionary acroform = root.getAsDict(PdfName.ACROFORM);
        acroform.remove(PdfName.XFA);
        try {
            xfa = new XfaForm(reader);
        } catch (Exception e) {
            throw new ExceptionConverter(e);
        }
    }

    /**
     * Creates a new pushbutton from an existing field. If there are several pushbuttons with the same name
     * only the first one is used. This pushbutton can be changed and be used to replace
     * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton
     * call {@link #replacePushbuttonField(String, PdfFormField)}.
     *
     * @param field the field name that should be a pushbutton
     * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton
     */
    public PushbuttonField getNewPushbuttonFromField(String field) {
        return getNewPushbuttonFromField(field, 0);
    }

    /**
     * Creates a new pushbutton from an existing field. This pushbutton can be changed and be used to replace
     * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton
     * call {@link #replacePushbuttonField(String, PdfFormField, int)}.
     *
     * @param field the field name that should be a pushbutton
     * @param order the field order in fields with same name
     * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton
     * @since 2.0.7
     */
    public PushbuttonField getNewPushbuttonFromField(String field, int order) {
        try {
            if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON)
                return null;
            Item item = getFieldItem(field);
            if (order >= item.size())
                return null;
            List<FieldPosition> pos = getFieldPositions(field);
            Rectangle box = pos.get(order).position;
            PushbuttonField newButton = new PushbuttonField(writer, box, null);
            PdfDictionary dic = item.getMerged(order);
            decodeGenericDictionary(dic, newButton);
            PdfDictionary mk = dic.getAsDict(PdfName.MK);
            if (mk != null) {
                PdfString text = mk.getAsString(PdfName.CA);
                if (text != null)
                    newButton.setText(text.toUnicodeString());
                PdfNumber tp = mk.getAsNumber(PdfName.TP);
                if (tp != null)
                    newButton.setLayout(tp.intValue() + 1);
                PdfDictionary ifit = mk.getAsDict(PdfName.IF);
                if (ifit != null) {
                    PdfName sw = ifit.getAsName(PdfName.SW);
                    if (sw != null) {
                        int scale = PushbuttonField.SCALE_ICON_ALWAYS;
                        if (sw.equals(PdfName.B))
                            scale = PushbuttonField.SCALE_ICON_IS_TOO_BIG;
                        else if (sw.equals(PdfName.S))
                            scale = PushbuttonField.SCALE_ICON_IS_TOO_SMALL;
                        else if (sw.equals(PdfName.N))
                            scale = PushbuttonField.SCALE_ICON_NEVER;
                        newButton.setScaleIcon(scale);
                    }
                    sw = ifit.getAsName(PdfName.S);
                    if (sw != null) {
                        if (sw.equals(PdfName.A))
                            newButton.setProportionalIcon(false);
                    }
                    PdfArray aj = ifit.getAsArray(PdfName.A);
                    if (aj != null && aj.size() == 2) {
                        float left = aj.getAsNumber(0).floatValue();
                        float bottom = aj.getAsNumber(1).floatValue();
                        newButton.setIconHorizontalAdjustment(left);
                        newButton.setIconVerticalAdjustment(bottom);
                    }
                    PdfBoolean fb = ifit.getAsBoolean(PdfName.FB);
                    if (fb != null && fb.booleanValue())
                        newButton.setIconFitToBounds(true);
                }
                PdfObject i = mk.get(PdfName.I);
                if (i != null && i.isIndirect())
                    newButton.setIconReference((PRIndirectReference) i);
            }
            return newButton;
        } catch (Exception e) {
            throw new ExceptionConverter(e);
        }
    }

    /**
     * Replaces the first field with a new pushbutton. The pushbutton can be created with
     * {@link #getNewPushbuttonFromField(String)} from the same document or it can be a
     * generic PdfFormField of the type pushbutton.
     *
     * @param field  the field name
     * @param button the <CODE>PdfFormField</CODE> representing the pushbutton
     * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field
     * was not a pushbutton
     */
    public boolean replacePushbuttonField(String field, PdfFormField button) {
        return replacePushbuttonField(field, button, 0);
    }

    /**
     * Replaces the designated field with a new pushbutton. The pushbutton can be created with
     * {@link #getNewPushbuttonFromField(String, int)} from the same document or it can be a
     * generic PdfFormField of the type pushbutton.
     *
     * @param field  the field name
     * @param button the <CODE>PdfFormField</CODE> representing the pushbutton
     * @param order  the field order in fields with same name
     * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field
     * was not a pushbutton
     * @since 2.0.7
     */
    public boolean replacePushbuttonField(String field, PdfFormField button, int order) {
        if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON)
            return false;
        Item item = getFieldItem(field);
        if (order >= item.size())
            return false;
        PdfDictionary merged = item.getMerged(order);
        PdfDictionary values = item.getValue(order);
        PdfDictionary widgets = item.getWidget(order);
        for (int k = 0; k < buttonRemove.length; ++k) {
            merged.remove(buttonRemove[k]);
            values.remove(buttonRemove[k]);
            widgets.remove(buttonRemove[k]);
        }
        for (Object element : button.getKeys()) {
            PdfName key = (PdfName) element;
            if (key.equals(PdfName.T))
                continue;
            if (key.equals(PdfName.FF))
                values.put(key, button.get(key));
            else
                widgets.put(key, button.get(key));
            merged.put(key, button.get(key));
            markUsed(values);
            markUsed(widgets);
        }
        return true;
    }

    /**
     * Checks whether a name exists as a signature field or not. It checks both signed fields and blank signatures.
     *
     * @param name String
     * @return boolean does the signature field exist
     * @since 5.5.1
     */
    public boolean doesSignatureFieldExist(String name) {
        return getBlankSignatureNames().contains(name) || getSignatureNames().contains(name);
    }

    /**
     * The field representations for retrieval and modification.
     */
    public static class Item {

        /**
         * <CODE>writeToAll</CODE> constant.
         *
         * @since 2.1.5
         */
        public static final int WRITE_MERGED = 1;

        /**
         * <CODE>writeToAll</CODE> and <CODE>markUsed</CODE> constant.
         *
         * @since 2.1.5
         */
        public static final int WRITE_WIDGET = 2;

        /**
         * <CODE>writeToAll</CODE> and <CODE>markUsed</CODE> constant.
         *
         * @since 2.1.5
         */
        public static final int WRITE_VALUE = 4;
        /**
         * An array of <CODE>PdfDictionary</CODE> where the value tag /V
         * is present.
         *
         * @since 5.0.2 public is now protected
         */
        protected ArrayList<PdfDictionary> values = new ArrayList<PdfDictionary>();
        /**
         * An array of <CODE>PdfDictionary</CODE> with the widgets.
         *
         * @since 5.0.2 public is now protected
         */
        protected ArrayList<PdfDictionary> widgets = new ArrayList<PdfDictionary>();
        /**
         * An array of <CODE>PdfDictionary</CODE> with the widget references.
         *
         * @since 5.0.2 public is now protected
         */
        protected ArrayList<PdfIndirectReference> widget_refs = new ArrayList<PdfIndirectReference>();
        /**
         * An array of <CODE>PdfDictionary</CODE> with all the field
         * and widget tags merged.
         *
         * @since 5.0.2 public is now protected
         */
        protected ArrayList<PdfDictionary> merged = new ArrayList<PdfDictionary>();
        /**
         * An array of <CODE>Integer</CODE> with the page numbers where
         * the widgets are displayed.
         *
         * @since 5.0.2 public is now protected
         */
        protected ArrayList<Integer> page = new ArrayList<Integer>();
        /**
         * An array of <CODE>Integer</CODE> with the tab order of the field in the page.
         *
         * @since 5.0.2 public is now protected
         */
        protected ArrayList<Integer> tabOrder = new ArrayList<Integer>();

        /**
         * This function writes the given key/value pair to all the instances
         * of merged, widget, and/or value, depending on the <code>writeFlags</code> setting
         *
         * @param key        you'll never guess what this is for.
         * @param value      if value is null, the key will be removed
         * @param writeFlags ORed together WRITE_* flags
         * @since 2.1.5
         */
        public void writeToAll(PdfName key, PdfObject value, int writeFlags) {
            int i;
            PdfDictionary curDict = null;
            if ((writeFlags & WRITE_MERGED) != 0) {
                for (i = 0; i < merged.size(); ++i) {
                    curDict = getMerged(i);
                    curDict.put(key, value);
                }
            }
            if ((writeFlags & WRITE_WIDGET) != 0) {
                for (i = 0; i < widgets.size(); ++i) {
                    curDict = getWidget(i);
                    curDict.put(key, value);
                }
            }
            if ((writeFlags & WRITE_VALUE) != 0) {
                for (i = 0; i < values.size(); ++i) {
                    curDict = getValue(i);
                    curDict.put(key, value);
                }
            }
        }

        /**
         * Mark all the item dictionaries used matching the given flags
         *
         * @param writeFlags WRITE_MERGED is ignored
         * @since 2.1.5
         */
        public void markUsed(AcroFields parentFields, int writeFlags) {
            if ((writeFlags & WRITE_VALUE) != 0) {
                for (int i = 0; i < size(); ++i) {
                    parentFields.markUsed(getValue(i));
                }
            }
            if ((writeFlags & WRITE_WIDGET) != 0) {
                for (int i = 0; i < size(); ++i) {
                    parentFields.markUsed(getWidget(i));
                }
            }
        }

        /**
         * Preferred method of determining the number of instances
         * of a given field.
         *
         * @return number of instances
         * @since 2.1.5
         */
        public int size() {
            return values.size();
        }

        /**
         * Remove the given instance from this item.  It is possible to
         * remove all instances using this function.
         *
         * @param killIdx
         * @since 2.1.5
         */
        void remove(int killIdx) {
            values.remove(killIdx);
            widgets.remove(killIdx);
            widget_refs.remove(killIdx);
            merged.remove(killIdx);
            page.remove(killIdx);
            tabOrder.remove(killIdx);
        }

        /**
         * Retrieve the value dictionary of the given instance
         *
         * @param idx instance index
         * @return dictionary storing this instance's value.  It may be shared across instances.
         * @since 2.1.5
         */
        public PdfDictionary getValue(int idx) {
            return values.get(idx);
        }

        /**
         * Add a value dict to this Item
         *
         * @param value new value dictionary
         * @since 2.1.5
         */
        void addValue(PdfDictionary value) {
            values.add(value);
        }

        /**
         * Retrieve the widget dictionary of the given instance
         *
         * @param idx instance index
         * @return The dictionary found in the appropriate page's Annot array.
         * @since 2.1.5
         */
        public PdfDictionary getWidget(int idx) {
            return widgets.get(idx);
        }

        /**
         * Add a widget dict to this Item
         *
         * @param widget
         * @since 2.1.5
         */
        void addWidget(PdfDictionary widget) {
            widgets.add(widget);
        }

        /**
         * Retrieve the reference to the given instance
         *
         * @param idx instance index
         * @return reference to the given field instance
         * @since 2.1.5
         */
        public PdfIndirectReference getWidgetRef(int idx) {
            return widget_refs.get(idx);
        }

        /**
         * Add a widget ref to this Item
         *
         * @param widgRef
         * @since 2.1.5
         */
        void addWidgetRef(PdfIndirectReference widgRef) {
            widget_refs.add(widgRef);
        }

        /**
         * Retrieve the merged dictionary for the given instance.  The merged
         * dictionary contains all the keys present in parent fields, though they
         * may have been overwritten (or modified?) by children.
         * Example: a merged radio field dict will contain /V
         *
         * @param idx instance index
         * @return the merged dictionary for the given instance
         * @since 2.1.5
         */
        public PdfDictionary getMerged(int idx) {
            return merged.get(idx);
        }

        /**
         * Adds a merged dictionary to this Item.
         *
         * @param mergeDict
         * @since 2.1.5
         */
        void addMerged(PdfDictionary mergeDict) {
            merged.add(mergeDict);
        }

        /**
         * Retrieve the page number of the given instance
         *
         * @param idx
         * @return remember, pages are "1-indexed", not "0-indexed" like field instances.
         * @since 2.1.5
         */
        public Integer getPage(int idx) {
            return page.get(idx);
        }

        /**
         * Adds a page to the current Item.
         *
         * @param pg
         * @since 2.1.5
         */
        void addPage(int pg) {
            page.add(Integer.valueOf(pg));
        }

        /**
         * forces a page value into the Item.
         *
         * @param idx
         * @since 2.1.5
         */
        void forcePage(int idx, int pg) {
            page.set(idx, Integer.valueOf(pg));
        }

        /**
         * Gets the tabOrder.
         *
         * @param idx
         * @return tab index of the given field instance
         * @since 2.1.5
         */
        public Integer getTabOrder(int idx) {
            return tabOrder.get(idx);
        }

        /**
         * Adds a tab order value to this Item.
         *
         * @param order
         * @since 2.1.5
         */
        void addTabOrder(int order) {
            tabOrder.add(Integer.valueOf(order));
        }
    }

    private static class InstHit {
        IntHashtable hits;

        public InstHit(int inst[]) {
            if (inst == null)
                return;
            hits = new IntHashtable();
            for (int k = 0; k < inst.length; ++k)
                hits.put(inst[k], 1);
        }

        public boolean isHit(int n) {
            if (hits == null)
                return true;
            return hits.containsKey(n);
        }
    }

    /**
     * A class representing a field position
     *
     * @since 5.0.2
     */
    public static class FieldPosition {
        public int page;
        public Rectangle position;
    }

    private static class SorterComparator implements Comparator<Object[]> {
        public int compare(Object[] o1, Object[] o2) {
            int n1 = ((int[]) o1[1])[0];
            int n2 = ((int[]) o2[1])[0];
            return n1 - n2;
        }
    }

    private static class ContentsChecker extends PdfReader {

        private long contentsStart;
        private long contentsEnd;

        private int currentLevel = 0;
        private int contentsLevel = 1;
        private boolean searchInV = true;

        private boolean rangeIsCorrect = false;

        public ContentsChecker(RandomAccessFileOrArray raf) throws IOException {
            super(raf, null);
        }

        public boolean checkWhetherSignatureCoversWholeDocument(Item signatureField) {
            rangeIsCorrect = false;
            PdfDictionary signature;
            int objNum;
            if (signatureField.getValue(0).get(PdfName.V) instanceof PRIndirectReference) {
                objNum = ((PdfIndirectReference) signatureField.getValue(0).get(PdfName.V)).number;
                signature = (PdfDictionary) getPdfObject(objNum);
                searchInV = true;
            } else {
                signature = (PdfDictionary) signatureField.getValue(0).get(PdfName.V);
                objNum = signatureField.getWidgetRef(0).number;
                searchInV = false;
                contentsLevel++;
            }

            long[] byteRange = ((PdfArray) signature.get(PdfName.BYTERANGE)).asLongArray();
            if (4 != byteRange.length || 0 != byteRange[0] || getFileLength() != byteRange[2] + byteRange[3]) {
                return false;
            }

            contentsStart = byteRange[1];
            contentsEnd = byteRange[2];

            long signatureOffset = xref[2 * objNum];
            try {
                tokens.seek(signatureOffset);
                tokens.nextValidToken(); // number
                tokens.nextValidToken(); // revision
                tokens.nextValidToken(); // obj
                readPRObject();
            } catch (Exception e) {
                // That's not expected because if the signature is invalid, it should have already failed
                return false;
            }
            return rangeIsCorrect;
        }

        @Override
        protected PdfDictionary readDictionary() throws IOException {
            currentLevel++;
            PdfDictionary dic = new PdfDictionary();
            while (!rangeIsCorrect) {
                tokens.nextValidToken();
                if (tokens.getTokenType() == TokenType.END_DIC) {
                    currentLevel--;
                    break;
                }
                if (tokens.getTokenType() != TokenType.NAME) {
                    tokens.throwError(MessageLocalization.getComposedMessage("dictionary.key.1.is.not.a.name", tokens.getStringValue()));
                }
                PdfName name = new PdfName(tokens.getStringValue(), false);
                PdfObject obj;
                if (PdfName.CONTENTS.equals(name) && searchInV && contentsLevel == currentLevel) {
                    long startPosition = tokens.getFilePointer();
                    int ch;
                    int whiteSpacesCount = -1;
                    do {
                        ch = tokens.read();
                        whiteSpacesCount++;
                    } while (ch != -1 && PRTokeniser.isWhitespace(ch));
                    tokens.seek(startPosition);
                    obj = readPRObject();
                    long endPosition = tokens.getFilePointer();
                    if (endPosition == contentsEnd && startPosition + whiteSpacesCount == contentsStart) {
                        rangeIsCorrect = true;
                    }
                } else if (PdfName.V.equals(name) && !searchInV && 1 == currentLevel) {
                    searchInV = true;
                    obj = readPRObject();
                    searchInV = false;
                } else {
                    obj = readPRObject();
                }
                int type = obj.type();
                if (-type == TokenType.END_DIC.ordinal())
                    tokens.throwError(MessageLocalization.getComposedMessage("unexpected.gt.gt"));
                if (-type == TokenType.END_ARRAY.ordinal())
                    tokens.throwError(MessageLocalization.getComposedMessage("unexpected.close.bracket"));
                dic.put(name, obj);
            }
            return dic;
        }
    }
}

com/itextpdf/text/pdf/AcroFields.java

 

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

⇐ iText layout.jar Source Code

⇑ Download and Install iText Java Library

⇑⇑ iText for PDF Generation

2021-07-03, 47740👍, 0💬