001/*
002 * $Id: AcroFields.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import java.io.IOException;
047import java.io.InputStream;
048import java.util.ArrayList;
049import java.util.Collections;
050import java.util.Comparator;
051import java.util.HashMap;
052import java.util.HashSet;
053import java.util.Iterator;
054import java.util.List;
055import java.util.Map;
056
057import org.w3c.dom.Node;
058
059import com.itextpdf.text.BaseColor;
060import com.itextpdf.text.DocumentException;
061import com.itextpdf.text.Element;
062import com.itextpdf.text.ExceptionConverter;
063import com.itextpdf.text.Image;
064import com.itextpdf.text.Rectangle;
065import com.itextpdf.text.error_messages.MessageLocalization;
066import com.itextpdf.text.pdf.PRTokeniser.TokenType;
067import com.itextpdf.text.pdf.codec.Base64;
068
069/**
070 * Query and change fields in existing documents either by method
071 * calls or by FDF merging.
072 *
073 * @author Paulo Soares
074 */
075public class AcroFields {
076
077    PdfReader reader;
078    PdfWriter writer;
079    Map<String, Item> fields;
080    private int topFirst;
081    private HashMap<String, int[]> sigNames;
082    private boolean append;
083    public static final int DA_FONT = 0;
084    public static final int DA_SIZE = 1;
085    public static final int DA_COLOR = 2;
086    private HashMap<Integer, BaseFont> extensionFonts = new HashMap<Integer, BaseFont>();
087    private XfaForm xfa;
088
089    /**
090     * A field type invalid or not found.
091     */
092    public static final int FIELD_TYPE_NONE = 0;
093
094    /**
095     * A field type.
096     */
097    public static final int FIELD_TYPE_PUSHBUTTON = 1;
098
099    /**
100     * A field type.
101     */
102    public static final int FIELD_TYPE_CHECKBOX = 2;
103
104    /**
105     * A field type.
106     */
107    public static final int FIELD_TYPE_RADIOBUTTON = 3;
108
109    /**
110     * A field type.
111     */
112    public static final int FIELD_TYPE_TEXT = 4;
113
114    /**
115     * A field type.
116     */
117    public static final int FIELD_TYPE_LIST = 5;
118
119    /**
120     * A field type.
121     */
122    public static final int FIELD_TYPE_COMBO = 6;
123
124    /**
125     * A field type.
126     */
127    public static final int FIELD_TYPE_SIGNATURE = 7;
128
129    private boolean lastWasString;
130
131    /** Holds value of property generateAppearances. */
132    private boolean generateAppearances = true;
133
134    private HashMap<String, BaseFont> localFonts = new HashMap<String, BaseFont>();
135
136    private float extraMarginLeft;
137    private float extraMarginTop;
138    private ArrayList<BaseFont> substitutionFonts;
139
140    AcroFields(PdfReader reader, PdfWriter writer) {
141        this.reader = reader;
142        this.writer = writer;
143        try {
144            xfa = new XfaForm(reader);
145        }
146        catch (Exception e) {
147            throw new ExceptionConverter(e);
148        }
149        if (writer instanceof PdfStamperImp) {
150            append = ((PdfStamperImp)writer).isAppend();
151        }
152        fill();
153    }
154
155    void fill() {
156        fields = new HashMap<String, Item>();
157        PdfDictionary top = (PdfDictionary)PdfReader.getPdfObjectRelease(reader.getCatalog().get(PdfName.ACROFORM));
158        if (top == null)
159            return;
160        PdfArray arrfds = (PdfArray)PdfReader.getPdfObjectRelease(top.get(PdfName.FIELDS));
161        if (arrfds == null || arrfds.size() == 0)
162            return;
163        for (int k = 1; k <= reader.getNumberOfPages(); ++k) {
164            PdfDictionary page = reader.getPageNRelease(k);
165            PdfArray annots = (PdfArray)PdfReader.getPdfObjectRelease(page.get(PdfName.ANNOTS), page);
166            if (annots == null)
167                continue;
168            for (int j = 0; j < annots.size(); ++j) {
169                PdfDictionary annot = annots.getAsDict(j);
170                if (annot == null) {
171                    PdfReader.releaseLastXrefPartial(annots.getAsIndirectObject(j));
172                    continue;
173                }
174                if (!PdfName.WIDGET.equals(annot.getAsName(PdfName.SUBTYPE))) {
175                    PdfReader.releaseLastXrefPartial(annots.getAsIndirectObject(j));
176                    continue;
177                }
178                PdfDictionary widget = annot;
179                PdfDictionary dic = new PdfDictionary();
180                dic.putAll(annot);
181                String name = "";
182                PdfDictionary value = null;
183                PdfObject lastV = null;
184                while (annot != null) {
185                    dic.mergeDifferent(annot);
186                    PdfString t = annot.getAsString(PdfName.T);
187                    if (t != null)
188                        name = t.toUnicodeString() + "." + name;
189                    if (lastV == null && annot.get(PdfName.V) != null)
190                        lastV = PdfReader.getPdfObjectRelease(annot.get(PdfName.V));
191                    if (value == null &&  t != null) {
192                        value = annot;
193                        if (annot.get(PdfName.V) == null && lastV  != null)
194                            value.put(PdfName.V, lastV);
195                    }
196                    annot = annot.getAsDict(PdfName.PARENT);
197                }
198                if (name.length() > 0)
199                    name = name.substring(0, name.length() - 1);
200                Item item = fields.get(name);
201                if (item == null) {
202                    item = new Item();
203                    fields.put(name, item);
204                }
205                if (value == null)
206                    item.addValue(widget);
207                else
208                    item.addValue(value);
209                item.addWidget(widget);
210                item.addWidgetRef(annots.getAsIndirectObject(j)); // must be a reference
211                if (top != null)
212                    dic.mergeDifferent(top);
213                item.addMerged(dic);
214                item.addPage(k);
215                item.addTabOrder(j);
216            }
217        }
218        // some tools produce invisible signatures without an entry in the page annotation array
219        // look for a single level annotation
220        PdfNumber sigFlags = top.getAsNumber(PdfName.SIGFLAGS);
221        if (sigFlags == null || (sigFlags.intValue() & 1) != 1)
222            return;
223        for (int j = 0; j < arrfds.size(); ++j) {
224            PdfDictionary annot = arrfds.getAsDict(j);
225            if (annot == null) {
226                PdfReader.releaseLastXrefPartial(arrfds.getAsIndirectObject(j));
227                continue;
228            }
229            if (!PdfName.WIDGET.equals(annot.getAsName(PdfName.SUBTYPE))) {
230                PdfReader.releaseLastXrefPartial(arrfds.getAsIndirectObject(j));
231                continue;
232            }
233            PdfArray kids = (PdfArray)PdfReader.getPdfObjectRelease(annot.get(PdfName.KIDS));
234            if (kids != null)
235                continue;
236            PdfDictionary dic = new PdfDictionary();
237            dic.putAll(annot);
238            PdfString t = annot.getAsString(PdfName.T);
239            if (t == null)
240                continue;
241            String name = t.toUnicodeString();
242            if (fields.containsKey(name))
243                continue;
244            Item item = new Item();
245            fields.put(name, item);
246            item.addValue(dic);
247            item.addWidget(dic);
248            item.addWidgetRef(arrfds.getAsIndirectObject(j)); // must be a reference
249            item.addMerged(dic);
250            item.addPage(-1);
251            item.addTabOrder(-1);
252        }
253    }
254
255    /**
256     * Gets the list of appearance names. Use it to get the names allowed
257     * with radio and checkbox fields. If the /Opt key exists the values will
258     * also be included. The name 'Off' may also be valid
259     * even if not returned in the list.
260     *
261     * @param fieldName the fully qualified field name
262     * @return the list of names or <CODE>null</CODE> if the field does not exist
263     */
264    public String[] getAppearanceStates(String fieldName) {
265        Item fd = fields.get(fieldName);
266        if (fd == null)
267            return null;
268        HashSet<String> names = new HashSet<String>();
269        PdfDictionary vals = fd.getValue(0);
270        PdfString stringOpt = vals.getAsString( PdfName.OPT );
271        if (stringOpt != null) {
272                names.add(stringOpt.toUnicodeString());
273        }
274        else {
275            PdfArray arrayOpt = vals.getAsArray(PdfName.OPT);
276            if (arrayOpt != null) {
277                for (int k = 0; k < arrayOpt.size(); ++k) {
278                        PdfString valStr = arrayOpt.getAsString( k );
279                        if (valStr != null)
280                                names.add(valStr.toUnicodeString());
281                }
282            }
283        }
284        for (int k = 0; k < fd.size(); ++k) {
285            PdfDictionary dic = fd.getWidget( k );
286            dic = dic.getAsDict(PdfName.AP);
287            if (dic == null)
288                continue;
289            dic = dic.getAsDict(PdfName.N);
290            if (dic == null)
291                continue;
292            for (Object element : dic.getKeys()) {
293                String name = PdfName.decodeName(((PdfName)element).toString());
294                names.add(name);
295            }
296        }
297        String out[] = new String[names.size()];
298        return names.toArray(out);
299    }
300
301    private String[] getListOption(String fieldName, int idx) {
302        Item fd = getFieldItem(fieldName);
303        if (fd == null)
304            return null;
305        PdfArray ar = fd.getMerged(0).getAsArray(PdfName.OPT);
306        if (ar == null)
307            return null;
308        String[] ret = new String[ar.size()];
309        for (int k = 0; k < ar.size(); ++k) {
310            PdfObject obj = ar.getDirectObject( k );
311            try {
312                if (obj.isArray()) {
313                    obj = ((PdfArray)obj).getDirectObject(idx);
314                }
315                if (obj.isString())
316                    ret[k] = ((PdfString)obj).toUnicodeString();
317                else
318                    ret[k] = obj.toString();
319            }
320            catch (Exception e) {
321                ret[k] = "";
322            }
323        }
324        return ret;
325    }
326
327    /**
328     * Gets the list of export option values from fields of type list or combo.
329     * If the field doesn't exist or the field type is not list or combo it will return
330     * <CODE>null</CODE>.
331     *
332     * @param fieldName the field name
333     * @return the list of export option values from fields of type list or combo
334     */
335    public String[] getListOptionExport(String fieldName) {
336        return getListOption(fieldName, 0);
337    }
338
339    /**
340     * Gets the list of display option values from fields of type list or combo.
341     * If the field doesn't exist or the field type is not list or combo it will return
342     * <CODE>null</CODE>.
343     *
344     * @param fieldName the field name
345     * @return the list of export option values from fields of type list or combo
346     */
347    public String[] getListOptionDisplay(String fieldName) {
348        return getListOption(fieldName, 1);
349    }
350
351    /**
352     * Sets the option list for fields of type list or combo. One of <CODE>exportValues</CODE>
353     * or <CODE>displayValues</CODE> may be <CODE>null</CODE> but not both. This method will only
354     * set the list but will not set the value or appearance. For that, calling <CODE>setField()</CODE>
355     * is required.
356     * <p>
357     * An example:
358     * <p>
359     * <PRE>
360     * PdfReader pdf = new PdfReader("input.pdf");
361     * PdfStamper stp = new PdfStamper(pdf, new FileOutputStream("output.pdf"));
362     * AcroFields af = stp.getAcroFields();
363     * af.setListOption("ComboBox", new String[]{"a", "b", "c"}, new String[]{"first", "second", "third"});
364     * af.setField("ComboBox", "b");
365     * stp.close();
366     * </PRE>
367     *
368     * @param fieldName the field name
369     * @param exportValues the export values
370     * @param displayValues the display values
371     * @return <CODE>true</CODE> if the operation succeeded, <CODE>false</CODE> otherwise
372     */
373    public boolean setListOption(String fieldName, String[] exportValues, String[] displayValues) {
374        if (exportValues == null && displayValues == null)
375            return false;
376        if (exportValues != null && displayValues != null && exportValues.length != displayValues.length)
377            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("the.export.and.the.display.array.must.have.the.same.size"));
378        int ftype = getFieldType(fieldName);
379        if (ftype != FIELD_TYPE_COMBO && ftype != FIELD_TYPE_LIST)
380            return false;
381        Item fd = fields.get(fieldName);
382        String[] sing = null;
383        if (exportValues == null && displayValues != null)
384            sing = displayValues;
385        else if (exportValues != null && displayValues == null)
386            sing = exportValues;
387        PdfArray opt = new PdfArray();
388        if (sing != null) {
389            for (int k = 0; k < sing.length; ++k)
390                opt.add(new PdfString(sing[k], PdfObject.TEXT_UNICODE));
391        }
392        else {
393            for (int k = 0; k < exportValues.length; ++k) {
394                PdfArray a = new PdfArray();
395                a.add(new PdfString(exportValues[k], PdfObject.TEXT_UNICODE));
396                a.add(new PdfString(displayValues[k], PdfObject.TEXT_UNICODE));
397                opt.add(a);
398            }
399        }
400        fd.writeToAll( PdfName.OPT, opt, Item.WRITE_VALUE | Item.WRITE_MERGED );
401        return true;
402    }
403
404    /**
405     * Gets the field type. The type can be one of: <CODE>FIELD_TYPE_PUSHBUTTON</CODE>,
406     * <CODE>FIELD_TYPE_CHECKBOX</CODE>, <CODE>FIELD_TYPE_RADIOBUTTON</CODE>,
407     * <CODE>FIELD_TYPE_TEXT</CODE>, <CODE>FIELD_TYPE_LIST</CODE>,
408     * <CODE>FIELD_TYPE_COMBO</CODE> or <CODE>FIELD_TYPE_SIGNATURE</CODE>.
409     * <p>
410     * If the field does not exist or is invalid it returns
411     * <CODE>FIELD_TYPE_NONE</CODE>.
412     *
413     * @param fieldName the field name
414     * @return the field type
415     */
416    public int getFieldType(String fieldName) {
417        Item fd = getFieldItem(fieldName);
418        if (fd == null)
419            return FIELD_TYPE_NONE;
420        PdfDictionary merged = fd.getMerged( 0 );
421        PdfName type = merged.getAsName(PdfName.FT);
422        if (type == null)
423            return FIELD_TYPE_NONE;
424        int ff = 0;
425        PdfNumber ffo = merged.getAsNumber(PdfName.FF);
426        if (ffo != null) {
427            ff = ffo.intValue();
428        }
429        if (PdfName.BTN.equals(type)) {
430            if ((ff & PdfFormField.FF_PUSHBUTTON) != 0)
431                return FIELD_TYPE_PUSHBUTTON;
432            if ((ff & PdfFormField.FF_RADIO) != 0)
433                return FIELD_TYPE_RADIOBUTTON;
434            else
435                return FIELD_TYPE_CHECKBOX;
436        }
437        else if (PdfName.TX.equals(type)) {
438            return FIELD_TYPE_TEXT;
439        }
440        else if (PdfName.CH.equals(type)) {
441            if ((ff & PdfFormField.FF_COMBO) != 0)
442                return FIELD_TYPE_COMBO;
443            else
444                return FIELD_TYPE_LIST;
445        }
446        else if (PdfName.SIG.equals(type)) {
447            return FIELD_TYPE_SIGNATURE;
448        }
449        return FIELD_TYPE_NONE;
450    }
451
452    /**
453     * Export the fields as a FDF.
454     *
455     * @param writer the FDF writer
456     */
457    public void exportAsFdf(FdfWriter writer) {
458        for (Map.Entry<String, Item> entry : fields.entrySet()) {
459            Item item = entry.getValue();
460            String name = entry.getKey();
461            PdfObject v = item.getMerged(0).get(PdfName.V);
462            if (v == null)
463                continue;
464            String value = getField(name);
465            if (lastWasString)
466                writer.setFieldAsString(name, value);
467            else
468                writer.setFieldAsName(name, value);
469        }
470    }
471
472    /**
473     * Renames a field. Only the last part of the name can be renamed. For example,
474     * if the original field is "ab.cd.ef" only the "ef" part can be renamed.
475     *
476     * @param oldName the old field name
477     * @param newName the new field name
478     * @return <CODE>true</CODE> if the renaming was successful, <CODE>false</CODE>
479     * otherwise
480     */
481    public boolean renameField(String oldName, String newName) {
482        int idx1 = oldName.lastIndexOf('.') + 1;
483        int idx2 = newName.lastIndexOf('.') + 1;
484        if (idx1 != idx2)
485            return false;
486        if (!oldName.substring(0, idx1).equals(newName.substring(0, idx2)))
487            return false;
488        if (fields.containsKey(newName))
489            return false;
490        Item item = fields.get(oldName);
491        if (item == null)
492            return false;
493        newName = newName.substring(idx2);
494        PdfString ss = new PdfString(newName, PdfObject.TEXT_UNICODE);
495
496        item.writeToAll( PdfName.T, ss, Item.WRITE_VALUE | Item.WRITE_MERGED);
497        item.markUsed( this, Item.WRITE_VALUE );
498
499        fields.remove(oldName);
500        fields.put(newName, item);
501
502        return true;
503    }
504
505    public static Object[] splitDAelements(String da) {
506        try {
507            PRTokeniser tk = new PRTokeniser(PdfEncodings.convertToBytes(da, null));
508            ArrayList<String> stack = new ArrayList<String>();
509            Object ret[] = new Object[3];
510            while (tk.nextToken()) {
511                if (tk.getTokenType() == TokenType.COMMENT)
512                    continue;
513                if (tk.getTokenType() == TokenType.OTHER) {
514                    String operator = tk.getStringValue();
515                    if (operator.equals("Tf")) {
516                        if (stack.size() >= 2) {
517                            ret[DA_FONT] = stack.get(stack.size() - 2);
518                            ret[DA_SIZE] = new Float(stack.get(stack.size() - 1));
519                        }
520                    }
521                    else if (operator.equals("g")) {
522                        if (stack.size() >= 1) {
523                            float gray = new Float(stack.get(stack.size() - 1)).floatValue();
524                            if (gray != 0)
525                                ret[DA_COLOR] = new GrayColor(gray);
526                        }
527                    }
528                    else if (operator.equals("rg")) {
529                        if (stack.size() >= 3) {
530                            float red = new Float(stack.get(stack.size() - 3)).floatValue();
531                            float green = new Float(stack.get(stack.size() - 2)).floatValue();
532                            float blue = new Float(stack.get(stack.size() - 1)).floatValue();
533                            ret[DA_COLOR] = new BaseColor(red, green, blue);
534                        }
535                    }
536                    else if (operator.equals("k")) {
537                        if (stack.size() >= 4) {
538                            float cyan = new Float(stack.get(stack.size() - 4)).floatValue();
539                            float magenta = new Float(stack.get(stack.size() - 3)).floatValue();
540                            float yellow = new Float(stack.get(stack.size() - 2)).floatValue();
541                            float black = new Float(stack.get(stack.size() - 1)).floatValue();
542                            ret[DA_COLOR] = new CMYKColor(cyan, magenta, yellow, black);
543                        }
544                    }
545                    stack.clear();
546                }
547                else
548                    stack.add(tk.getStringValue());
549            }
550            return ret;
551        }
552        catch (IOException ioe) {
553            throw new ExceptionConverter(ioe);
554        }
555    }
556
557    public void decodeGenericDictionary(PdfDictionary merged, BaseField tx) throws IOException, DocumentException {
558        int flags = 0;
559        // the text size and color
560        PdfString da = merged.getAsString(PdfName.DA);
561        if (da != null) {
562            Object dab[] = splitDAelements(da.toUnicodeString());
563            if (dab[DA_SIZE] != null)
564                tx.setFontSize(((Float)dab[DA_SIZE]).floatValue());
565            if (dab[DA_COLOR] != null)
566                tx.setTextColor((BaseColor)dab[DA_COLOR]);
567            if (dab[DA_FONT] != null) {
568                PdfDictionary font = merged.getAsDict(PdfName.DR);
569                if (font != null) {
570                    font = font.getAsDict(PdfName.FONT);
571                    if (font != null) {
572                        PdfObject po = font.get(new PdfName((String)dab[DA_FONT]));
573                        if (po != null && po.type() == PdfObject.INDIRECT) {
574                            PRIndirectReference por = (PRIndirectReference)po;
575                            BaseFont bp = new DocumentFont((PRIndirectReference)po);
576                            tx.setFont(bp);
577                            Integer porkey = Integer.valueOf(por.getNumber());
578                            BaseFont porf = extensionFonts.get(porkey);
579                            if (porf == null) {
580                                if (!extensionFonts.containsKey(porkey)) {
581                                    PdfDictionary fo = (PdfDictionary)PdfReader.getPdfObject(po);
582                                    PdfDictionary fd = fo.getAsDict(PdfName.FONTDESCRIPTOR);
583                                    if (fd != null) {
584                                        PRStream prs = (PRStream)PdfReader.getPdfObject(fd.get(PdfName.FONTFILE2));
585                                        if (prs == null)
586                                            prs = (PRStream)PdfReader.getPdfObject(fd.get(PdfName.FONTFILE3));
587                                        if (prs == null) {
588                                            extensionFonts.put(porkey, null);
589                                        }
590                                        else {
591                                            try {
592                                                porf = BaseFont.createFont("font.ttf", BaseFont.IDENTITY_H, true, false, PdfReader.getStreamBytes(prs), null);
593                                            }
594                                            catch (Exception e) {
595                                            }
596                                            extensionFonts.put(porkey, porf);
597                                        }
598                                    }
599                                }
600                            }
601                            if (tx instanceof TextField)
602                                ((TextField)tx).setExtensionFont(porf);
603                        }
604                        else {
605                            BaseFont bf = localFonts.get(dab[DA_FONT]);
606                            if (bf == null) {
607                                String fn[] = stdFieldFontNames.get(dab[DA_FONT]);
608                                if (fn != null) {
609                                    try {
610                                        String enc = "winansi";
611                                        if (fn.length > 1)
612                                            enc = fn[1];
613                                        bf = BaseFont.createFont(fn[0], enc, false);
614                                        tx.setFont(bf);
615                                    }
616                                    catch (Exception e) {
617                                        // empty
618                                    }
619                                }
620                            }
621                            else
622                                tx.setFont(bf);
623                        }
624                    }
625                }
626            }
627        }
628        //rotation, border and background color
629        PdfDictionary mk = merged.getAsDict(PdfName.MK);
630        if (mk != null) {
631            PdfArray ar = mk.getAsArray(PdfName.BC);
632            BaseColor border = getMKColor(ar);
633            tx.setBorderColor(border);
634            if (border != null)
635                tx.setBorderWidth(1);
636            ar = mk.getAsArray(PdfName.BG);
637            tx.setBackgroundColor(getMKColor(ar));
638            PdfNumber rotation = mk.getAsNumber(PdfName.R);
639            if (rotation != null)
640                tx.setRotation(rotation.intValue());
641        }
642        //flags
643        PdfNumber nfl = merged.getAsNumber(PdfName.F);
644        flags = 0;
645        tx.setVisibility(BaseField.VISIBLE_BUT_DOES_NOT_PRINT);
646        if (nfl != null) {
647            flags = nfl.intValue();
648            if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) != 0)
649                tx.setVisibility(BaseField.HIDDEN);
650            else if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_NOVIEW) != 0)
651                tx.setVisibility(BaseField.HIDDEN_BUT_PRINTABLE);
652            else if ((flags & PdfFormField.FLAGS_PRINT) != 0)
653                tx.setVisibility(BaseField.VISIBLE);
654        }
655        //multiline
656        nfl = merged.getAsNumber(PdfName.FF);
657        flags = 0;
658        if (nfl != null)
659            flags = nfl.intValue();
660        tx.setOptions(flags);
661        if ((flags & PdfFormField.FF_COMB) != 0) {
662            PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN);
663            int len = 0;
664            if (maxLen != null)
665                len = maxLen.intValue();
666            tx.setMaxCharacterLength(len);
667        }
668        //alignment
669        nfl = merged.getAsNumber(PdfName.Q);
670        if (nfl != null) {
671            if (nfl.intValue() == PdfFormField.Q_CENTER)
672                tx.setAlignment(Element.ALIGN_CENTER);
673            else if (nfl.intValue() == PdfFormField.Q_RIGHT)
674                tx.setAlignment(Element.ALIGN_RIGHT);
675        }
676        //border styles
677        PdfDictionary bs = merged.getAsDict(PdfName.BS);
678        if (bs != null) {
679            PdfNumber w = bs.getAsNumber(PdfName.W);
680            if (w != null)
681                tx.setBorderWidth(w.floatValue());
682            PdfName s = bs.getAsName(PdfName.S);
683            if (PdfName.D.equals(s))
684                tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
685            else if (PdfName.B.equals(s))
686                tx.setBorderStyle(PdfBorderDictionary.STYLE_BEVELED);
687            else if (PdfName.I.equals(s))
688                tx.setBorderStyle(PdfBorderDictionary.STYLE_INSET);
689            else if (PdfName.U.equals(s))
690                tx.setBorderStyle(PdfBorderDictionary.STYLE_UNDERLINE);
691        }
692        else {
693            PdfArray bd = merged.getAsArray(PdfName.BORDER);
694            if (bd != null) {
695                if (bd.size() >= 3)
696                    tx.setBorderWidth(bd.getAsNumber(2).floatValue());
697                if (bd.size() >= 4)
698                    tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
699            }
700        }
701    }
702
703    PdfAppearance getAppearance(PdfDictionary merged, String values[], String fieldName) throws IOException, DocumentException {
704        topFirst = 0;
705        String text = values.length > 0 ? values[0] : null;
706
707        TextField tx = null;
708        if (fieldCache == null || !fieldCache.containsKey(fieldName)) {
709            tx = new TextField(writer, null, null);
710            tx.setExtraMargin(extraMarginLeft, extraMarginTop);
711            tx.setBorderWidth(0);
712            tx.setSubstitutionFonts(substitutionFonts);
713            decodeGenericDictionary(merged, tx);
714            //rect
715            PdfArray rect = merged.getAsArray(PdfName.RECT);
716            Rectangle box = PdfReader.getNormalizedRectangle(rect);
717            if (tx.getRotation() == 90 || tx.getRotation() == 270)
718                box = box.rotate();
719            tx.setBox(box);
720            if (fieldCache != null)
721                fieldCache.put(fieldName, tx);
722        }
723        else {
724            tx = fieldCache.get(fieldName);
725            tx.setWriter(writer);
726        }
727        PdfName fieldType = merged.getAsName(PdfName.FT);
728        if (PdfName.TX.equals(fieldType)) {
729            if (values.length > 0 && values[0] != null) {
730                tx.setText(values[0]);
731            }
732            return tx.getAppearance();
733        }
734        if (!PdfName.CH.equals(fieldType))
735            throw new DocumentException(MessageLocalization.getComposedMessage("an.appearance.was.requested.without.a.variable.text.field"));
736        PdfArray opt = merged.getAsArray(PdfName.OPT);
737        int flags = 0;
738        PdfNumber nfl = merged.getAsNumber(PdfName.FF);
739        if (nfl != null)
740            flags = nfl.intValue();
741        if ((flags & PdfFormField.FF_COMBO) != 0 && opt == null) {
742            tx.setText(text);
743            return tx.getAppearance();
744        }
745        if (opt != null) {
746            String choices[] = new String[opt.size()];
747            String choicesExp[] = new String[opt.size()];
748            for (int k = 0; k < opt.size(); ++k) {
749                PdfObject obj = opt.getPdfObject(k);
750                if (obj.isString()) {
751                    choices[k] = choicesExp[k] = ((PdfString)obj).toUnicodeString();
752                }
753                else {
754                    PdfArray a = (PdfArray) obj;
755                    choicesExp[k] = a.getAsString(0).toUnicodeString();
756                    choices[k] = a.getAsString(1).toUnicodeString();
757                }
758            }
759            if ((flags & PdfFormField.FF_COMBO) != 0) {
760                for (int k = 0; k < choices.length; ++k) {
761                    if (text.equals(choicesExp[k])) {
762                        text = choices[k];
763                        break;
764                    }
765                }
766                tx.setText(text);
767                return tx.getAppearance();
768            }
769            ArrayList<Integer> indexes = new ArrayList<Integer>();
770            for (int k = 0; k < choicesExp.length; ++k) {
771                for (int j = 0; j < values.length; ++j) {
772                        String val = values[j];
773                        if (val != null && val.equals(choicesExp[k])) {
774                                indexes.add( Integer.valueOf( k ) );
775                                break;
776                        }
777                }
778            }
779            tx.setChoices(choices);
780            tx.setChoiceExports(choicesExp);
781            tx.setChoiceSelections( indexes );
782        }
783        PdfAppearance app = tx.getListAppearance();
784        topFirst = tx.getTopFirst();
785        return app;
786    }
787
788    PdfAppearance getAppearance(PdfDictionary merged, String text, String fieldName) throws IOException, DocumentException {
789      String valueArr[] = new String[1];
790      valueArr[0] = text;
791      return getAppearance( merged, valueArr, fieldName );
792    }
793
794    BaseColor getMKColor(PdfArray ar) {
795        if (ar == null)
796            return null;
797        switch (ar.size()) {
798            case 1:
799                return new GrayColor(ar.getAsNumber(0).floatValue());
800            case 3:
801                return new BaseColor(ExtendedColor.normalize(ar.getAsNumber(0).floatValue()), ExtendedColor.normalize(ar.getAsNumber(1).floatValue()), ExtendedColor.normalize(ar.getAsNumber(2).floatValue()));
802            case 4:
803                return new CMYKColor(ar.getAsNumber(0).floatValue(), ar.getAsNumber(1).floatValue(), ar.getAsNumber(2).floatValue(), ar.getAsNumber(3).floatValue());
804            default:
805                return null;
806        }
807    }
808
809    /**
810     * Retrieve the rich value for the given field
811     * @param name
812     * @return The rich value if present, or null.
813     * @since 5.0.6
814     */
815    public String getFieldRichValue(String name) {
816        if (xfa.isXfaPresent()) {
817                return null;
818        }
819
820        Item item = fields.get(name);
821        if (item == null) { 
822                return null;
823        }
824
825        PdfDictionary merged = item.getMerged(0);
826        PdfString rich = merged.getAsString(PdfName.RV);
827        
828        String markup = null;
829        if (rich != null) {
830                markup = rich.toString();
831        }
832
833        return markup;
834    }
835    /**
836     * Gets the field value.
837     *
838     * @param name the fully qualified field name
839     * @return the field value
840     */
841    public String getField(String name) {
842        if (xfa.isXfaPresent()) {
843            name = xfa.findFieldName(name, this);
844            if (name == null)
845                return null;
846            name = XfaForm.Xml2Som.getShortName(name);
847            return XfaForm.getNodeText(xfa.findDatasetsNode(name));
848        }
849        Item item = fields.get(name);
850        if (item == null)
851            return null;
852        lastWasString = false;
853        PdfDictionary mergedDict = item.getMerged( 0 );
854
855        // Jose A. Rodriguez posted a fix to the mailing list (May 11, 2009)
856        // explaining that the value can also be a stream value
857        // the fix was made against an old iText version. Bruno adapted it.
858        PdfObject v = PdfReader.getPdfObject(mergedDict.get(PdfName.V));
859        if (v == null)
860                return "";
861        if (v instanceof PRStream) {
862                byte[] valBytes;
863                                try {
864                                        valBytes = PdfReader.getStreamBytes((PRStream)v);
865                        return new String(valBytes);
866                                } catch (IOException e) {
867                                        throw new ExceptionConverter(e);
868                                }
869        }
870
871        PdfName type = mergedDict.getAsName(PdfName.FT);
872        if (PdfName.BTN.equals(type)) {
873            PdfNumber ff = mergedDict.getAsNumber(PdfName.FF);
874            int flags = 0;
875            if (ff != null)
876                flags = ff.intValue();
877            if ((flags & PdfFormField.FF_PUSHBUTTON) != 0)
878                return "";
879            String value = "";
880            if (v instanceof PdfName)
881                value = PdfName.decodeName(v.toString());
882            else if (v instanceof PdfString)
883                value = ((PdfString)v).toUnicodeString();
884            PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT);
885            if (opts != null) {
886                int idx = 0;
887                try {
888                    idx = Integer.parseInt(value);
889                    PdfString ps = opts.getAsString(idx);
890                    value = ps.toUnicodeString();
891                    lastWasString = true;
892                }
893                catch (Exception e) {
894                }
895            }
896            return value;
897        }
898        if (v instanceof PdfString) {
899            lastWasString = true;
900            return ((PdfString)v).toUnicodeString();
901        } else if (v instanceof PdfName) {
902            return PdfName.decodeName(v.toString());
903        } else
904            return "";
905    }
906
907    /**
908     * Gets the field values of a Choice field.
909     *
910     * @param name the fully qualified field name
911     * @return the field value
912     * @since 2.1.3
913     */
914    public String[] getListSelection(String name) {
915        String[] ret;
916        String s = getField(name);
917        if (s == null) {
918                ret = new String[]{};
919        }
920        else {
921                ret = new String[]{ s };
922        }
923        Item item = fields.get(name);
924        if (item == null)
925            return ret;
926        //PdfName type = (PdfName)PdfReader.getPdfObject(((PdfDictionary)item.merged.get(0)).get(PdfName.FT));
927        //if (!PdfName.CH.equals(type)) {
928        //      return ret;
929        //}
930        PdfArray values = item.getMerged(0).getAsArray(PdfName.I);
931        if (values == null)
932            return ret;
933        ret = new String[values.size()];
934        String[] options = getListOptionExport(name);
935        PdfNumber n;
936        int idx = 0;
937        for (Iterator<PdfObject> i = values.listIterator(); i.hasNext(); ) {
938                n = (PdfNumber)i.next();
939                ret[idx++] = options[n.intValue()];
940        }
941        return ret;
942    }
943
944
945    /**
946     * Sets a field property. Valid property names are:
947     * <p>
948     * <ul>
949     * <li>textfont - sets the text font. The value for this entry is a <CODE>BaseFont</CODE>.<br>
950     * <li>textcolor - sets the text color. The value for this entry is a <CODE>BaseColor</CODE>.<br>
951     * <li>textsize - sets the text size. The value for this entry is a <CODE>Float</CODE>.
952     * <li>bgcolor - sets the background color. The value for this entry is a <CODE>BaseColor</CODE>.
953     *     If <code>null</code> removes the background.<br>
954     * <li>bordercolor - sets the border color. The value for this entry is a <CODE>BaseColor</CODE>.
955     *     If <code>null</code> removes the border.<br>
956     * </ul>
957     *
958     * @param field the field name
959     * @param name the property name
960     * @param value the property value
961     * @param inst an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process.
962     * Set to <CODE>null</CODE> to process all
963     * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise
964     */
965    public boolean setFieldProperty(String field, String name, Object value, int inst[]) {
966        if (writer == null)
967            throw new RuntimeException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
968        try {
969            Item item = fields.get(field);
970            if (item == null)
971                return false;
972            InstHit hit = new InstHit(inst);
973            PdfDictionary merged;
974            PdfString da;
975            if (name.equalsIgnoreCase("textfont")) {
976                for (int k = 0; k < item.size(); ++k) {
977                    if (hit.isHit(k)) {
978                        merged = item.getMerged( k );
979                        da = merged.getAsString(PdfName.DA);
980                        PdfDictionary dr = merged.getAsDict(PdfName.DR);
981                        if (da != null && dr != null) {
982                            Object dao[] = splitDAelements(da.toUnicodeString());
983                            PdfAppearance cb = new PdfAppearance();
984                            if (dao[DA_FONT] != null) {
985                                BaseFont bf = (BaseFont)value;
986                                PdfName psn = PdfAppearance.stdFieldFontNames.get(bf.getPostscriptFontName());
987                                if (psn == null) {
988                                    psn = new PdfName(bf.getPostscriptFontName());
989                                }
990                                PdfDictionary fonts = dr.getAsDict(PdfName.FONT);
991                                if (fonts == null) {
992                                    fonts = new PdfDictionary();
993                                    dr.put(PdfName.FONT, fonts);
994                                }
995                                PdfIndirectReference fref = (PdfIndirectReference)fonts.get(psn);
996                                PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM);
997                                markUsed(top);
998                                dr = top.getAsDict(PdfName.DR);
999                                if (dr == null) {
1000                                    dr = new PdfDictionary();
1001                                    top.put(PdfName.DR, dr);
1002                                }
1003                                markUsed(dr);
1004                                PdfDictionary fontsTop = dr.getAsDict(PdfName.FONT);
1005                                if (fontsTop == null) {
1006                                    fontsTop = new PdfDictionary();
1007                                    dr.put(PdfName.FONT, fontsTop);
1008                                }
1009                                markUsed(fontsTop);
1010                                PdfIndirectReference frefTop = (PdfIndirectReference)fontsTop.get(psn);
1011                                if (frefTop != null) {
1012                                    if (fref == null)
1013                                        fonts.put(psn, frefTop);
1014                                }
1015                                else if (fref == null) {
1016                                    FontDetails fd;
1017                                    if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) {
1018                                        fd = new FontDetails(null, ((DocumentFont)bf).getIndirectReference(), bf);
1019                                    }
1020                                    else {
1021                                        bf.setSubset(false);
1022                                        fd = writer.addSimple(bf);
1023                                        localFonts.put(psn.toString().substring(1), bf);
1024                                    }
1025                                    fontsTop.put(psn, fd.getIndirectReference());
1026                                    fonts.put(psn, fd.getIndirectReference());
1027                                }
1028                                ByteBuffer buf = cb.getInternalBuffer();
1029                                buf.append(psn.getBytes()).append(' ').append(((Float)dao[DA_SIZE]).floatValue()).append(" Tf ");
1030                                if (dao[DA_COLOR] != null)
1031                                    cb.setColorFill((BaseColor)dao[DA_COLOR]);
1032                                PdfString s = new PdfString(cb.toString());
1033                                item.getMerged(k).put(PdfName.DA, s);
1034                                item.getWidget(k).put(PdfName.DA, s);
1035                                markUsed(item.getWidget(k));
1036                            }
1037                        }
1038                    }
1039                }
1040            }
1041            else if (name.equalsIgnoreCase("textcolor")) {
1042                for (int k = 0; k < item.size(); ++k) {
1043                    if (hit.isHit(k)) {
1044                        merged = item.getMerged( k );
1045                        da = merged.getAsString(PdfName.DA);
1046                        if (da != null) {
1047                            Object dao[] = splitDAelements(da.toUnicodeString());
1048                            PdfAppearance cb = new PdfAppearance();
1049                            if (dao[DA_FONT] != null) {
1050                                ByteBuffer buf = cb.getInternalBuffer();
1051                                buf.append(new PdfName((String)dao[DA_FONT]).getBytes()).append(' ').append(((Float)dao[DA_SIZE]).floatValue()).append(" Tf ");
1052                                cb.setColorFill((BaseColor)value);
1053                                PdfString s = new PdfString(cb.toString());
1054                                item.getMerged(k).put(PdfName.DA, s);
1055                                item.getWidget(k).put(PdfName.DA, s);
1056                                markUsed(item.getWidget(k));
1057                            }
1058                        }
1059                    }
1060                }
1061            }
1062            else if (name.equalsIgnoreCase("textsize")) {
1063                for (int k = 0; k < item.size(); ++k) {
1064                    if (hit.isHit(k)) {
1065                        merged = item.getMerged( k );
1066                        da = merged.getAsString(PdfName.DA);
1067                        if (da != null) {
1068                            Object dao[] = splitDAelements(da.toUnicodeString());
1069                            PdfAppearance cb = new PdfAppearance();
1070                            if (dao[DA_FONT] != null) {
1071                                ByteBuffer buf = cb.getInternalBuffer();
1072                                buf.append(new PdfName((String)dao[DA_FONT]).getBytes()).append(' ').append(((Float)value).floatValue()).append(" Tf ");
1073                                if (dao[DA_COLOR] != null)
1074                                    cb.setColorFill((BaseColor)dao[DA_COLOR]);
1075                                PdfString s = new PdfString(cb.toString());
1076                                item.getMerged(k).put(PdfName.DA, s);
1077                                item.getWidget(k).put(PdfName.DA, s);
1078                                markUsed(item.getWidget(k));
1079                            }
1080                        }
1081                    }
1082                }
1083            }
1084            else if (name.equalsIgnoreCase("bgcolor") || name.equalsIgnoreCase("bordercolor")) {
1085                PdfName dname = name.equalsIgnoreCase("bgcolor") ? PdfName.BG : PdfName.BC;
1086                for (int k = 0; k < item.size(); ++k) {
1087                    if (hit.isHit(k)) {
1088                        merged = item.getMerged( k );
1089                        PdfDictionary mk = merged.getAsDict(PdfName.MK);
1090                        if (mk == null) {
1091                            if (value == null)
1092                                return true;
1093                            mk = new PdfDictionary();
1094                            item.getMerged(k).put(PdfName.MK, mk);
1095                            item.getWidget(k).put(PdfName.MK, mk);
1096                            markUsed(item.getWidget(k));
1097                        } else {
1098                            markUsed( mk );
1099                        }
1100                        if (value == null)
1101                            mk.remove(dname);
1102                        else
1103                            mk.put(dname, PdfFormField.getMKColor((BaseColor)value));
1104                    }
1105                }
1106            }
1107            else
1108                return false;
1109            return true;
1110        }
1111        catch (Exception e) {
1112            throw new ExceptionConverter(e);
1113        }
1114    }
1115
1116    /**
1117     * Sets a field property. Valid property names are:
1118     * <p>
1119     * <ul>
1120     * <li>flags - a set of flags specifying various characteristics of the field's widget annotation.
1121     * The value of this entry replaces that of the F entry in the form's corresponding annotation dictionary.<br>
1122     * <li>setflags - a set of flags to be set (turned on) in the F entry of the form's corresponding
1123     * widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 1.<br>
1124     * <li>clrflags - a set of flags to be cleared (turned off) in the F entry of the form's corresponding
1125     * widget annotation dictionary. Bits equal to 1 cause the corresponding
1126     * bits in F to be set to 0.<br>
1127     * <li>fflags - a set of flags specifying various characteristics of the field. The value
1128     * of this entry replaces that of the Ff entry in the form's corresponding field dictionary.<br>
1129     * <li>setfflags - a set of flags to be set (turned on) in the Ff entry of the form's corresponding
1130     * field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 1.<br>
1131     * <li>clrfflags - a set of flags to be cleared (turned off) in the Ff entry of the form's corresponding
1132     * field dictionary. Bits equal to 1 cause the corresponding bits in Ff
1133     * to be set to 0.<br>
1134     * </ul>
1135     *
1136     * @param field the field name
1137     * @param name the property name
1138     * @param value the property value
1139     * @param inst an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process.
1140     * Set to <CODE>null</CODE> to process all
1141     * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise
1142     */
1143    public boolean setFieldProperty(String field, String name, int value, int inst[]) {
1144        if (writer == null)
1145            throw new RuntimeException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
1146        Item item = fields.get(field);
1147        if (item == null)
1148            return false;
1149        InstHit hit = new InstHit(inst);
1150        if (name.equalsIgnoreCase("flags")) {
1151            PdfNumber num = new PdfNumber(value);
1152            for (int k = 0; k < item.size(); ++k) {
1153                if (hit.isHit(k)) {
1154                    item.getMerged(k).put(PdfName.F, num);
1155                    item.getWidget(k).put(PdfName.F, num);
1156                    markUsed(item.getWidget(k));
1157                }
1158            }
1159        }
1160        else if (name.equalsIgnoreCase("setflags")) {
1161            for (int k = 0; k < item.size(); ++k) {
1162                if (hit.isHit(k)) {
1163                    PdfNumber num = item.getWidget(k).getAsNumber(PdfName.F);
1164                    int val = 0;
1165                    if (num != null)
1166                        val = num.intValue();
1167                    num = new PdfNumber(val | value);
1168                    item.getMerged(k).put(PdfName.F, num);
1169                    item.getWidget(k).put(PdfName.F, num);
1170                    markUsed(item.getWidget(k));
1171                }
1172            }
1173        }
1174        else if (name.equalsIgnoreCase("clrflags")) {
1175            for (int k = 0; k < item.size(); ++k) {
1176                if (hit.isHit(k)) {
1177                    PdfDictionary widget = item.getWidget( k );
1178                    PdfNumber num = widget.getAsNumber(PdfName.F);
1179                    int val = 0;
1180                    if (num != null)
1181                        val = num.intValue();
1182                    num = new PdfNumber(val & ~value);
1183                    item.getMerged(k).put(PdfName.F, num);
1184                    widget.put(PdfName.F, num);
1185                    markUsed(widget);
1186                }
1187            }
1188        }
1189        else if (name.equalsIgnoreCase("fflags")) {
1190            PdfNumber num = new PdfNumber(value);
1191            for (int k = 0; k < item.size(); ++k) {
1192                if (hit.isHit(k)) {
1193                    item.getMerged(k).put(PdfName.FF, num);
1194                    item.getValue(k).put(PdfName.FF, num);
1195                    markUsed(item.getValue(k));
1196                }
1197            }
1198        }
1199        else if (name.equalsIgnoreCase("setfflags")) {
1200            for (int k = 0; k < item.size(); ++k) {
1201                if (hit.isHit(k)) {
1202                    PdfDictionary valDict = item.getValue( k );
1203                    PdfNumber num = valDict.getAsNumber( PdfName.FF );
1204                    int val = 0;
1205                    if (num != null)
1206                        val = num.intValue();
1207                    num = new PdfNumber(val | value);
1208                    item.getMerged(k).put(PdfName.FF, num);
1209                    valDict.put(PdfName.FF, num);
1210                    markUsed(valDict);
1211                }
1212            }
1213        }
1214        else if (name.equalsIgnoreCase("clrfflags")) {
1215            for (int k = 0; k < item.size(); ++k) {
1216                if (hit.isHit(k)) {
1217                    PdfDictionary valDict = item.getValue( k );
1218                    PdfNumber num = valDict.getAsNumber(PdfName.FF);
1219                    int val = 0;
1220                    if (num != null)
1221                        val = num.intValue();
1222                    num = new PdfNumber(val & ~value);
1223                    item.getMerged(k).put(PdfName.FF, num);
1224                    valDict.put(PdfName.FF, num);
1225                    markUsed(valDict);
1226                }
1227            }
1228        }
1229        else
1230            return false;
1231        return true;
1232    }
1233
1234    /**
1235     * Merges an XML data structure into this form.
1236     *
1237     * @param n the top node of the data structure
1238     * @throws java.io.IOException on error
1239     * @throws com.itextpdf.text.DocumentException o error
1240     */
1241    public void mergeXfaData(Node n) throws IOException, DocumentException {
1242        XfaForm.Xml2SomDatasets data = new XfaForm.Xml2SomDatasets(n);
1243        for (String string : data.getOrder()) {
1244            String name = string;
1245            String text = XfaForm.getNodeText(data.getName2Node().get(name));
1246            setField(name, text);
1247        }
1248    }
1249
1250    /**
1251     * Sets the fields by FDF merging.
1252     *
1253     * @param fdf the FDF form
1254     * @throws IOException on error
1255     * @throws DocumentException on error
1256     */
1257    public void setFields(FdfReader fdf) throws IOException, DocumentException {
1258        HashMap<String, PdfDictionary> fd = fdf.getFields();
1259        for (String f: fd.keySet()) {
1260            String v = fdf.getFieldValue(f);
1261            if (v != null)
1262                setField(f, v);
1263        }
1264    }
1265
1266    /**
1267     * Sets the fields by XFDF merging.
1268     *
1269     * @param xfdf the XFDF form
1270     * @throws IOException on error
1271     * @throws DocumentException on error
1272     */
1273    public void setFields(XfdfReader xfdf) throws IOException, DocumentException {
1274        HashMap<String, String> fd = xfdf.getFields();
1275        for (String f: fd.keySet()) {
1276            String v = xfdf.getFieldValue(f);
1277            if (v != null)
1278                setField(f, v);
1279            List<String> l = xfdf.getListValues(f);
1280            if (l != null)
1281                setListSelection(v, l.toArray(new String[l.size()]));
1282        }
1283    }
1284
1285    /**
1286     * Regenerates the field appearance.
1287     * This is useful when you change a field property, but not its value,
1288     * for instance form.setFieldProperty("f", "bgcolor", BaseColor.BLUE, null);
1289     * This won't have any effect, unless you use regenerateField("f") after changing
1290     * the property.
1291     *
1292     * @param name the fully qualified field name or the partial name in the case of XFA forms
1293     * @throws IOException on error
1294     * @throws DocumentException on error
1295     * @return <CODE>true</CODE> if the field was found and changed,
1296     * <CODE>false</CODE> otherwise
1297     */
1298    public boolean regenerateField(String name) throws IOException, DocumentException {
1299        String value = getField(name);
1300        return setField(name, value, value);
1301    }
1302
1303    /**
1304     * Sets the field value.
1305     *
1306     * @param name the fully qualified field name or the partial name in the case of XFA forms
1307     * @param value the field value
1308     * @throws IOException on error
1309     * @throws DocumentException on error
1310     * @return <CODE>true</CODE> if the field was found and changed,
1311     * <CODE>false</CODE> otherwise
1312     */
1313    public boolean setField(String name, String value) throws IOException, DocumentException {
1314        return setField(name, value, null);
1315    }
1316    
1317    /**
1318     * 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 
1319     * 12.7.3.4 (Rich Text) and 12.7.4.3 (Text Fields) for further details.
1320     * @param name  Field name
1321     * @param richValue html markup 
1322     * @return success/failure (will fail if the field isn't found, isn't a text field, or doesn't support rich text)
1323     * @throws DocumentException
1324     * @since 5.0.6
1325     */
1326    public boolean setFieldRichValue(String name, String richValue) throws DocumentException {
1327        if (writer == null) {
1328                // can't set field values: fail
1329            throw new DocumentException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
1330        }
1331
1332        AcroFields.Item item = getFieldItem(name);
1333        if (item == null) {
1334                // can't find the field: fail.
1335                return false;
1336        }
1337        
1338        if (getFieldType(name) != FIELD_TYPE_TEXT) {
1339                // field isn't a text field: fail
1340                return false;
1341        }
1342        
1343        PdfDictionary merged = item.getMerged(0);
1344        PdfNumber ffNum = merged.getAsNumber(PdfName.FF);
1345        int flagVal = 0;
1346        if (ffNum != null) {
1347                flagVal = ffNum.intValue();
1348        }
1349        if ((flagVal | PdfFormField.FF_RICHTEXT) == 0) {
1350                // text field doesn't support rich text: fail
1351                return false;
1352        }
1353        
1354        PdfString richString = new PdfString(richValue);
1355        item.writeToAll(PdfName.RV, richString, Item.WRITE_MERGED | Item.WRITE_VALUE);
1356        
1357        return true;
1358    }
1359
1360    /**
1361     * Sets the field value and the display string. The display string
1362     * is used to build the appearance in the cases where the value
1363     * is modified by Acrobat with JavaScript and the algorithm is
1364     * known.
1365     *
1366     * @param name the fully qualified field name or the partial name in the case of XFA forms
1367     * @param value the field value
1368     * @param display the string that is used for the appearance. If <CODE>null</CODE>
1369     * the <CODE>value</CODE> parameter will be used
1370     * @return <CODE>true</CODE> if the field was found and changed,
1371     * <CODE>false</CODE> otherwise
1372     * @throws IOException on error
1373     * @throws DocumentException on error
1374     */
1375    public boolean setField(String name, String value, String display) throws IOException, DocumentException {
1376        if (writer == null)
1377            throw new DocumentException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
1378        if (xfa.isXfaPresent()) {
1379            name = xfa.findFieldName(name, this);
1380            if (name == null)
1381                return false;
1382            String shortName = XfaForm.Xml2Som.getShortName(name);
1383            Node xn = xfa.findDatasetsNode(shortName);
1384            if (xn == null) {
1385                xn = xfa.getDatasetsSom().insertNode(xfa.getDatasetsNode(), shortName);
1386            }
1387            xfa.setNodeText(xn, value);
1388        }
1389        Item item = fields.get(name);
1390        if (item == null)
1391            return false;
1392        PdfDictionary merged = item.getMerged( 0 );
1393        PdfName type = merged.getAsName(PdfName.FT);
1394        if (PdfName.TX.equals(type)) {
1395            PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN);
1396            int len = 0;
1397            if (maxLen != null)
1398                len = maxLen.intValue();
1399            if (len > 0)
1400                value = value.substring(0, Math.min(len, value.length()));
1401        }
1402        if (display == null)
1403            display = value;
1404        if (PdfName.TX.equals(type) || PdfName.CH.equals(type)) {
1405            PdfString v = new PdfString(value, PdfObject.TEXT_UNICODE);
1406            for (int idx = 0; idx < item.size(); ++idx) {
1407                PdfDictionary valueDic = item.getValue(idx);
1408                valueDic.put(PdfName.V, v);
1409                valueDic.remove(PdfName.I);
1410                markUsed(valueDic);
1411                merged = item.getMerged(idx);
1412                merged.remove(PdfName.I);
1413                merged.put(PdfName.V, v);
1414                PdfDictionary widget = item.getWidget(idx);
1415                if (generateAppearances) {
1416                    PdfAppearance app = getAppearance(merged, display, name);
1417                    if (PdfName.CH.equals(type)) {
1418                        PdfNumber n = new PdfNumber(topFirst);
1419                        widget.put(PdfName.TI, n);
1420                        merged.put(PdfName.TI, n);
1421                    }
1422                    PdfDictionary appDic = widget.getAsDict(PdfName.AP);
1423                    if (appDic == null) {
1424                        appDic = new PdfDictionary();
1425                        widget.put(PdfName.AP, appDic);
1426                        merged.put(PdfName.AP, appDic);
1427                    }
1428                    appDic.put(PdfName.N, app.getIndirectReference());
1429                    writer.releaseTemplate(app);
1430                }
1431                else {
1432                    widget.remove(PdfName.AP);
1433                    merged.remove(PdfName.AP);
1434                }
1435                markUsed(widget);
1436            }
1437            return true;
1438        }
1439        else if (PdfName.BTN.equals(type)) {
1440            PdfNumber ff = item.getMerged(0).getAsNumber(PdfName.FF);
1441            int flags = 0;
1442            if (ff != null)
1443                flags = ff.intValue();
1444            if ((flags & PdfFormField.FF_PUSHBUTTON) != 0) {
1445                //we'll assume that the value is an image in base64
1446                Image img;
1447                try {
1448                    img = Image.getInstance(Base64.decode(value));
1449                }
1450                catch (Exception e) {
1451                    return false;
1452                }
1453                PushbuttonField pb = getNewPushbuttonFromField(name);
1454                pb.setImage(img);
1455                replacePushbuttonField(name, pb.getField());
1456                return true;
1457            }
1458            PdfName v = new PdfName(value);
1459            ArrayList<String> lopt = new ArrayList<String>();
1460            PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT);
1461            if (opts != null) {
1462                for (int k = 0; k < opts.size(); ++k) {
1463                    PdfString valStr = opts.getAsString(k);
1464                    if (valStr != null)
1465                        lopt.add(valStr.toUnicodeString());
1466                    else
1467                        lopt.add(null);
1468                }
1469            }
1470            int vidx = lopt.indexOf(value);
1471            PdfName vt;
1472            if (vidx >= 0)
1473                vt = new PdfName(String.valueOf(vidx));
1474            else
1475                vt = v;
1476            for (int idx = 0; idx < item.size(); ++idx) {
1477                merged = item.getMerged(idx);
1478                PdfDictionary widget = item.getWidget(idx);
1479                PdfDictionary valDict = item.getValue(idx);
1480                markUsed(item.getValue(idx));
1481                valDict.put(PdfName.V, vt);
1482                merged.put(PdfName.V, vt);
1483                markUsed(widget);
1484                if (isInAP(widget,  vt)) {
1485                    merged.put(PdfName.AS, vt);
1486                    widget.put(PdfName.AS, vt);
1487                }
1488                else {
1489                    merged.put(PdfName.AS, PdfName.Off);
1490                    widget.put(PdfName.AS, PdfName.Off);
1491                }
1492            }
1493            return true;
1494        }
1495        return false;
1496    }
1497
1498    /**
1499     * Sets different values in a list selection.
1500     * No appearance is generated yet; nor does the code check if multiple select is allowed.
1501     *
1502     * @param   name    the name of the field
1503     * @param   value   an array with values that need to be selected
1504     * @return  true only if the field value was changed
1505     * @since 2.1.4
1506     */
1507        public boolean setListSelection(String name, String[] value) throws IOException, DocumentException {
1508        Item item = getFieldItem(name);
1509        if (item == null)
1510            return false;
1511        PdfDictionary merged = item.getMerged( 0 );
1512        PdfName type = merged.getAsName(PdfName.FT);
1513        if (!PdfName.CH.equals(type)) {
1514                return false;
1515        }
1516        String[] options = getListOptionExport(name);
1517        PdfArray array = new PdfArray();
1518        for (String element : value) {
1519                for (int j = 0; j < options.length; j++) {
1520                        if (options[j].equals(element)) {
1521                                array.add(new PdfNumber(j));
1522                                break;
1523                        }
1524                }
1525        }
1526        item.writeToAll(PdfName.I, array, Item.WRITE_MERGED | Item.WRITE_VALUE);
1527
1528        PdfArray vals = new PdfArray();
1529        for (int i = 0; i < value.length; ++i) {
1530                vals.add( new PdfString( value[i] ) );
1531        }
1532        item.writeToAll(PdfName.V, vals, Item.WRITE_MERGED | Item.WRITE_VALUE);
1533
1534        PdfAppearance app = getAppearance( merged, value, name );
1535
1536        PdfDictionary apDic = new PdfDictionary();
1537        apDic.put( PdfName.N, app.getIndirectReference() );
1538        item.writeToAll(PdfName.AP, apDic, Item.WRITE_MERGED | Item.WRITE_WIDGET);
1539
1540        writer.releaseTemplate( app );
1541
1542        item.markUsed( this, Item.WRITE_VALUE | Item.WRITE_WIDGET );
1543        return true;
1544        }
1545
1546    boolean isInAP(PdfDictionary dic, PdfName check) {
1547        PdfDictionary appDic = dic.getAsDict(PdfName.AP);
1548        if (appDic == null)
1549            return false;
1550        PdfDictionary NDic = appDic.getAsDict(PdfName.N);
1551        return NDic != null && NDic.get(check) != null;
1552    }
1553
1554    /**
1555     * Gets all the fields. The fields are keyed by the fully qualified field name and
1556     * the value is an instance of <CODE>AcroFields.Item</CODE>.
1557     *
1558     * @return all the fields
1559     */
1560    public Map<String, Item> getFields() {
1561        return fields;
1562    }
1563
1564    /**
1565     * Gets the field structure.
1566     *
1567     * @param name the name of the field
1568     * @return the field structure or <CODE>null</CODE> if the field
1569     * does not exist
1570     */
1571    public Item getFieldItem(String name) {
1572        if (xfa.isXfaPresent()) {
1573            name = xfa.findFieldName(name, this);
1574            if (name == null)
1575                return null;
1576        }
1577        return fields.get(name);
1578    }
1579
1580    /**
1581     * Gets the long XFA translated name.
1582     *
1583     * @param name the name of the field
1584     * @return the long field name
1585     */
1586    public String getTranslatedFieldName(String name) {
1587        if (xfa.isXfaPresent()) {
1588            String namex = xfa.findFieldName(name, this);
1589            if (namex != null)
1590                name = namex;
1591        }
1592        return name;
1593    }
1594
1595    /**
1596     * Gets the field box positions in the document. The return is an array of <CODE>float</CODE>
1597     * multiple of 5. For each of this groups the values are: [page, llx, lly, urx,
1598     * ury]. The coordinates have the page rotation in consideration.
1599     *
1600     * @param name the field name
1601     * @return the positions or <CODE>null</CODE> if field does not exist
1602     */
1603    public List<FieldPosition> getFieldPositions(String name) {
1604        Item item = getFieldItem(name);
1605        if (item == null)
1606            return null;
1607        ArrayList<FieldPosition> ret = new ArrayList<FieldPosition>();
1608        for (int k = 0; k < item.size(); ++k) {
1609            try {
1610                PdfDictionary wd = item.getWidget(k);
1611                PdfArray rect = wd.getAsArray(PdfName.RECT);
1612                if (rect == null)
1613                    continue;
1614                Rectangle r = PdfReader.getNormalizedRectangle(rect);
1615                int page = item.getPage(k).intValue();
1616                int rotation = reader.getPageRotation(page);
1617                FieldPosition fp = new FieldPosition();
1618                fp.page = page;
1619                if (rotation != 0) {
1620                    Rectangle pageSize = reader.getPageSize(page);
1621                    switch (rotation) {
1622                        case 270:
1623                            r = new Rectangle(
1624                                pageSize.getTop() - r.getBottom(),
1625                                r.getLeft(),
1626                                pageSize.getTop() - r.getTop(),
1627                                r.getRight());
1628                            break;
1629                        case 180:
1630                            r = new Rectangle(
1631                                pageSize.getRight() - r.getLeft(),
1632                                pageSize.getTop() - r.getBottom(),
1633                                pageSize.getRight() - r.getRight(),
1634                                pageSize.getTop() - r.getTop());
1635                            break;
1636                        case 90:
1637                            r = new Rectangle(
1638                                r.getBottom(),
1639                                pageSize.getRight() - r.getLeft(),
1640                                r.getTop(),
1641                                pageSize.getRight() - r.getRight());
1642                            break;
1643                    }
1644                    r.normalize();
1645                }
1646                fp.position = r;
1647                ret.add(fp);
1648            }
1649            catch (Exception e) {
1650                // empty on purpose
1651            }
1652        }
1653        return ret;
1654    }
1655
1656    private int removeRefFromArray(PdfArray array, PdfObject refo) {
1657        if (refo == null || !refo.isIndirect())
1658            return array.size();
1659        PdfIndirectReference ref = (PdfIndirectReference)refo;
1660        for (int j = 0; j < array.size(); ++j) {
1661            PdfObject obj = array.getPdfObject(j);
1662            if (!obj.isIndirect())
1663                continue;
1664            if (((PdfIndirectReference)obj).getNumber() == ref.getNumber())
1665                array.remove(j--);
1666        }
1667        return array.size();
1668    }
1669
1670    /**
1671     * Removes all the fields from <CODE>page</CODE>.
1672     *
1673     * @param page the page to remove the fields from
1674     * @return <CODE>true</CODE> if any field was removed, <CODE>false otherwise</CODE>
1675     */
1676    public boolean removeFieldsFromPage(int page) {
1677        if (page < 1)
1678            return false;
1679        String names[] = new String[fields.size()];
1680        fields.keySet().toArray(names);
1681        boolean found = false;
1682        for (int k = 0; k < names.length; ++k) {
1683            boolean fr = removeField(names[k], page);
1684            found = found || fr;
1685        }
1686        return found;
1687    }
1688
1689    /**
1690     * Removes a field from the document. If page equals -1 all the fields with this
1691     * <CODE>name</CODE> are removed from the document otherwise only the fields in
1692     * that particular page are removed.
1693     *
1694     * @param name the field name
1695     * @param page the page to remove the field from or -1 to remove it from all the pages
1696     * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE>
1697     */
1698    public boolean removeField(String name, int page) {
1699        Item item = getFieldItem(name);
1700        if (item == null)
1701            return false;
1702        PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(reader.getCatalog().get(PdfName.ACROFORM), reader.getCatalog());
1703
1704        if (acroForm == null)
1705            return false;
1706        PdfArray arrayf = acroForm.getAsArray(PdfName.FIELDS);
1707        if (arrayf == null)
1708            return false;
1709        for (int k = 0; k < item.size(); ++k) {
1710            int pageV = item.getPage(k).intValue();
1711            if (page != -1 && page != pageV)
1712                continue;
1713            PdfIndirectReference ref = item.getWidgetRef(k);
1714            PdfDictionary wd = item.getWidget( k );
1715            PdfDictionary pageDic = reader.getPageN(pageV);
1716            PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
1717            if (annots != null) {
1718                if (removeRefFromArray(annots, ref) == 0) {
1719                    pageDic.remove(PdfName.ANNOTS);
1720                    markUsed(pageDic);
1721                }
1722                else
1723                    markUsed(annots);
1724            }
1725            PdfReader.killIndirect(ref);
1726            PdfIndirectReference kid = ref;
1727            while ((ref = wd.getAsIndirectObject(PdfName.PARENT)) != null) {
1728                wd = wd.getAsDict( PdfName.PARENT );
1729                PdfArray kids = wd.getAsArray(PdfName.KIDS);
1730                if (removeRefFromArray(kids, kid) != 0)
1731                    break;
1732                kid = ref;
1733                PdfReader.killIndirect(ref);
1734            }
1735            if (ref == null) {
1736                removeRefFromArray(arrayf, kid);
1737                markUsed(arrayf);
1738            }
1739            if (page != -1) {
1740                item.remove( k );
1741                --k;
1742            }
1743        }
1744        if (page == -1 || item.size() == 0)
1745            fields.remove(name);
1746        return true;
1747    }
1748
1749    /**
1750     * Removes a field from the document.
1751     *
1752     * @param name the field name
1753     * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE>
1754     */
1755    public boolean removeField(String name) {
1756        return removeField(name, -1);
1757    }
1758
1759    /**
1760     * Gets the property generateAppearances.
1761     *
1762     * @return the property generateAppearances
1763     */
1764    public boolean isGenerateAppearances() {
1765        return generateAppearances;
1766    }
1767
1768    /**
1769     * Sets the option to generate appearances. Not generating appearances
1770     * will speed-up form filling but the results can be
1771     * unexpected in Acrobat. Don't use it unless your environment is well
1772     * controlled. The default is <CODE>true</CODE>.
1773     *
1774     * @param generateAppearances the option to generate appearances
1775     */
1776    public void setGenerateAppearances(boolean generateAppearances) {
1777        this.generateAppearances = generateAppearances;
1778        PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM);
1779        if (generateAppearances)
1780            top.remove(PdfName.NEEDAPPEARANCES);
1781        else
1782            top.put(PdfName.NEEDAPPEARANCES, PdfBoolean.PDFTRUE);
1783    }
1784
1785    /** The field representations for retrieval and modification. */
1786    public static class Item {
1787
1788        /**
1789         * <CODE>writeToAll</CODE> constant.
1790         *
1791         *  @since 2.1.5
1792         */
1793        public static final int WRITE_MERGED = 1;
1794
1795        /**
1796         * <CODE>writeToAll</CODE> and <CODE>markUsed</CODE> constant.
1797         *
1798         *  @since 2.1.5
1799         */
1800        public static final int WRITE_WIDGET = 2;
1801
1802        /**
1803         * <CODE>writeToAll</CODE> and <CODE>markUsed</CODE> constant.
1804         *
1805         *  @since 2.1.5
1806         */
1807        public static final int WRITE_VALUE = 4;
1808
1809        /**
1810         * This function writes the given key/value pair to all the instances
1811         * of merged, widget, and/or value, depending on the <code>writeFlags</code> setting
1812         *
1813         * @since 2.1.5
1814         *
1815         * @param key        you'll never guess what this is for.
1816         * @param value      if value is null, the key will be removed
1817         * @param writeFlags ORed together WRITE_* flags
1818         */
1819        public void writeToAll(PdfName key, PdfObject value, int writeFlags) {
1820            int i;
1821            PdfDictionary curDict = null;
1822            if ((writeFlags & WRITE_MERGED) != 0) {
1823                for (i = 0; i < merged.size(); ++i) {
1824                    curDict = getMerged(i);
1825                    curDict.put(key, value);
1826                }
1827            }
1828            if ((writeFlags & WRITE_WIDGET) != 0) {
1829                for (i = 0; i < widgets.size(); ++i) {
1830                    curDict = getWidget(i);
1831                    curDict.put(key, value);
1832                }
1833            }
1834            if ((writeFlags & WRITE_VALUE) != 0) {
1835                for (i = 0; i < values.size(); ++i) {
1836                    curDict = getValue(i);
1837                    curDict.put(key, value);
1838                }
1839            }
1840        }
1841
1842        /**
1843         * Mark all the item dictionaries used matching the given flags
1844         *
1845         * @since 2.1.5
1846         * @param writeFlags WRITE_MERGED is ignored
1847         */
1848        public void markUsed( AcroFields parentFields, int writeFlags ) {
1849            if ((writeFlags & WRITE_VALUE) != 0) {
1850                for (int i = 0; i < size(); ++i) {
1851                    parentFields.markUsed( getValue( i ) );
1852                }
1853            }
1854            if ((writeFlags & WRITE_WIDGET) != 0) {
1855                for (int i = 0; i < size(); ++i) {
1856                    parentFields.markUsed(getWidget(i));
1857                }
1858            }
1859        }
1860
1861        /**
1862         * An array of <CODE>PdfDictionary</CODE> where the value tag /V
1863         * is present.
1864         *
1865         * @since 5.0.2 public is now protected
1866         */
1867        protected ArrayList<PdfDictionary> values = new ArrayList<PdfDictionary>();
1868
1869        /**
1870         * An array of <CODE>PdfDictionary</CODE> with the widgets.
1871         *
1872         * @since 5.0.2 public is now protected
1873         */
1874        protected ArrayList<PdfDictionary> widgets = new ArrayList<PdfDictionary>();
1875
1876        /**
1877         * An array of <CODE>PdfDictionary</CODE> with the widget references.
1878         *
1879         * @since 5.0.2 public is now protected
1880         */
1881        protected ArrayList<PdfIndirectReference> widget_refs = new ArrayList<PdfIndirectReference>();
1882
1883        /**
1884         * An array of <CODE>PdfDictionary</CODE> with all the field
1885         * and widget tags merged.
1886         *
1887         * @since 5.0.2 public is now protected
1888         */
1889        protected ArrayList<PdfDictionary> merged = new ArrayList<PdfDictionary>();
1890
1891        /**
1892         * An array of <CODE>Integer</CODE> with the page numbers where
1893         * the widgets are displayed.
1894         *
1895         * @since 5.0.2 public is now protected
1896         */
1897        protected ArrayList<Integer> page = new ArrayList<Integer>();
1898        /**
1899         * An array of <CODE>Integer</CODE> with the tab order of the field in the page.
1900         *
1901         * @since 5.0.2 public is now protected
1902         */
1903        protected ArrayList<Integer> tabOrder = new ArrayList<Integer>();
1904
1905        /**
1906         * Preferred method of determining the number of instances
1907         * of a given field.
1908         *
1909         * @since 2.1.5
1910         * @return number of instances
1911         */
1912        public int size() {
1913            return values.size();
1914        }
1915
1916        /**
1917         * Remove the given instance from this item.  It is possible to
1918         * remove all instances using this function.
1919         *
1920         * @since 2.1.5
1921         * @param killIdx
1922         */
1923        void remove(int killIdx) {
1924            values.remove(killIdx);
1925            widgets.remove(killIdx);
1926            widget_refs.remove(killIdx);
1927            merged.remove(killIdx);
1928            page.remove(killIdx);
1929            tabOrder.remove(killIdx);
1930        }
1931
1932        /**
1933         * Retrieve the value dictionary of the given instance
1934         *
1935         * @since 2.1.5
1936         * @param idx instance index
1937         * @return dictionary storing this instance's value.  It may be shared across instances.
1938         */
1939        public PdfDictionary getValue(int idx) {
1940            return values.get(idx);
1941        }
1942
1943        /**
1944         * Add a value dict to this Item
1945         *
1946         * @since 2.1.5
1947         * @param value new value dictionary
1948         */
1949        void addValue(PdfDictionary value) {
1950            values.add(value);
1951        }
1952
1953        /**
1954         * Retrieve the widget dictionary of the given instance
1955         *
1956         * @since 2.1.5
1957         * @param idx instance index
1958         * @return The dictionary found in the appropriate page's Annot array.
1959         */
1960        public PdfDictionary getWidget(int idx) {
1961            return widgets.get(idx);
1962        }
1963
1964        /**
1965         * Add a widget dict to this Item
1966         *
1967         * @since 2.1.5
1968         * @param widget
1969         */
1970        void addWidget(PdfDictionary widget) {
1971            widgets.add(widget);
1972        }
1973
1974        /**
1975         * Retrieve the reference to the given instance
1976         *
1977         * @since 2.1.5
1978         * @param idx instance index
1979         * @return reference to the given field instance
1980         */
1981        public PdfIndirectReference getWidgetRef(int idx) {
1982            return widget_refs.get(idx);
1983        }
1984
1985        /**
1986         * Add a widget ref to this Item
1987         *
1988         * @since 2.1.5
1989         * @param widgRef
1990         */
1991        void addWidgetRef(PdfIndirectReference widgRef) {
1992            widget_refs.add(widgRef);
1993        }
1994
1995        /**
1996         * Retrieve the merged dictionary for the given instance.  The merged
1997         * dictionary contains all the keys present in parent fields, though they
1998         * may have been overwritten (or modified?) by children.
1999         * Example: a merged radio field dict will contain /V
2000         *
2001         * @since 2.1.5
2002         * @param idx  instance index
2003         * @return the merged dictionary for the given instance
2004         */
2005        public PdfDictionary getMerged(int idx) {
2006            return merged.get(idx);
2007        }
2008
2009        /**
2010         * Adds a merged dictionary to this Item.
2011         *
2012         * @since 2.1.5
2013         * @param mergeDict
2014         */
2015        void addMerged(PdfDictionary mergeDict) {
2016            merged.add(mergeDict);
2017        }
2018
2019        /**
2020         * Retrieve the page number of the given instance
2021         *
2022         * @since 2.1.5
2023         * @param idx
2024         * @return remember, pages are "1-indexed", not "0-indexed" like field instances.
2025         */
2026        public Integer getPage(int idx) {
2027            return page.get(idx);
2028        }
2029
2030        /**
2031         * Adds a page to the current Item.
2032         *
2033         * @since 2.1.5
2034         * @param pg
2035         */
2036        void addPage(int pg) {
2037            page.add(Integer.valueOf(pg));
2038        }
2039
2040        /**
2041         * forces a page value into the Item.
2042         *
2043         * @since 2.1.5
2044         * @param idx
2045         */
2046        void forcePage(int idx, int pg) {
2047            page.set(idx, Integer.valueOf( pg ));
2048        }
2049
2050        /**
2051         * Gets the tabOrder.
2052         *
2053         * @since 2.1.5
2054         * @param idx
2055         * @return tab index of the given field instance
2056         */
2057        public Integer getTabOrder(int idx) {
2058            return tabOrder.get(idx);
2059        }
2060
2061        /**
2062         * Adds a tab order value to this Item.
2063         *
2064         * @since 2.1.5
2065         * @param order
2066         */
2067        void addTabOrder(int order) {
2068            tabOrder.add(Integer.valueOf(order));
2069        }
2070    }
2071
2072    private static class InstHit {
2073        IntHashtable hits;
2074        public InstHit(int inst[]) {
2075            if (inst == null)
2076                return;
2077            hits = new IntHashtable();
2078            for (int k = 0; k < inst.length; ++k)
2079                hits.put(inst[k], 1);
2080        }
2081
2082        public boolean isHit(int n) {
2083            if (hits == null)
2084                return true;
2085            return hits.containsKey(n);
2086        }
2087    }
2088
2089    /**
2090     * Clears a signed field.
2091     * @param name the field name
2092     * @return true if the field was signed, false if the field was not signed or not found
2093     * @since 5.0.5
2094     */
2095    public boolean clearSignatureField(String name) {
2096        sigNames = null;
2097        getSignatureNames();
2098        if (!sigNames.containsKey(name))
2099            return false;
2100        Item sig = fields.get(name);
2101        sig.markUsed(this, Item.WRITE_VALUE | Item.WRITE_WIDGET);
2102        int n = sig.size();
2103        for (int k = 0; k < n; ++k) {
2104            clearSigDic(sig.getMerged(k));
2105            clearSigDic(sig.getWidget(k));
2106            clearSigDic(sig.getValue(k));
2107        }
2108        return true;
2109    }
2110
2111    private static void clearSigDic(PdfDictionary dic) {
2112        dic.remove(PdfName.AP);
2113        dic.remove(PdfName.AS);
2114        dic.remove(PdfName.V);
2115        dic.remove(PdfName.DV);
2116        dic.remove(PdfName.SV);
2117        dic.remove(PdfName.FF);
2118        dic.put(PdfName.F, new PdfNumber(PdfAnnotation.FLAGS_PRINT));
2119    }
2120
2121    /**
2122     * Gets the field names that have signatures and are signed.
2123     *
2124     * @return the field names that have signatures and are signed
2125     */
2126    public ArrayList<String> getSignatureNames() {
2127        if (sigNames != null)
2128            return new ArrayList<String>(sigNames.keySet());
2129        sigNames = new HashMap<String, int[]>();
2130        ArrayList<Object[]> sorter = new ArrayList<Object[]>();
2131        for (Map.Entry<String, Item> entry: fields.entrySet()) {
2132            Item item = entry.getValue();
2133            PdfDictionary merged = item.getMerged(0);
2134            if (!PdfName.SIG.equals(merged.get(PdfName.FT)))
2135                continue;
2136            PdfDictionary v = merged.getAsDict(PdfName.V);
2137            if (v == null)
2138                continue;
2139            PdfString contents = v.getAsString(PdfName.CONTENTS);
2140            if (contents == null)
2141                continue;
2142            PdfArray ro = v.getAsArray(PdfName.BYTERANGE);
2143            if (ro == null)
2144                continue;
2145            int rangeSize = ro.size();
2146            if (rangeSize < 2)
2147                continue;
2148            int length = ro.getAsNumber(rangeSize - 1).intValue() + ro.getAsNumber(rangeSize - 2).intValue();
2149            sorter.add(new Object[]{entry.getKey(), new int[]{length, 0}});
2150        }
2151        Collections.sort(sorter, new AcroFields.SorterComparator());
2152        if (!sorter.isEmpty()) {
2153            if (((int[])sorter.get(sorter.size() - 1)[1])[0] == reader.getFileLength())
2154                totalRevisions = sorter.size();
2155            else
2156                totalRevisions = sorter.size() + 1;
2157            for (int k = 0; k < sorter.size(); ++k) {
2158                Object objs[] = sorter.get(k);
2159                String name = (String)objs[0];
2160                int p[] = (int[])objs[1];
2161                p[1] = k + 1;
2162                sigNames.put(name, p);
2163            }
2164        }
2165        return new ArrayList<String>(sigNames.keySet());
2166    }
2167
2168    /**
2169     * Gets the field names that have blank signatures.
2170     *
2171     * @return the field names that have blank signatures
2172     */
2173    public ArrayList<String> getBlankSignatureNames() {
2174        getSignatureNames();
2175        ArrayList<String> sigs = new ArrayList<String>();
2176        for (Map.Entry<String, Item> entry: fields.entrySet()) {
2177            Item item = entry.getValue();
2178            PdfDictionary merged = item.getMerged(0);
2179            if (!PdfName.SIG.equals(merged.getAsName(PdfName.FT)))
2180                continue;
2181            if (sigNames.containsKey(entry.getKey()))
2182                continue;
2183            sigs.add(entry.getKey());
2184        }
2185        return sigs;
2186    }
2187
2188    /**
2189     * Gets the signature dictionary, the one keyed by /V.
2190     *
2191     * @param name the field name
2192     * @return the signature dictionary keyed by /V or <CODE>null</CODE> if the field is not
2193     * a signature
2194     */
2195    public PdfDictionary getSignatureDictionary(String name) {
2196        getSignatureNames();
2197        name = getTranslatedFieldName(name);
2198        if (!sigNames.containsKey(name))
2199            return null;
2200        Item item = fields.get(name);
2201        PdfDictionary merged = item.getMerged(0);
2202        return merged.getAsDict(PdfName.V);
2203    }
2204
2205    /**
2206     * Checks is the signature covers the entire document or just part of it.
2207     *
2208     * @param name the signature field name
2209     * @return <CODE>true</CODE> if the signature covers the entire document,
2210     * <CODE>false</CODE> otherwise
2211     */
2212    public boolean signatureCoversWholeDocument(String name) {
2213        getSignatureNames();
2214        name = getTranslatedFieldName(name);
2215        if (!sigNames.containsKey(name))
2216            return false;
2217        return sigNames.get(name)[0] == reader.getFileLength();
2218    }
2219
2220    /**
2221     * Verifies a signature. An example usage is:
2222     * <p>
2223     * <pre>
2224     * KeyStore kall = PdfPKCS7.loadCacertsKeyStore();
2225     * PdfReader reader = new PdfReader("my_signed_doc.pdf");
2226     * AcroFields af = reader.getAcroFields();
2227     * ArrayList names = af.getSignatureNames();
2228     * for (int k = 0; k &lt; names.size(); ++k) {
2229     *    String name = (String)names.get(k);
2230     *    System.out.println("Signature name: " + name);
2231     *    System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name));
2232     *    PdfPKCS7 pk = af.verifySignature(name);
2233     *    Calendar cal = pk.getSignDate();
2234     *    Certificate pkc[] = pk.getCertificates();
2235     *    System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate()));
2236     *    System.out.println("Document modified: " + !pk.verify());
2237     *    Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal);
2238     *    if (fails == null)
2239     *        System.out.println("Certificates verified against the KeyStore");
2240     *    else
2241     *        System.out.println("Certificate failed: " + fails[1]);
2242     * }
2243     * </pre>
2244     *
2245     * @param name the signature field name
2246     * @return a <CODE>PdfPKCS7</CODE> class to continue the verification
2247     */
2248    public PdfPKCS7 verifySignature(String name) {
2249        return verifySignature(name, null);
2250    }
2251
2252    /**
2253     * Verifies a signature. An example usage is:
2254     * <p>
2255     * <pre>
2256     * KeyStore kall = PdfPKCS7.loadCacertsKeyStore();
2257     * PdfReader reader = new PdfReader("my_signed_doc.pdf");
2258     * AcroFields af = reader.getAcroFields();
2259     * ArrayList names = af.getSignatureNames();
2260     * for (int k = 0; k &lt; names.size(); ++k) {
2261     *    String name = (String)names.get(k);
2262     *    System.out.println("Signature name: " + name);
2263     *    System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name));
2264     *    PdfPKCS7 pk = af.verifySignature(name);
2265     *    Calendar cal = pk.getSignDate();
2266     *    Certificate pkc[] = pk.getCertificates();
2267     *    System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate()));
2268     *    System.out.println("Document modified: " + !pk.verify());
2269     *    Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal);
2270     *    if (fails == null)
2271     *        System.out.println("Certificates verified against the KeyStore");
2272     *    else
2273     *        System.out.println("Certificate failed: " + fails[1]);
2274     * }
2275     * </pre>
2276     *
2277     * @param name the signature field name
2278     * @param provider the provider or <code>null</code> for the default provider
2279     * @return a <CODE>PdfPKCS7</CODE> class to continue the verification
2280     */
2281    public PdfPKCS7 verifySignature(String name, String provider) {
2282        PdfDictionary v = getSignatureDictionary(name);
2283        if (v == null)
2284            return null;
2285        try {
2286            PdfName sub = v.getAsName(PdfName.SUBFILTER);
2287            PdfString contents = v.getAsString(PdfName.CONTENTS);
2288            PdfPKCS7 pk = null;
2289            if (sub.equals(PdfName.ADBE_X509_RSA_SHA1)) {
2290                PdfString cert = v.getAsString(PdfName.CERT);
2291                if (cert == null)
2292                    cert = v.getAsArray(PdfName.CERT).getAsString(0);
2293                pk = new PdfPKCS7(contents.getOriginalBytes(), cert.getBytes(), provider);
2294            }
2295            else
2296                pk = new PdfPKCS7(contents.getOriginalBytes(), provider);
2297            updateByteRange(pk, v);
2298            PdfString str = v.getAsString(PdfName.M);
2299            if (str != null)
2300                pk.setSignDate(PdfDate.decode(str.toString()));
2301            PdfObject obj = PdfReader.getPdfObject(v.get(PdfName.NAME));
2302            if (obj != null) {
2303              if (obj.isString())
2304                pk.setSignName(((PdfString)obj).toUnicodeString());
2305              else if(obj.isName())
2306                pk.setSignName(PdfName.decodeName(obj.toString()));
2307            }
2308            str = v.getAsString(PdfName.REASON);
2309            if (str != null)
2310                pk.setReason(str.toUnicodeString());
2311            str = v.getAsString(PdfName.LOCATION);
2312            if (str != null)
2313                pk.setLocation(str.toUnicodeString());
2314            return pk;
2315        }
2316        catch (Exception e) {
2317            throw new ExceptionConverter(e);
2318        }
2319    }
2320
2321    private void updateByteRange(PdfPKCS7 pkcs7, PdfDictionary v) {
2322        PdfArray b = v.getAsArray(PdfName.BYTERANGE);
2323        RandomAccessFileOrArray rf = reader.getSafeFile();
2324        try {
2325            rf.reOpen();
2326            byte buf[] = new byte[8192];
2327            for (int k = 0; k < b.size(); ++k) {
2328                int start = b.getAsNumber(k).intValue();
2329                int length = b.getAsNumber(++k).intValue();
2330                rf.seek(start);
2331                while (length > 0) {
2332                    int rd = rf.read(buf, 0, Math.min(length, buf.length));
2333                    if (rd <= 0)
2334                        break;
2335                    length -= rd;
2336                    pkcs7.update(buf, 0, rd);
2337                }
2338            }
2339        }
2340        catch (Exception e) {
2341            throw new ExceptionConverter(e);
2342        }
2343        finally {
2344            try{rf.close();}catch(Exception e){}
2345        }
2346    }
2347
2348    private void markUsed(PdfObject obj) {
2349        if (!append)
2350            return;
2351        ((PdfStamperImp)writer).markUsed(obj);
2352    }
2353
2354    /**
2355     * Gets the total number of revisions this document has.
2356     *
2357     * @return the total number of revisions
2358     */
2359    public int getTotalRevisions() {
2360        getSignatureNames();
2361        return this.totalRevisions;
2362    }
2363
2364    /**
2365     * Gets this <CODE>field</CODE> revision.
2366     *
2367     * @param field the signature field name
2368     * @return the revision or zero if it's not a signature field
2369     */
2370    public int getRevision(String field) {
2371        getSignatureNames();
2372        field = getTranslatedFieldName(field);
2373        if (!sigNames.containsKey(field))
2374            return 0;
2375        return sigNames.get(field)[1];
2376    }
2377
2378    /**
2379     * Extracts a revision from the document.
2380     *
2381     * @param field the signature field name
2382     * @return an <CODE>InputStream</CODE> covering the revision. Returns <CODE>null</CODE> if
2383     * it's not a signature field
2384     * @throws IOException on error
2385     */
2386    public InputStream extractRevision(String field) throws IOException {
2387        getSignatureNames();
2388        field = getTranslatedFieldName(field);
2389        if (!sigNames.containsKey(field))
2390            return null;
2391        int length = sigNames.get(field)[0];
2392        RandomAccessFileOrArray raf = reader.getSafeFile();
2393        raf.reOpen();
2394        raf.seek(0);
2395        return new RevisionStream(raf, length);
2396    }
2397
2398    /**
2399     * Gets the appearances cache.
2400     *
2401     * @return the appearances cache
2402     * @since   2.1.5   this method used to return a HashMap
2403     */
2404    public Map<String, TextField> getFieldCache() {
2405        return this.fieldCache;
2406    }
2407
2408    /**
2409     * Sets a cache for field appearances. Parsing the existing PDF to
2410     * create a new TextField is time expensive. For those tasks that repeatedly
2411     * fill the same PDF with different field values the use of the cache has dramatic
2412     * speed advantages. An example usage:
2413     * <p>
2414     * <pre>
2415     * String pdfFile = ...;// the pdf file used as template
2416     * ArrayList xfdfFiles = ...;// the xfdf file names
2417     * ArrayList pdfOutFiles = ...;// the output file names, one for each element in xpdfFiles
2418     * HashMap cache = new HashMap();// the appearances cache
2419     * PdfReader originalReader = new PdfReader(pdfFile);
2420     * for (int k = 0; k &lt; xfdfFiles.size(); ++k) {
2421     *    PdfReader reader = new PdfReader(originalReader);
2422     *    XfdfReader xfdf = new XfdfReader((String)xfdfFiles.get(k));
2423     *    PdfStamper stp = new PdfStamper(reader, new FileOutputStream((String)pdfOutFiles.get(k)));
2424     *    AcroFields af = stp.getAcroFields();
2425     *    af.setFieldCache(cache);
2426     *    af.setFields(xfdf);
2427     *    stp.close();
2428     * }
2429     * </pre>
2430     *
2431     * @param fieldCache a Map that will carry the cached appearances
2432     * @since   2.1.5   this method used to take a HashMap as parameter
2433     */
2434    public void setFieldCache(Map<String, TextField> fieldCache) {
2435        this.fieldCache = fieldCache;
2436    }
2437
2438    /**
2439     * Sets extra margins in text fields to better mimic the Acrobat layout.
2440     *
2441     * @param extraMarginLeft the extra margin left
2442     * @param extraMarginTop the extra margin top
2443     */
2444    public void setExtraMargin(float extraMarginLeft, float extraMarginTop) {
2445        this.extraMarginLeft = extraMarginLeft;
2446        this.extraMarginTop = extraMarginTop;
2447    }
2448
2449    /**
2450     * Adds a substitution font to the list. The fonts in this list will be used if the original
2451     * font doesn't contain the needed glyphs.
2452     *
2453     * @param font the font
2454     */
2455    public void addSubstitutionFont(BaseFont font) {
2456        if (substitutionFonts == null)
2457            substitutionFonts = new ArrayList<BaseFont>();
2458        substitutionFonts.add(font);
2459    }
2460
2461    private static final HashMap<String, String[]> stdFieldFontNames = new HashMap<String, String[]>();
2462
2463    /**
2464     * Holds value of property totalRevisions.
2465     */
2466    private int totalRevisions;
2467
2468    /**
2469     * Holds value of property fieldCache.
2470     *
2471     * @since   2.1.5   this used to be a HashMap
2472     */
2473    private Map<String, TextField> fieldCache;
2474
2475    static {
2476        stdFieldFontNames.put("CoBO", new String[]{"Courier-BoldOblique"});
2477        stdFieldFontNames.put("CoBo", new String[]{"Courier-Bold"});
2478        stdFieldFontNames.put("CoOb", new String[]{"Courier-Oblique"});
2479        stdFieldFontNames.put("Cour", new String[]{"Courier"});
2480        stdFieldFontNames.put("HeBO", new String[]{"Helvetica-BoldOblique"});
2481        stdFieldFontNames.put("HeBo", new String[]{"Helvetica-Bold"});
2482        stdFieldFontNames.put("HeOb", new String[]{"Helvetica-Oblique"});
2483        stdFieldFontNames.put("Helv", new String[]{"Helvetica"});
2484        stdFieldFontNames.put("Symb", new String[]{"Symbol"});
2485        stdFieldFontNames.put("TiBI", new String[]{"Times-BoldItalic"});
2486        stdFieldFontNames.put("TiBo", new String[]{"Times-Bold"});
2487        stdFieldFontNames.put("TiIt", new String[]{"Times-Italic"});
2488        stdFieldFontNames.put("TiRo", new String[]{"Times-Roman"});
2489        stdFieldFontNames.put("ZaDb", new String[]{"ZapfDingbats"});
2490        stdFieldFontNames.put("HySm", new String[]{"HYSMyeongJo-Medium", "UniKS-UCS2-H"});
2491        stdFieldFontNames.put("HyGo", new String[]{"HYGoThic-Medium", "UniKS-UCS2-H"});
2492        stdFieldFontNames.put("KaGo", new String[]{"HeiseiKakuGo-W5", "UniKS-UCS2-H"});
2493        stdFieldFontNames.put("KaMi", new String[]{"HeiseiMin-W3", "UniJIS-UCS2-H"});
2494        stdFieldFontNames.put("MHei", new String[]{"MHei-Medium", "UniCNS-UCS2-H"});
2495        stdFieldFontNames.put("MSun", new String[]{"MSung-Light", "UniCNS-UCS2-H"});
2496        stdFieldFontNames.put("STSo", new String[]{"STSong-Light", "UniGB-UCS2-H"});
2497    }
2498
2499    private static class RevisionStream extends InputStream {
2500        private byte b[] = new byte[1];
2501        private RandomAccessFileOrArray raf;
2502        private int length;
2503        private int rangePosition = 0;
2504        private boolean closed;
2505
2506        private RevisionStream(RandomAccessFileOrArray raf, int length) {
2507            this.raf = raf;
2508            this.length = length;
2509        }
2510
2511        @Override
2512        public int read() throws IOException {
2513            int n = read(b);
2514            if (n != 1)
2515                return -1;
2516            return b[0] & 0xff;
2517        }
2518
2519        @Override
2520        public int read(byte[] b, int off, int len) throws IOException {
2521            if (b == null) {
2522                throw new NullPointerException();
2523            } else if (off < 0 || off > b.length || len < 0 ||
2524            off + len > b.length || off + len < 0) {
2525                throw new IndexOutOfBoundsException();
2526            } else if (len == 0) {
2527                return 0;
2528            }
2529            if (rangePosition >= length) {
2530                close();
2531                return -1;
2532            }
2533            int elen = Math.min(len, length - rangePosition);
2534            raf.readFully(b, off, elen);
2535            rangePosition += elen;
2536            return elen;
2537        }
2538
2539        @Override
2540        public void close() throws IOException {
2541            if (!closed) {
2542                raf.close();
2543                closed = true;
2544            }
2545        }
2546    }
2547
2548    private static class SorterComparator implements Comparator<Object[]> {
2549        public int compare(Object[] o1, Object[] o2) {
2550            int n1 = ((int[])o1[1])[0];
2551            int n2 = ((int[])o2[1])[0];
2552            return n1 - n2;
2553        }
2554    }
2555
2556    /**
2557     * 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
2558     * font doesn't contain the needed glyphs.
2559     *
2560     * @return the list
2561     */
2562    public ArrayList<BaseFont> getSubstitutionFonts() {
2563        return substitutionFonts;
2564    }
2565
2566    /**
2567     * 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
2568     * font doesn't contain the needed glyphs.
2569     *
2570     * @param substitutionFonts the list
2571     */
2572    public void setSubstitutionFonts(ArrayList<BaseFont> substitutionFonts) {
2573        this.substitutionFonts = substitutionFonts;
2574    }
2575
2576    /**
2577     * Gets the XFA form processor.
2578     *
2579     * @return the XFA form processor
2580     */
2581    public XfaForm getXfa() {
2582        return xfa;
2583    }
2584
2585    /**
2586     * Removes the XFA stream from the document.
2587     */
2588    public void removeXfa() {
2589                PdfDictionary root = reader.getCatalog();
2590                PdfDictionary acroform = root.getAsDict(PdfName.ACROFORM);
2591                acroform.remove(PdfName.XFA);
2592                try {
2593                        xfa = new XfaForm(reader);
2594                }
2595                catch(Exception e) {
2596            throw new ExceptionConverter(e);
2597                }
2598    }
2599
2600    private static final PdfName[] buttonRemove = {PdfName.MK, PdfName.F , PdfName.FF , PdfName.Q , PdfName.BS , PdfName.BORDER};
2601
2602    /**
2603     * Creates a new pushbutton from an existing field. If there are several pushbuttons with the same name
2604     * only the first one is used. This pushbutton can be changed and be used to replace
2605     * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton
2606     * call {@link #replacePushbuttonField(String,PdfFormField)}.
2607     *
2608     * @param field the field name that should be a pushbutton
2609     * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton
2610     */
2611    public PushbuttonField getNewPushbuttonFromField(String field) {
2612        return getNewPushbuttonFromField(field, 0);
2613    }
2614
2615    /**
2616     * Creates a new pushbutton from an existing field. This pushbutton can be changed and be used to replace
2617     * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton
2618     * call {@link #replacePushbuttonField(String,PdfFormField,int)}.
2619     *
2620     * @param field the field name that should be a pushbutton
2621     * @param order the field order in fields with same name
2622     * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton
2623     *
2624     * @since 2.0.7
2625     */
2626    public PushbuttonField getNewPushbuttonFromField(String field, int order) {
2627        try {
2628            if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON)
2629                return null;
2630            Item item = getFieldItem(field);
2631            if (order >= item.size())
2632                return null;
2633            List<FieldPosition> pos = getFieldPositions(field);
2634            Rectangle box = pos.get(order).position;
2635            PushbuttonField newButton = new PushbuttonField(writer, box, null);
2636            PdfDictionary dic = item.getMerged(order);
2637            decodeGenericDictionary(dic, newButton);
2638            PdfDictionary mk = dic.getAsDict(PdfName.MK);
2639            if (mk != null) {
2640                PdfString text = mk.getAsString(PdfName.CA);
2641                if (text != null)
2642                    newButton.setText(text.toUnicodeString());
2643                PdfNumber tp = mk.getAsNumber(PdfName.TP);
2644                if (tp != null)
2645                    newButton.setLayout(tp.intValue() + 1);
2646                PdfDictionary ifit = mk.getAsDict(PdfName.IF);
2647                if (ifit != null) {
2648                    PdfName sw = ifit.getAsName(PdfName.SW);
2649                    if (sw != null) {
2650                        int scale = PushbuttonField.SCALE_ICON_ALWAYS;
2651                        if (sw.equals(PdfName.B))
2652                            scale = PushbuttonField.SCALE_ICON_IS_TOO_BIG;
2653                        else if (sw.equals(PdfName.S))
2654                            scale = PushbuttonField.SCALE_ICON_IS_TOO_SMALL;
2655                        else if (sw.equals(PdfName.N))
2656                            scale = PushbuttonField.SCALE_ICON_NEVER;
2657                        newButton.setScaleIcon(scale);
2658                    }
2659                    sw = ifit.getAsName(PdfName.S);
2660                    if (sw != null) {
2661                        if (sw.equals(PdfName.A))
2662                            newButton.setProportionalIcon(false);
2663                    }
2664                    PdfArray aj = ifit.getAsArray(PdfName.A);
2665                    if (aj != null && aj.size() == 2) {
2666                        float left = aj.getAsNumber(0).floatValue();
2667                        float bottom = aj.getAsNumber(1).floatValue();
2668                        newButton.setIconHorizontalAdjustment(left);
2669                        newButton.setIconVerticalAdjustment(bottom);
2670                    }
2671                    PdfBoolean fb = ifit.getAsBoolean(PdfName.FB);
2672                    if (fb != null && fb.booleanValue())
2673                        newButton.setIconFitToBounds(true);
2674                }
2675                PdfObject i = mk.get(PdfName.I);
2676                if (i != null && i.isIndirect())
2677                    newButton.setIconReference((PRIndirectReference)i);
2678            }
2679            return newButton;
2680        }
2681        catch (Exception e) {
2682            throw new ExceptionConverter(e);
2683        }
2684    }
2685
2686    /**
2687     * Replaces the first field with a new pushbutton. The pushbutton can be created with
2688     * {@link #getNewPushbuttonFromField(String)} from the same document or it can be a
2689     * generic PdfFormField of the type pushbutton.
2690     *
2691     * @param field the field name
2692     * @param button the <CODE>PdfFormField</CODE> representing the pushbutton
2693     * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field
2694     * was not a pushbutton
2695     */
2696    public boolean replacePushbuttonField(String field, PdfFormField button) {
2697        return replacePushbuttonField(field, button, 0);
2698    }
2699
2700    /**
2701     * Replaces the designated field with a new pushbutton. The pushbutton can be created with
2702     * {@link #getNewPushbuttonFromField(String,int)} from the same document or it can be a
2703     * generic PdfFormField of the type pushbutton.
2704     *
2705     * @param field the field name
2706     * @param button the <CODE>PdfFormField</CODE> representing the pushbutton
2707     * @param order the field order in fields with same name
2708     * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field
2709     * was not a pushbutton
2710     *
2711     * @since 2.0.7
2712     */
2713    public boolean replacePushbuttonField(String field, PdfFormField button, int order) {
2714        if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON)
2715            return false;
2716        Item item = getFieldItem(field);
2717        if (order >= item.size())
2718            return false;
2719        PdfDictionary merged = item.getMerged(order);
2720        PdfDictionary values = item.getValue(order);
2721        PdfDictionary widgets = item.getWidget(order);
2722        for (int k = 0; k < buttonRemove.length; ++k) {
2723            merged.remove(buttonRemove[k]);
2724            values.remove(buttonRemove[k]);
2725            widgets.remove(buttonRemove[k]);
2726        }
2727        for (Object element : button.getKeys()) {
2728            PdfName key = (PdfName)element;
2729            if (key.equals(PdfName.T) || key.equals(PdfName.RECT))
2730                continue;
2731            if (key.equals(PdfName.FF))
2732                values.put(key, button.get(key));
2733            else
2734                widgets.put(key, button.get(key));
2735            merged.put(key, button.get(key));
2736        }
2737        return true;
2738    }
2739
2740    /**
2741     * A class representing a field position
2742     * @since 5.0.2
2743     */
2744    public static class FieldPosition {
2745        public int page;
2746        public Rectangle position;
2747    }
2748}