1 /*
2  * Copyright 2003-2005 by Paulo Soares.
3  *
4  * The contents of this file are subject to the Mozilla Public License Version 1.1
5  * (the "License"); you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
7  *
8  * Software distributed under the License is distributed on an "AS IS" basis,
9  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10  * for the specific language governing rights and limitations under the License.
11  *
12  * The Original Code is 'iText, a free JAVA-PDF library'.
13  *
14  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
15  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
16  * All Rights Reserved.
17  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
18  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
19  *
20  * Contributor(s): all the names of the contributors are added in the source code
21  * where applicable.
22  *
23  * Alternatively, the contents of this file may be used under the terms of the
24  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
25  * provisions of LGPL are applicable instead of those above.  If you wish to
26  * allow use of your version of this file only under the terms of the LGPL
27  * License and not to allow others to use your version of this file under
28  * the MPL, indicate your decision by deleting the provisions above and
29  * replace them with the notice and other provisions required by the LGPL.
30  * If you do not delete the provisions above, a recipient may use your version
31  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
32  *
33  * This library is free software; you can redistribute it and/or modify it
34  * under the terms of the MPL as stated above or under the terms of the GNU
35  * Library General Public License as published by the Free Software Foundation;
36  * either version 2 of the License, or any later version.
37  *
38  * This library is distributed in the hope that it will be useful, but WITHOUT
39  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
40  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
41  * details.
42  *
43  * If you didn't download this code from the following link, you should check if
44  * you aren't using an obsolete version:
45  * http://www.lowagie.com/iText/
46  */
47 package com.lowagie.text.pdf;
48 
49 import java.awt.Color;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.Comparator;
55 import java.util.HashMap;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Map;
59 
60 import org.w3c.dom.Node;
61 
62 import com.lowagie.text.DocumentException;
63 import com.lowagie.text.Element;
64 import com.lowagie.text.ExceptionConverter;
65 import com.lowagie.text.Image;
66 import com.lowagie.text.Rectangle;
67 import com.lowagie.text.pdf.codec.Base64;
68 
69 /**
70  * Query and change fields in existing documents either by method
71  * calls or by FDF merging.
72  *
73  * @author Paulo Soares (psoares@consiste.pt)
74  */
75 public class AcroFields {
76 
77     PdfReader reader;
78     PdfWriter writer;
79     HashMap fields;
80     private int topFirst;
81     private HashMap sigNames;
82     private boolean append;
83     public static final int DA_FONT = 0;
84     public static final int DA_SIZE = 1;
85     public static final int DA_COLOR = 2;
86     private HashMap extensionFonts = new HashMap();
87     private XfaForm xfa;
88 
89     /**
90      * A field type invalid or not found.
91      */
92     public static final int FIELD_TYPE_NONE = 0;
93 
94     /**
95      * A field type.
96      */
97     public static final int FIELD_TYPE_PUSHBUTTON = 1;
98 
99     /**
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 localFonts = new HashMap();
135 
136     private float extraMarginLeft;
137     private float extraMarginTop;
138     private ArrayList substitutionFonts;
139 
AcroFields(PdfReader reader, PdfWriter writer)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 
fill()155     void fill() {
156         fields = new HashMap();
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 = (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      */
getAppearanceStates(String fieldName)264     public String[] getAppearanceStates(String fieldName) {
265         Item fd = (Item)fields.get(fieldName);
266         if (fd == null)
267             return null;
268         HashMap names = new HashMap();
269         PdfDictionary vals = fd.getValue(0);
270         PdfString stringOpt = vals.getAsString( PdfName.OPT );
271         if (stringOpt != null) {
272         	names.put(stringOpt.toUnicodeString(), null);
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.put(valStr.toUnicodeString(), null);
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 (Iterator it = dic.getKeys().iterator(); it.hasNext();) {
293                 String name = PdfName.decodeName(((PdfName)it.next()).toString());
294                 names.put(name, null);
295             }
296         }
297         String out[] = new String[names.size()];
298         return (String[])names.keySet().toArray(out);
299     }
300 
getListOption(String fieldName, int idx)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      */
getListOptionExport(String fieldName)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      */
getListOptionDisplay(String fieldName)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      */
setListOption(String fieldName, String[] exportValues, String[] displayValues)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("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 = (Item)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      */
getFieldType(String fieldName)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      */
exportAsFdf(FdfWriter writer)457     public void exportAsFdf(FdfWriter writer) {
458         for (Iterator it = fields.entrySet().iterator(); it.hasNext();) {
459             Map.Entry entry = (Map.Entry)it.next();
460             Item item = (Item)entry.getValue();
461             String name = (String)entry.getKey();
462             PdfObject v = item.getMerged(0).get(PdfName.V);
463             if (v == null)
464                 continue;
465             String value = getField(name);
466             if (lastWasString)
467                 writer.setFieldAsString(name, value);
468             else
469                 writer.setFieldAsName(name, value);
470         }
471     }
472 
473     /**
474      * Renames a field. Only the last part of the name can be renamed. For example,
475      * if the original field is "ab.cd.ef" only the "ef" part can be renamed.
476      *
477      * @param oldName the old field name
478      * @param newName the new field name
479      * @return <CODE>true</CODE> if the renaming was successful, <CODE>false</CODE>
480      * otherwise
481      */
renameField(String oldName, String newName)482     public boolean renameField(String oldName, String newName) {
483         int idx1 = oldName.lastIndexOf('.') + 1;
484         int idx2 = newName.lastIndexOf('.') + 1;
485         if (idx1 != idx2)
486             return false;
487         if (!oldName.substring(0, idx1).equals(newName.substring(0, idx2)))
488             return false;
489         if (fields.containsKey(newName))
490             return false;
491         Item item = (Item)fields.get(oldName);
492         if (item == null)
493             return false;
494         newName = newName.substring(idx2);
495         PdfString ss = new PdfString(newName, PdfObject.TEXT_UNICODE);
496 
497         item.writeToAll( PdfName.T, ss, Item.WRITE_VALUE | Item.WRITE_MERGED);
498         item.markUsed( this, Item.WRITE_VALUE );
499 
500         fields.remove(oldName);
501         fields.put(newName, item);
502 
503         return true;
504     }
505 
splitDAelements(String da)506     public static Object[] splitDAelements(String da) {
507         try {
508             PRTokeniser tk = new PRTokeniser(PdfEncodings.convertToBytes(da, null));
509             ArrayList stack = new ArrayList();
510             Object ret[] = new Object[3];
511             while (tk.nextToken()) {
512                 if (tk.getTokenType() == PRTokeniser.TK_COMMENT)
513                     continue;
514                 if (tk.getTokenType() == PRTokeniser.TK_OTHER) {
515                     String operator = tk.getStringValue();
516                     if (operator.equals("Tf")) {
517                         if (stack.size() >= 2) {
518                             ret[DA_FONT] = stack.get(stack.size() - 2);
519                             ret[DA_SIZE] = new Float((String)stack.get(stack.size() - 1));
520                         }
521                     }
522                     else if (operator.equals("g")) {
523                         if (stack.size() >= 1) {
524                             float gray = new Float((String)stack.get(stack.size() - 1)).floatValue();
525                             if (gray != 0)
526                                 ret[DA_COLOR] = new GrayColor(gray);
527                         }
528                     }
529                     else if (operator.equals("rg")) {
530                         if (stack.size() >= 3) {
531                             float red = new Float((String)stack.get(stack.size() - 3)).floatValue();
532                             float green = new Float((String)stack.get(stack.size() - 2)).floatValue();
533                             float blue = new Float((String)stack.get(stack.size() - 1)).floatValue();
534                             ret[DA_COLOR] = new Color(red, green, blue);
535                         }
536                     }
537                     else if (operator.equals("k")) {
538                         if (stack.size() >= 4) {
539                             float cyan = new Float((String)stack.get(stack.size() - 4)).floatValue();
540                             float magenta = new Float((String)stack.get(stack.size() - 3)).floatValue();
541                             float yellow = new Float((String)stack.get(stack.size() - 2)).floatValue();
542                             float black = new Float((String)stack.get(stack.size() - 1)).floatValue();
543                             ret[DA_COLOR] = new CMYKColor(cyan, magenta, yellow, black);
544                         }
545                     }
546                     stack.clear();
547                 }
548                 else
549                     stack.add(tk.getStringValue());
550             }
551             return ret;
552         }
553         catch (IOException ioe) {
554             throw new ExceptionConverter(ioe);
555         }
556     }
557 
decodeGenericDictionary(PdfDictionary merged, BaseField tx)558     public void decodeGenericDictionary(PdfDictionary merged, BaseField tx) throws IOException, DocumentException {
559         int flags = 0;
560         // the text size and color
561         PdfString da = merged.getAsString(PdfName.DA);
562         if (da != null) {
563             Object dab[] = splitDAelements(da.toUnicodeString());
564             if (dab[DA_SIZE] != null)
565                 tx.setFontSize(((Float)dab[DA_SIZE]).floatValue());
566             if (dab[DA_COLOR] != null)
567                 tx.setTextColor((Color)dab[DA_COLOR]);
568             if (dab[DA_FONT] != null) {
569                 PdfDictionary font = merged.getAsDict(PdfName.DR);
570                 if (font != null) {
571                     font = font.getAsDict(PdfName.FONT);
572                     if (font != null) {
573                         PdfObject po = font.get(new PdfName((String)dab[DA_FONT]));
574                         if (po != null && po.type() == PdfObject.INDIRECT) {
575                             PRIndirectReference por = (PRIndirectReference)po;
576                             BaseFont bp = new DocumentFont((PRIndirectReference)po);
577                             tx.setFont(bp);
578                             Integer porkey = new Integer(por.getNumber());
579                             BaseFont porf = (BaseFont)extensionFonts.get(porkey);
580                             if (porf == null) {
581                                 if (!extensionFonts.containsKey(porkey)) {
582                                     PdfDictionary fo = (PdfDictionary)PdfReader.getPdfObject(po);
583                                     PdfDictionary fd = fo.getAsDict(PdfName.FONTDESCRIPTOR);
584                                     if (fd != null) {
585                                         PRStream prs = (PRStream)PdfReader.getPdfObject(fd.get(PdfName.FONTFILE2));
586                                         if (prs == null)
587                                             prs = (PRStream)PdfReader.getPdfObject(fd.get(PdfName.FONTFILE3));
588                                         if (prs == null) {
589                                             extensionFonts.put(porkey, null);
590                                         }
591                                         else {
592                                             try {
593                                                 porf = BaseFont.createFont("font.ttf", BaseFont.IDENTITY_H, true, false, PdfReader.getStreamBytes(prs), null);
594                                             }
595                                             catch (Exception e) {
596                                             }
597                                             extensionFonts.put(porkey, porf);
598                                         }
599                                     }
600                                 }
601                             }
602                             if (tx instanceof TextField)
603                                 ((TextField)tx).setExtensionFont(porf);
604                         }
605                         else {
606                             BaseFont bf = (BaseFont)localFonts.get(dab[DA_FONT]);
607                             if (bf == null) {
608                                 String fn[] = (String[])stdFieldFontNames.get(dab[DA_FONT]);
609                                 if (fn != null) {
610                                     try {
611                                         String enc = "winansi";
612                                         if (fn.length > 1)
613                                             enc = fn[1];
614                                         bf = BaseFont.createFont(fn[0], enc, false);
615                                         tx.setFont(bf);
616                                     }
617                                     catch (Exception e) {
618                                         // empty
619                                     }
620                                 }
621                             }
622                             else
623                                 tx.setFont(bf);
624                         }
625                     }
626                 }
627             }
628         }
629         //rotation, border and background color
630         PdfDictionary mk = merged.getAsDict(PdfName.MK);
631         if (mk != null) {
632             PdfArray ar = mk.getAsArray(PdfName.BC);
633             Color border = getMKColor(ar);
634             tx.setBorderColor(border);
635             if (border != null)
636                 tx.setBorderWidth(1);
637             ar = mk.getAsArray(PdfName.BG);
638             tx.setBackgroundColor(getMKColor(ar));
639             PdfNumber rotation = mk.getAsNumber(PdfName.R);
640             if (rotation != null)
641                 tx.setRotation(rotation.intValue());
642         }
643         //flags
644         PdfNumber nfl = merged.getAsNumber(PdfName.F);
645         flags = 0;
646         tx.setVisibility(BaseField.VISIBLE_BUT_DOES_NOT_PRINT);
647         if (nfl != null) {
648             flags = nfl.intValue();
649             if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) != 0)
650                 tx.setVisibility(BaseField.HIDDEN);
651             else if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_NOVIEW) != 0)
652                 tx.setVisibility(BaseField.HIDDEN_BUT_PRINTABLE);
653             else if ((flags & PdfFormField.FLAGS_PRINT) != 0)
654                 tx.setVisibility(BaseField.VISIBLE);
655         }
656         //multiline
657         nfl = merged.getAsNumber(PdfName.FF);
658         flags = 0;
659         if (nfl != null)
660             flags = nfl.intValue();
661         tx.setOptions(flags);
662         if ((flags & PdfFormField.FF_COMB) != 0) {
663             PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN);
664             int len = 0;
665             if (maxLen != null)
666                 len = maxLen.intValue();
667             tx.setMaxCharacterLength(len);
668         }
669         //alignment
670         nfl = merged.getAsNumber(PdfName.Q);
671         if (nfl != null) {
672             if (nfl.intValue() == PdfFormField.Q_CENTER)
673                 tx.setAlignment(Element.ALIGN_CENTER);
674             else if (nfl.intValue() == PdfFormField.Q_RIGHT)
675                 tx.setAlignment(Element.ALIGN_RIGHT);
676         }
677         //border styles
678         PdfDictionary bs = merged.getAsDict(PdfName.BS);
679         if (bs != null) {
680             PdfNumber w = bs.getAsNumber(PdfName.W);
681             if (w != null)
682                 tx.setBorderWidth(w.floatValue());
683             PdfName s = bs.getAsName(PdfName.S);
684             if (PdfName.D.equals(s))
685                 tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
686             else if (PdfName.B.equals(s))
687                 tx.setBorderStyle(PdfBorderDictionary.STYLE_BEVELED);
688             else if (PdfName.I.equals(s))
689                 tx.setBorderStyle(PdfBorderDictionary.STYLE_INSET);
690             else if (PdfName.U.equals(s))
691                 tx.setBorderStyle(PdfBorderDictionary.STYLE_UNDERLINE);
692         }
693         else {
694             PdfArray bd = merged.getAsArray(PdfName.BORDER);
695             if (bd != null) {
696                 if (bd.size() >= 3)
697                     tx.setBorderWidth(bd.getAsNumber(2).floatValue());
698                 if (bd.size() >= 4)
699                     tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
700             }
701         }
702     }
703 
getAppearance(PdfDictionary merged, String text, String fieldName)704     PdfAppearance getAppearance(PdfDictionary merged, String text, String fieldName) throws IOException, DocumentException {
705         topFirst = 0;
706         TextField tx = null;
707         if (fieldCache == null || !fieldCache.containsKey(fieldName)) {
708             tx = new TextField(writer, null, null);
709             tx.setExtraMargin(extraMarginLeft, extraMarginTop);
710             tx.setBorderWidth(0);
711             tx.setSubstitutionFonts(substitutionFonts);
712             decodeGenericDictionary(merged, tx);
713             //rect
714             PdfArray rect = merged.getAsArray(PdfName.RECT);
715             Rectangle box = PdfReader.getNormalizedRectangle(rect);
716             if (tx.getRotation() == 90 || tx.getRotation() == 270)
717                 box = box.rotate();
718             tx.setBox(box);
719             if (fieldCache != null)
720                 fieldCache.put(fieldName, tx);
721         }
722         else {
723             tx = (TextField)fieldCache.get(fieldName);
724             tx.setWriter(writer);
725         }
726         PdfName fieldType = merged.getAsName(PdfName.FT);
727         if (PdfName.TX.equals(fieldType)) {
728             tx.setText(text);
729             return tx.getAppearance();
730         }
731         if (!PdfName.CH.equals(fieldType))
732             throw new DocumentException("An appearance was requested without a variable text field.");
733         PdfArray opt = merged.getAsArray(PdfName.OPT);
734         int flags = 0;
735         PdfNumber nfl = merged.getAsNumber(PdfName.FF);
736         if (nfl != null)
737             flags = nfl.intValue();
738         if ((flags & PdfFormField.FF_COMBO) != 0 && opt == null) {
739             tx.setText(text);
740             return tx.getAppearance();
741         }
742         if (opt != null) {
743             String choices[] = new String[opt.size()];
744             String choicesExp[] = new String[opt.size()];
745             for (int k = 0; k < opt.size(); ++k) {
746                 PdfObject obj = opt.getPdfObject(k);
747                 if (obj.isString()) {
748                     choices[k] = choicesExp[k] = ((PdfString)obj).toUnicodeString();
749                 }
750                 else {
751                     PdfArray a = (PdfArray) obj;
752                     choicesExp[k] = a.getAsString(0).toUnicodeString();
753                     choices[k] = a.getAsString(1).toUnicodeString();
754                 }
755             }
756             if ((flags & PdfFormField.FF_COMBO) != 0) {
757                 for (int k = 0; k < choices.length; ++k) {
758                     if (text.equals(choicesExp[k])) {
759                         text = choices[k];
760                         break;
761                     }
762                 }
763                 tx.setText(text);
764                 return tx.getAppearance();
765             }
766             int idx = 0;
767             for (int k = 0; k < choicesExp.length; ++k) {
768                 if (text.equals(choicesExp[k])) {
769                     idx = k;
770                     break;
771                 }
772             }
773             tx.setChoices(choices);
774             tx.setChoiceExports(choicesExp);
775             tx.setChoiceSelection(idx);
776         }
777         PdfAppearance app = tx.getListAppearance();
778         topFirst = tx.getTopFirst();
779         return app;
780     }
781 
getMKColor(PdfArray ar)782     Color getMKColor(PdfArray ar) {
783         if (ar == null)
784             return null;
785         switch (ar.size()) {
786             case 1:
787                 return new GrayColor(ar.getAsNumber(0).floatValue());
788             case 3:
789                 return new Color(ExtendedColor.normalize(ar.getAsNumber(0).floatValue()), ExtendedColor.normalize(ar.getAsNumber(1).floatValue()), ExtendedColor.normalize(ar.getAsNumber(2).floatValue()));
790             case 4:
791                 return new CMYKColor(ar.getAsNumber(0).floatValue(), ar.getAsNumber(1).floatValue(), ar.getAsNumber(2).floatValue(), ar.getAsNumber(3).floatValue());
792             default:
793                 return null;
794         }
795     }
796 
797     /**
798      * Gets the field value.
799      *
800      * @param name the fully qualified field name
801      * @return the field value
802      */
getField(String name)803     public String getField(String name) {
804         if (xfa.isXfaPresent()) {
805             name = xfa.findFieldName(name, this);
806             if (name == null)
807                 return null;
808             name = XfaForm.Xml2Som.getShortName(name);
809             return XfaForm.getNodeText(xfa.findDatasetsNode(name));
810         }
811         Item item = (Item)fields.get(name);
812         if (item == null)
813             return null;
814         lastWasString = false;
815         PdfDictionary mergedDict = item.getMerged( 0 );
816 
817         // Jose A. Rodriguez posted a fix to the mailing list (May 11, 2009)
818         // explaining that the value can also be a stream value
819         // the fix was made against an old iText version. Bruno adapted it.
820         PdfObject v = PdfReader.getPdfObject(mergedDict.get(PdfName.V));
821         if (v == null)
822         	return "";
823         if (v instanceof PRStream) {
824                 byte[] valBytes;
825 				try {
826 					valBytes = PdfReader.getStreamBytes((PRStream)v);
827 	                return new String(valBytes);
828 				} catch (IOException e) {
829 					throw new ExceptionConverter(e);
830 				}
831         }
832 
833         PdfName type = mergedDict.getAsName(PdfName.FT);
834         if (PdfName.BTN.equals(type)) {
835             PdfNumber ff = mergedDict.getAsNumber(PdfName.FF);
836             int flags = 0;
837             if (ff != null)
838                 flags = ff.intValue();
839             if ((flags & PdfFormField.FF_PUSHBUTTON) != 0)
840                 return "";
841             String value = "";
842             if (v instanceof PdfName)
843                 value = PdfName.decodeName(v.toString());
844             else if (v instanceof PdfString)
845                 value = ((PdfString)v).toUnicodeString();
846             PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT);
847             if (opts != null) {
848                 int idx = 0;
849                 try {
850                     idx = Integer.parseInt(value);
851                     PdfString ps = opts.getAsString(idx);
852                     value = ps.toUnicodeString();
853                     lastWasString = true;
854                 }
855                 catch (Exception e) {
856                 }
857             }
858             return value;
859         }
860         if (v instanceof PdfString) {
861             lastWasString = true;
862             return ((PdfString)v).toUnicodeString();
863         } else if (v instanceof PdfName) {
864             return PdfName.decodeName(v.toString());
865         } else
866             return "";
867     }
868 
869     /**
870      * Gets the field values of a Choice field.
871      *
872      * @param name the fully qualified field name
873      * @return the field value
874      * @since 2.1.3
875      */
getListSelection(String name)876     public String[] getListSelection(String name) {
877     	String[] ret;
878     	String s = getField(name);
879     	if (s == null) {
880     		ret = new String[]{};
881     	}
882     	else {
883     		ret = new String[]{ s };
884     	}
885         Item item = (Item)fields.get(name);
886         if (item == null)
887             return ret;
888         //PdfName type = (PdfName)PdfReader.getPdfObject(((PdfDictionary)item.merged.get(0)).get(PdfName.FT));
889         //if (!PdfName.CH.equals(type)) {
890         //	return ret;
891         //}
892         PdfArray values = item.getMerged(0).getAsArray(PdfName.I);
893         if (values == null)
894             return ret;
895         ret = new String[values.size()];
896         String[] options = getListOptionExport(name);
897         PdfNumber n;
898         int idx = 0;
899         for (Iterator i = values.listIterator(); i.hasNext(); ) {
900         	n = (PdfNumber)i.next();
901         	ret[idx++] = options[n.intValue()];
902         }
903         return ret;
904     }
905 
906 
907     /**
908      * Sets a field property. Valid property names are:
909      * <p>
910      * <ul>
911      * <li>textfont - sets the text font. The value for this entry is a <CODE>BaseFont</CODE>.<br>
912      * <li>textcolor - sets the text color. The value for this entry is a <CODE>java.awt.Color</CODE>.<br>
913      * <li>textsize - sets the text size. The value for this entry is a <CODE>Float</CODE>.
914      * <li>bgcolor - sets the background color. The value for this entry is a <CODE>java.awt.Color</CODE>.
915      *     If <code>null</code> removes the background.<br>
916      * <li>bordercolor - sets the border color. The value for this entry is a <CODE>java.awt.Color</CODE>.
917      *     If <code>null</code> removes the border.<br>
918      * </ul>
919      *
920      * @param field the field name
921      * @param name the property name
922      * @param value the property value
923      * @param inst an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process.
924      * Set to <CODE>null</CODE> to process all
925      * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise
926      */
setFieldProperty(String field, String name, Object value, int inst[])927     public boolean setFieldProperty(String field, String name, Object value, int inst[]) {
928         if (writer == null)
929             throw new RuntimeException("This AcroFields instance is read-only.");
930         try {
931             Item item = (Item)fields.get(field);
932             if (item == null)
933                 return false;
934             InstHit hit = new InstHit(inst);
935             PdfDictionary merged;
936             PdfString da;
937             if (name.equalsIgnoreCase("textfont")) {
938                 for (int k = 0; k < item.size(); ++k) {
939                     if (hit.isHit(k)) {
940                         merged = item.getMerged( k );
941                         da = merged.getAsString(PdfName.DA);
942                         PdfDictionary dr = merged.getAsDict(PdfName.DR);
943                         if (da != null && dr != null) {
944                             Object dao[] = splitDAelements(da.toUnicodeString());
945                             PdfAppearance cb = new PdfAppearance();
946                             if (dao[DA_FONT] != null) {
947                                 BaseFont bf = (BaseFont)value;
948                                 PdfName psn = (PdfName)PdfAppearance.stdFieldFontNames.get(bf.getPostscriptFontName());
949                                 if (psn == null) {
950                                     psn = new PdfName(bf.getPostscriptFontName());
951                                 }
952                                 PdfDictionary fonts = dr.getAsDict(PdfName.FONT);
953                                 if (fonts == null) {
954                                     fonts = new PdfDictionary();
955                                     dr.put(PdfName.FONT, fonts);
956                                 }
957                                 PdfIndirectReference fref = (PdfIndirectReference)fonts.get(psn);
958                                 PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM);
959                                 markUsed(top);
960                                 dr = top.getAsDict(PdfName.DR);
961                                 if (dr == null) {
962                                     dr = new PdfDictionary();
963                                     top.put(PdfName.DR, dr);
964                                 }
965                                 markUsed(dr);
966                                 PdfDictionary fontsTop = dr.getAsDict(PdfName.FONT);
967                                 if (fontsTop == null) {
968                                     fontsTop = new PdfDictionary();
969                                     dr.put(PdfName.FONT, fontsTop);
970                                 }
971                                 markUsed(fontsTop);
972                                 PdfIndirectReference frefTop = (PdfIndirectReference)fontsTop.get(psn);
973                                 if (frefTop != null) {
974                                     if (fref == null)
975                                         fonts.put(psn, frefTop);
976                                 }
977                                 else if (fref == null) {
978                                     FontDetails fd;
979                                     if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) {
980                                         fd = new FontDetails(null, ((DocumentFont)bf).getIndirectReference(), bf);
981                                     }
982                                     else {
983                                         bf.setSubset(false);
984                                         fd = writer.addSimple(bf);
985                                         localFonts.put(psn.toString().substring(1), bf);
986                                     }
987                                     fontsTop.put(psn, fd.getIndirectReference());
988                                     fonts.put(psn, fd.getIndirectReference());
989                                 }
990                                 ByteBuffer buf = cb.getInternalBuffer();
991                                 buf.append(psn.getBytes()).append(' ').append(((Float)dao[DA_SIZE]).floatValue()).append(" Tf ");
992                                 if (dao[DA_COLOR] != null)
993                                     cb.setColorFill((Color)dao[DA_COLOR]);
994                                 PdfString s = new PdfString(cb.toString());
995                                 item.getMerged(k).put(PdfName.DA, s);
996                                 item.getWidget(k).put(PdfName.DA, s);
997                                 markUsed(item.getWidget(k));
998                             }
999                         }
1000                     }
1001                 }
1002             }
1003             else if (name.equalsIgnoreCase("textcolor")) {
1004                 for (int k = 0; k < item.size(); ++k) {
1005                     if (hit.isHit(k)) {
1006                         merged = item.getMerged( k );
1007                         da = merged.getAsString(PdfName.DA);
1008                         if (da != null) {
1009                             Object dao[] = splitDAelements(da.toUnicodeString());
1010                             PdfAppearance cb = new PdfAppearance();
1011                             if (dao[DA_FONT] != null) {
1012                                 ByteBuffer buf = cb.getInternalBuffer();
1013                                 buf.append(new PdfName((String)dao[DA_FONT]).getBytes()).append(' ').append(((Float)dao[DA_SIZE]).floatValue()).append(" Tf ");
1014                                 cb.setColorFill((Color)value);
1015                                 PdfString s = new PdfString(cb.toString());
1016                                 item.getMerged(k).put(PdfName.DA, s);
1017                                 item.getWidget(k).put(PdfName.DA, s);
1018                                 markUsed(item.getWidget(k));
1019                             }
1020                         }
1021                     }
1022                 }
1023             }
1024             else if (name.equalsIgnoreCase("textsize")) {
1025                 for (int k = 0; k < item.size(); ++k) {
1026                     if (hit.isHit(k)) {
1027                         merged = item.getMerged( k );
1028                         da = merged.getAsString(PdfName.DA);
1029                         if (da != null) {
1030                             Object dao[] = splitDAelements(da.toUnicodeString());
1031                             PdfAppearance cb = new PdfAppearance();
1032                             if (dao[DA_FONT] != null) {
1033                                 ByteBuffer buf = cb.getInternalBuffer();
1034                                 buf.append(new PdfName((String)dao[DA_FONT]).getBytes()).append(' ').append(((Float)value).floatValue()).append(" Tf ");
1035                                 if (dao[DA_COLOR] != null)
1036                                     cb.setColorFill((Color)dao[DA_COLOR]);
1037                                 PdfString s = new PdfString(cb.toString());
1038                                 item.getMerged(k).put(PdfName.DA, s);
1039                                 item.getWidget(k).put(PdfName.DA, s);
1040                                 markUsed(item.getWidget(k));
1041                             }
1042                         }
1043                     }
1044                 }
1045             }
1046             else if (name.equalsIgnoreCase("bgcolor") || name.equalsIgnoreCase("bordercolor")) {
1047                 PdfName dname = (name.equalsIgnoreCase("bgcolor") ? PdfName.BG : PdfName.BC);
1048                 for (int k = 0; k < item.size(); ++k) {
1049                     if (hit.isHit(k)) {
1050                         merged = item.getMerged( k );
1051                         PdfDictionary mk = merged.getAsDict(PdfName.MK);
1052                         if (mk == null) {
1053                             if (value == null)
1054                                 return true;
1055                             mk = new PdfDictionary();
1056                             item.getMerged(k).put(PdfName.MK, mk);
1057                             item.getWidget(k).put(PdfName.MK, mk);
1058                             markUsed(item.getWidget(k));
1059                         } else {
1060                             markUsed( mk );
1061                         }
1062                         if (value == null)
1063                             mk.remove(dname);
1064                         else
1065                             mk.put(dname, PdfFormField.getMKColor((Color)value));
1066                     }
1067                 }
1068             }
1069             else
1070                 return false;
1071             return true;
1072         }
1073         catch (Exception e) {
1074             throw new ExceptionConverter(e);
1075         }
1076     }
1077 
1078     /**
1079      * Sets a field property. Valid property names are:
1080      * <p>
1081      * <ul>
1082      * <li>flags - a set of flags specifying various characteristics of the field's widget annotation.
1083      * The value of this entry replaces that of the F entry in the form's corresponding annotation dictionary.<br>
1084      * <li>setflags - a set of flags to be set (turned on) in the F entry of the form's corresponding
1085      * widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 1.<br>
1086      * <li>clrflags - a set of flags to be cleared (turned off) in the F entry of the form's corresponding
1087      * widget annotation dictionary. Bits equal to 1 cause the corresponding
1088      * bits in F to be set to 0.<br>
1089      * <li>fflags - a set of flags specifying various characteristics of the field. The value
1090      * of this entry replaces that of the Ff entry in the form's corresponding field dictionary.<br>
1091      * <li>setfflags - a set of flags to be set (turned on) in the Ff entry of the form's corresponding
1092      * field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 1.<br>
1093      * <li>clrfflags - a set of flags to be cleared (turned off) in the Ff entry of the form's corresponding
1094      * field dictionary. Bits equal to 1 cause the corresponding bits in Ff
1095      * to be set to 0.<br>
1096      * </ul>
1097      *
1098      * @param field the field name
1099      * @param name the property name
1100      * @param value the property value
1101      * @param inst an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process.
1102      * Set to <CODE>null</CODE> to process all
1103      * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise
1104      */
setFieldProperty(String field, String name, int value, int inst[])1105     public boolean setFieldProperty(String field, String name, int value, int inst[]) {
1106         if (writer == null)
1107             throw new RuntimeException("This AcroFields instance is read-only.");
1108         Item item = (Item)fields.get(field);
1109         if (item == null)
1110             return false;
1111         InstHit hit = new InstHit(inst);
1112         if (name.equalsIgnoreCase("flags")) {
1113             PdfNumber num = new PdfNumber(value);
1114             for (int k = 0; k < item.size(); ++k) {
1115                 if (hit.isHit(k)) {
1116                     item.getMerged(k).put(PdfName.F, num);
1117                     item.getWidget(k).put(PdfName.F, num);
1118                     markUsed(item.getWidget(k));
1119                 }
1120             }
1121         }
1122         else if (name.equalsIgnoreCase("setflags")) {
1123             for (int k = 0; k < item.size(); ++k) {
1124                 if (hit.isHit(k)) {
1125                     PdfNumber num = item.getWidget(k).getAsNumber(PdfName.F);
1126                     int val = 0;
1127                     if (num != null)
1128                         val = num.intValue();
1129                     num = new PdfNumber(val | value);
1130                     item.getMerged(k).put(PdfName.F, num);
1131                     item.getWidget(k).put(PdfName.F, num);
1132                     markUsed(item.getWidget(k));
1133                 }
1134             }
1135         }
1136         else if (name.equalsIgnoreCase("clrflags")) {
1137             for (int k = 0; k < item.size(); ++k) {
1138                 if (hit.isHit(k)) {
1139                     PdfDictionary widget = item.getWidget( k );
1140                     PdfNumber num = widget.getAsNumber(PdfName.F);
1141                     int val = 0;
1142                     if (num != null)
1143                         val = num.intValue();
1144                     num = new PdfNumber(val & (~value));
1145                     item.getMerged(k).put(PdfName.F, num);
1146                     widget.put(PdfName.F, num);
1147                     markUsed(widget);
1148                 }
1149             }
1150         }
1151         else if (name.equalsIgnoreCase("fflags")) {
1152             PdfNumber num = new PdfNumber(value);
1153             for (int k = 0; k < item.size(); ++k) {
1154                 if (hit.isHit(k)) {
1155                     item.getMerged(k).put(PdfName.FF, num);
1156                     item.getValue(k).put(PdfName.FF, num);
1157                     markUsed(item.getValue(k));
1158                 }
1159             }
1160         }
1161         else if (name.equalsIgnoreCase("setfflags")) {
1162             for (int k = 0; k < item.size(); ++k) {
1163                 if (hit.isHit(k)) {
1164                     PdfDictionary valDict = item.getValue( k );
1165                     PdfNumber num = valDict.getAsNumber( PdfName.FF );
1166                     int val = 0;
1167                     if (num != null)
1168                         val = num.intValue();
1169                     num = new PdfNumber(val | value);
1170                     item.getMerged(k).put(PdfName.FF, num);
1171                     valDict.put(PdfName.FF, num);
1172                     markUsed(valDict);
1173                 }
1174             }
1175         }
1176         else if (name.equalsIgnoreCase("clrfflags")) {
1177             for (int k = 0; k < item.size(); ++k) {
1178                 if (hit.isHit(k)) {
1179                     PdfDictionary valDict = item.getValue( k );
1180                     PdfNumber num = valDict.getAsNumber(PdfName.FF);
1181                     int val = 0;
1182                     if (num != null)
1183                         val = num.intValue();
1184                     num = new PdfNumber(val & (~value));
1185                     item.getMerged(k).put(PdfName.FF, num);
1186                     valDict.put(PdfName.FF, num);
1187                     markUsed(valDict);
1188                 }
1189             }
1190         }
1191         else
1192             return false;
1193         return true;
1194     }
1195 
1196     /**
1197      * Merges an XML data structure into this form.
1198      *
1199      * @param n the top node of the data structure
1200      * @throws java.io.IOException on error
1201      * @throws com.lowagie.text.DocumentException o error
1202      */
mergeXfaData(Node n)1203     public void mergeXfaData(Node n) throws IOException, DocumentException {
1204         XfaForm.Xml2SomDatasets data = new XfaForm.Xml2SomDatasets(n);
1205         for (Iterator it = data.getOrder().iterator(); it.hasNext();) {
1206             String name = (String)it.next();
1207             String text = XfaForm.getNodeText((Node)data.getName2Node().get(name));
1208             setField(name, text);
1209         }
1210     }
1211 
1212     /**
1213      * Sets the fields by FDF merging.
1214      *
1215      * @param fdf the FDF form
1216      * @throws IOException on error
1217      * @throws DocumentException on error
1218      */
setFields(FdfReader fdf)1219     public void setFields(FdfReader fdf) throws IOException, DocumentException {
1220         HashMap fd = fdf.getFields();
1221         for (Iterator i = fd.keySet().iterator(); i.hasNext();) {
1222             String f = (String)i.next();
1223             String v = fdf.getFieldValue(f);
1224             if (v != null)
1225                 setField(f, v);
1226         }
1227     }
1228 
1229     /**
1230      * Sets the fields by XFDF merging.
1231      *
1232      * @param xfdf the XFDF form
1233      * @throws IOException on error
1234      * @throws DocumentException on error
1235      */
setFields(XfdfReader xfdf)1236     public void setFields(XfdfReader xfdf) throws IOException, DocumentException {
1237         HashMap fd = xfdf.getFields();
1238         for (Iterator i = fd.keySet().iterator(); i.hasNext();) {
1239             String f = (String)i.next();
1240             String v = xfdf.getFieldValue(f);
1241             if (v != null)
1242                 setField(f, v);
1243             List l = xfdf.getListValues(f);
1244             if (l != null)
1245             	setListSelection(v, (String[])l.toArray(new String[l.size()]));
1246         }
1247     }
1248 
1249     /**
1250      * Regenerates the field appearance.
1251      * This is useful when you change a field property, but not its value,
1252      * for instance form.setFieldProperty("f", "bgcolor", Color.BLUE, null);
1253      * This won't have any effect, unless you use regenerateField("f") after changing
1254      * the property.
1255      *
1256      * @param name the fully qualified field name or the partial name in the case of XFA forms
1257      * @throws IOException on error
1258      * @throws DocumentException on error
1259      * @return <CODE>true</CODE> if the field was found and changed,
1260      * <CODE>false</CODE> otherwise
1261      */
regenerateField(String name)1262     public boolean regenerateField(String name) throws IOException, DocumentException {
1263     	String value = getField(name);
1264         return setField(name, value, value);
1265     }
1266 
1267     /**
1268      * Sets the field value.
1269      *
1270      * @param name the fully qualified field name or the partial name in the case of XFA forms
1271      * @param value the field value
1272      * @throws IOException on error
1273      * @throws DocumentException on error
1274      * @return <CODE>true</CODE> if the field was found and changed,
1275      * <CODE>false</CODE> otherwise
1276      */
setField(String name, String value)1277     public boolean setField(String name, String value) throws IOException, DocumentException {
1278         return setField(name, value, null);
1279     }
1280 
1281     /**
1282      * Sets the field value and the display string. The display string
1283      * is used to build the appearance in the cases where the value
1284      * is modified by Acrobat with JavaScript and the algorithm is
1285      * known.
1286      *
1287      * @param name the fully qualified field name or the partial name in the case of XFA forms
1288      * @param value the field value
1289      * @param display the string that is used for the appearance. If <CODE>null</CODE>
1290      * the <CODE>value</CODE> parameter will be used
1291      * @return <CODE>true</CODE> if the field was found and changed,
1292      * <CODE>false</CODE> otherwise
1293      * @throws IOException on error
1294      * @throws DocumentException on error
1295      */
setField(String name, String value, String display)1296     public boolean setField(String name, String value, String display) throws IOException, DocumentException {
1297         if (writer == null)
1298             throw new DocumentException("This AcroFields instance is read-only.");
1299         if (xfa.isXfaPresent()) {
1300             name = xfa.findFieldName(name, this);
1301             if (name == null)
1302                 return false;
1303             String shortName = XfaForm.Xml2Som.getShortName(name);
1304             Node xn = xfa.findDatasetsNode(shortName);
1305             if (xn == null) {
1306                 xn = xfa.getDatasetsSom().insertNode(xfa.getDatasetsNode(), shortName);
1307             }
1308             xfa.setNodeText(xn, value);
1309         }
1310         Item item = (Item)fields.get(name);
1311         if (item == null)
1312             return false;
1313         PdfDictionary merged = item.getMerged( 0 );
1314         PdfName type = merged.getAsName(PdfName.FT);
1315         if (PdfName.TX.equals(type)) {
1316             PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN);
1317             int len = 0;
1318             if (maxLen != null)
1319                 len = maxLen.intValue();
1320             if (len > 0)
1321                 value = value.substring(0, Math.min(len, value.length()));
1322         }
1323         if (display == null)
1324             display = value;
1325         if (PdfName.TX.equals(type) || PdfName.CH.equals(type)) {
1326             PdfString v = new PdfString(value, PdfObject.TEXT_UNICODE);
1327             for (int idx = 0; idx < item.size(); ++idx) {
1328                 PdfDictionary valueDic = item.getValue(idx);
1329                 valueDic.put(PdfName.V, v);
1330                 valueDic.remove(PdfName.I);
1331                 markUsed(valueDic);
1332                 merged = item.getMerged(idx);
1333                 merged.remove(PdfName.I);
1334                 merged.put(PdfName.V, v);
1335                 PdfDictionary widget = item.getWidget(idx);
1336                 if (generateAppearances) {
1337                     PdfAppearance app = getAppearance(merged, display, name);
1338                     if (PdfName.CH.equals(type)) {
1339                         PdfNumber n = new PdfNumber(topFirst);
1340                         widget.put(PdfName.TI, n);
1341                         merged.put(PdfName.TI, n);
1342                     }
1343                     PdfDictionary appDic = widget.getAsDict(PdfName.AP);
1344                     if (appDic == null) {
1345                         appDic = new PdfDictionary();
1346                         widget.put(PdfName.AP, appDic);
1347                         merged.put(PdfName.AP, appDic);
1348                     }
1349                     appDic.put(PdfName.N, app.getIndirectReference());
1350                     writer.releaseTemplate(app);
1351                 }
1352                 else {
1353                     widget.remove(PdfName.AP);
1354                     merged.remove(PdfName.AP);
1355                 }
1356                 markUsed(widget);
1357             }
1358             return true;
1359         }
1360         else if (PdfName.BTN.equals(type)) {
1361             PdfNumber ff = item.getMerged(0).getAsNumber(PdfName.FF);
1362             int flags = 0;
1363             if (ff != null)
1364                 flags = ff.intValue();
1365             if ((flags & PdfFormField.FF_PUSHBUTTON) != 0) {
1366                 //we'll assume that the value is an image in base64
1367                 Image img;
1368                 try {
1369                     img = Image.getInstance(Base64.decode(value));
1370                 }
1371                 catch (Exception e) {
1372                     return false;
1373                 }
1374                 PushbuttonField pb = getNewPushbuttonFromField(name);
1375                 pb.setImage(img);
1376                 replacePushbuttonField(name, pb.getField());
1377                 return true;
1378             }
1379             PdfName v = new PdfName(value);
1380             ArrayList lopt = new ArrayList();
1381             PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT);
1382             if (opts != null) {
1383                 for (int k = 0; k < opts.size(); ++k) {
1384                     PdfString valStr = opts.getAsString(k);
1385                     if (valStr != null)
1386                         lopt.add(valStr.toUnicodeString());
1387                     else
1388                         lopt.add(null);
1389                 }
1390             }
1391             int vidx = lopt.indexOf(value);
1392             PdfName valt = null;
1393             PdfName vt;
1394             if (vidx >= 0) {
1395                 vt = valt = new PdfName(String.valueOf(vidx));
1396             }
1397             else
1398                 vt = v;
1399             for (int idx = 0; idx < item.size(); ++idx) {
1400                 merged = item.getMerged(idx);
1401                 PdfDictionary widget = item.getWidget(idx);
1402                 PdfDictionary valDict = item.getValue(idx);
1403                 markUsed(item.getValue(idx));
1404                 if (valt != null) {
1405                     PdfString ps = new PdfString(value, PdfObject.TEXT_UNICODE);
1406                     valDict.put(PdfName.V, ps);
1407                     merged.put(PdfName.V, ps);
1408                 }
1409                 else {
1410                     valDict.put(PdfName.V, v);
1411                     merged.put(PdfName.V, v);
1412                 }
1413                 markUsed(widget);
1414                 if (isInAP(widget,  vt)) {
1415                     merged.put(PdfName.AS, vt);
1416                     widget.put(PdfName.AS, vt);
1417                 }
1418                 else {
1419                     merged.put(PdfName.AS, PdfName.Off);
1420                     widget.put(PdfName.AS, PdfName.Off);
1421                 }
1422             }
1423             return true;
1424         }
1425         return false;
1426     }
1427 
1428     /**
1429      * Sets different values in a list selection.
1430      * No appearance is generated yet; nor does the code check if multiple select is allowed.
1431      *
1432      * @param	name	the name of the field
1433      * @param	value	an array with values that need to be selected
1434      * @return	true only if the field value was changed
1435      * @since 2.1.4
1436      */
setListSelection(String name, String[] value)1437 	public boolean setListSelection(String name, String[] value) throws IOException, DocumentException {
1438         Item item = getFieldItem(name);
1439         if (item == null)
1440             return false;
1441         PdfName type = item.getMerged(0).getAsName(PdfName.FT);
1442         if (!PdfName.CH.equals(type)) {
1443         	return false;
1444         }
1445         String[] options = getListOptionExport(name);
1446         PdfArray array = new PdfArray();
1447         for (int i = 0; i < value.length; i++) {
1448         	for (int j = 0; j < options.length; j++) {
1449         		if (options[j].equals(value[i])) {
1450         			array.add(new PdfNumber(j));
1451         		}
1452         	}
1453         }
1454         item.writeToAll(PdfName.I, array, Item.WRITE_MERGED | Item.WRITE_VALUE);
1455         item.writeToAll(PdfName.V, null, Item.WRITE_MERGED | Item.WRITE_VALUE);
1456         item.writeToAll(PdfName.AP, null, Item.WRITE_MERGED | Item.WRITE_WIDGET);
1457         item.markUsed( this, Item.WRITE_VALUE | Item.WRITE_WIDGET );
1458         return true;
1459 	}
1460 
isInAP(PdfDictionary dic, PdfName check)1461     boolean isInAP(PdfDictionary dic, PdfName check) {
1462         PdfDictionary appDic = dic.getAsDict(PdfName.AP);
1463         if (appDic == null)
1464             return false;
1465         PdfDictionary NDic = appDic.getAsDict(PdfName.N);
1466         return (NDic != null && NDic.get(check) != null);
1467     }
1468 
1469     /**
1470      * Gets all the fields. The fields are keyed by the fully qualified field name and
1471      * the value is an instance of <CODE>AcroFields.Item</CODE>.
1472      *
1473      * @return all the fields
1474      */
getFields()1475     public HashMap getFields() {
1476         return fields;
1477     }
1478 
1479     /**
1480      * Gets the field structure.
1481      *
1482      * @param name the name of the field
1483      * @return the field structure or <CODE>null</CODE> if the field
1484      * does not exist
1485      */
getFieldItem(String name)1486     public Item getFieldItem(String name) {
1487         if (xfa.isXfaPresent()) {
1488             name = xfa.findFieldName(name, this);
1489             if (name == null)
1490                 return null;
1491         }
1492         return (Item)fields.get(name);
1493     }
1494 
1495     /**
1496      * Gets the long XFA translated name.
1497      *
1498      * @param name the name of the field
1499      * @return the long field name
1500      */
getTranslatedFieldName(String name)1501     public String getTranslatedFieldName(String name) {
1502         if (xfa.isXfaPresent()) {
1503             String namex = xfa.findFieldName(name, this);
1504             if (namex != null)
1505                 name = namex;
1506         }
1507         return name;
1508     }
1509 
1510     /**
1511      * Gets the field box positions in the document. The return is an array of <CODE>float</CODE>
1512      * multiple of 5. For each of this groups the values are: [page, llx, lly, urx,
1513      * ury]. The coordinates have the page rotation in consideration.
1514      *
1515      * @param name the field name
1516      * @return the positions or <CODE>null</CODE> if field does not exist
1517      */
getFieldPositions(String name)1518     public float[] getFieldPositions(String name) {
1519         Item item = getFieldItem(name);
1520         if (item == null)
1521             return null;
1522         float ret[] = new float[item.size() * 5];
1523         int ptr = 0;
1524         for (int k = 0; k < item.size(); ++k) {
1525             try {
1526                 PdfDictionary wd = item.getWidget(k);
1527                 PdfArray rect = wd.getAsArray(PdfName.RECT);
1528                 if (rect == null)
1529                     continue;
1530                 Rectangle r = PdfReader.getNormalizedRectangle(rect);
1531                 int page = item.getPage(k).intValue();
1532                 int rotation = reader.getPageRotation(page);
1533                 ret[ptr++] = page;
1534                 if (rotation != 0) {
1535                     Rectangle pageSize = reader.getPageSize(page);
1536                     switch (rotation) {
1537                         case 270:
1538                             r = new Rectangle(
1539                                 pageSize.getTop() - r.getBottom(),
1540                                 r.getLeft(),
1541                                 pageSize.getTop() - r.getTop(),
1542                                 r.getRight());
1543                             break;
1544                         case 180:
1545                             r = new Rectangle(
1546                                 pageSize.getRight() - r.getLeft(),
1547                                 pageSize.getTop() - r.getBottom(),
1548                                 pageSize.getRight() - r.getRight(),
1549                                 pageSize.getTop() - r.getTop());
1550                             break;
1551                         case 90:
1552                             r = new Rectangle(
1553                                 r.getBottom(),
1554                                 pageSize.getRight() - r.getLeft(),
1555                                 r.getTop(),
1556                                 pageSize.getRight() - r.getRight());
1557                             break;
1558                     }
1559                     r.normalize();
1560                 }
1561                 ret[ptr++] = r.getLeft();
1562                 ret[ptr++] = r.getBottom();
1563                 ret[ptr++] = r.getRight();
1564                 ret[ptr++] = r.getTop();
1565             }
1566             catch (Exception e) {
1567                 // empty on purpose
1568             }
1569         }
1570         if (ptr < ret.length) {
1571             float ret2[] = new float[ptr];
1572             System.arraycopy(ret, 0, ret2, 0, ptr);
1573             return ret2;
1574         }
1575         return ret;
1576     }
1577 
removeRefFromArray(PdfArray array, PdfObject refo)1578     private int removeRefFromArray(PdfArray array, PdfObject refo) {
1579         if (refo == null || !refo.isIndirect())
1580             return array.size();
1581         PdfIndirectReference ref = (PdfIndirectReference)refo;
1582         for (int j = 0; j < array.size(); ++j) {
1583             PdfObject obj = array.getPdfObject(j);
1584             if (!obj.isIndirect())
1585                 continue;
1586             if (((PdfIndirectReference)obj).getNumber() == ref.getNumber())
1587                 array.remove(j--);
1588         }
1589         return array.size();
1590     }
1591 
1592     /**
1593      * Removes all the fields from <CODE>page</CODE>.
1594      *
1595      * @param page the page to remove the fields from
1596      * @return <CODE>true</CODE> if any field was removed, <CODE>false otherwise</CODE>
1597      */
removeFieldsFromPage(int page)1598     public boolean removeFieldsFromPage(int page) {
1599         if (page < 1)
1600             return false;
1601         String names[] = new String[fields.size()];
1602         fields.keySet().toArray(names);
1603         boolean found = false;
1604         for (int k = 0; k < names.length; ++k) {
1605             boolean fr = removeField(names[k], page);
1606             found = (found || fr);
1607         }
1608         return found;
1609     }
1610 
1611     /**
1612      * Removes a field from the document. If page equals -1 all the fields with this
1613      * <CODE>name</CODE> are removed from the document otherwise only the fields in
1614      * that particular page are removed.
1615      *
1616      * @param name the field name
1617      * @param page the page to remove the field from or -1 to remove it from all the pages
1618      * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE>
1619      */
removeField(String name, int page)1620     public boolean removeField(String name, int page) {
1621         Item item = getFieldItem(name);
1622         if (item == null)
1623             return false;
1624         PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(reader.getCatalog().get(PdfName.ACROFORM), reader.getCatalog());
1625 
1626         if (acroForm == null)
1627             return false;
1628         PdfArray arrayf = acroForm.getAsArray(PdfName.FIELDS);
1629         if (arrayf == null)
1630             return false;
1631         for (int k = 0; k < item.size(); ++k) {
1632             int pageV = item.getPage(k).intValue();
1633             if (page != -1 && page != pageV)
1634                 continue;
1635             PdfIndirectReference ref = item.getWidgetRef(k);
1636             PdfDictionary wd = item.getWidget( k );
1637             PdfDictionary pageDic = reader.getPageN(pageV);
1638             PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
1639             if (annots != null) {
1640                 if (removeRefFromArray(annots, ref) == 0) {
1641                     pageDic.remove(PdfName.ANNOTS);
1642                     markUsed(pageDic);
1643                 }
1644                 else
1645                     markUsed(annots);
1646             }
1647             PdfReader.killIndirect(ref);
1648             PdfIndirectReference kid = ref;
1649             while ((ref = wd.getAsIndirectObject(PdfName.PARENT)) != null) {
1650                 wd = wd.getAsDict( PdfName.PARENT );
1651                 PdfArray kids = wd.getAsArray(PdfName.KIDS);
1652                 if (removeRefFromArray(kids, kid) != 0)
1653                     break;
1654                 kid = ref;
1655                 PdfReader.killIndirect(ref);
1656             }
1657             if (ref == null) {
1658                 removeRefFromArray(arrayf, kid);
1659                 markUsed(arrayf);
1660             }
1661             if (page != -1) {
1662                 item.remove( k );
1663                 --k;
1664             }
1665         }
1666         if (page == -1 || item.size() == 0)
1667             fields.remove(name);
1668         return true;
1669     }
1670 
1671     /**
1672      * Removes a field from the document.
1673      *
1674      * @param name the field name
1675      * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE>
1676      */
removeField(String name)1677     public boolean removeField(String name) {
1678         return removeField(name, -1);
1679     }
1680 
1681     /**
1682      * Gets the property generateAppearances.
1683      *
1684      * @return the property generateAppearances
1685      */
isGenerateAppearances()1686     public boolean isGenerateAppearances() {
1687         return generateAppearances;
1688     }
1689 
1690     /**
1691      * Sets the option to generate appearances. Not generating appearances
1692      * will speed-up form filling but the results can be
1693      * unexpected in Acrobat. Don't use it unless your environment is well
1694      * controlled. The default is <CODE>true</CODE>.
1695      *
1696      * @param generateAppearances the option to generate appearances
1697      */
setGenerateAppearances(boolean generateAppearances)1698     public void setGenerateAppearances(boolean generateAppearances) {
1699         this.generateAppearances = generateAppearances;
1700         PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM);
1701         if (generateAppearances)
1702             top.remove(PdfName.NEEDAPPEARANCES);
1703         else
1704             top.put(PdfName.NEEDAPPEARANCES, PdfBoolean.PDFTRUE);
1705     }
1706 
1707     /** The field representations for retrieval and modification. */
1708     public static class Item {
1709 
1710         /**
1711          * <CODE>writeToAll</CODE> constant.
1712          *
1713          *  @since 2.1.5
1714          */
1715         public static final int WRITE_MERGED = 1;
1716 
1717         /**
1718          * <CODE>writeToAll</CODE> and <CODE>markUsed</CODE> constant.
1719          *
1720          *  @since 2.1.5
1721          */
1722         public static final int WRITE_WIDGET = 2;
1723 
1724         /**
1725          * <CODE>writeToAll</CODE> and <CODE>markUsed</CODE> constant.
1726          *
1727          *  @since 2.1.5
1728          */
1729         public static final int WRITE_VALUE = 4;
1730 
1731         /**
1732          * This function writes the given key/value pair to all the instances
1733          * of merged, widget, and/or value, depending on the <code>writeFlags</code> setting
1734          *
1735          * @since 2.1.5
1736          *
1737          * @param key        you'll never guess what this is for.
1738          * @param value      if value is null, the key will be removed
1739          * @param writeFlags ORed together WRITE_* flags
1740          */
writeToAll(PdfName key, PdfObject value, int writeFlags)1741         public void writeToAll(PdfName key, PdfObject value, int writeFlags) {
1742             int i;
1743             PdfDictionary curDict = null;
1744             if ((writeFlags & WRITE_MERGED) != 0) {
1745                 for (i = 0; i < merged.size(); ++i) {
1746                     curDict = getMerged(i);
1747                     curDict.put(key, value);
1748                 }
1749             }
1750             if ((writeFlags & WRITE_WIDGET) != 0) {
1751                 for (i = 0; i < widgets.size(); ++i) {
1752                     curDict = getWidget(i);
1753                     curDict.put(key, value);
1754                 }
1755             }
1756             if ((writeFlags & WRITE_VALUE) != 0) {
1757                 for (i = 0; i < values.size(); ++i) {
1758                     curDict = getValue(i);
1759                     curDict.put(key, value);
1760                 }
1761             }
1762         }
1763 
1764         /**
1765          * Mark all the item dictionaries used matching the given flags
1766          *
1767          * @since 2.1.5
1768          * @param writeFlags WRITE_MERGED is ignored
1769          */
markUsed( AcroFields parentFields, int writeFlags )1770         public void markUsed( AcroFields parentFields, int writeFlags ) {
1771             if ((writeFlags & WRITE_VALUE) != 0) {
1772                 for (int i = 0; i < size(); ++i) {
1773                     parentFields.markUsed( getValue( i ) );
1774                 }
1775             }
1776             if ((writeFlags & WRITE_WIDGET) != 0) {
1777                 for (int i = 0; i < size(); ++i) {
1778                     parentFields.markUsed(getWidget(i));
1779                 }
1780             }
1781         }
1782 
1783         /**
1784          * An array of <CODE>PdfDictionary</CODE> where the value tag /V
1785          * is present.
1786          *
1787          * @deprecated (will remove 'public' in the future)
1788          */
1789         public ArrayList values = new ArrayList();
1790 
1791         /**
1792          * An array of <CODE>PdfDictionary</CODE> with the widgets.
1793          *
1794          * @deprecated (will remove 'public' in the future)
1795          */
1796         public ArrayList widgets = new ArrayList();
1797 
1798         /**
1799          * An array of <CODE>PdfDictionary</CODE> with the widget references.
1800          *
1801          * @deprecated (will remove 'public' in the future)
1802          */
1803         public ArrayList widget_refs = new ArrayList();
1804 
1805         /**
1806          * An array of <CODE>PdfDictionary</CODE> with all the field
1807          * and widget tags merged.
1808          *
1809          * @deprecated (will remove 'public' in the future)
1810          */
1811         public ArrayList merged = new ArrayList();
1812 
1813         /**
1814          * An array of <CODE>Integer</CODE> with the page numbers where
1815          * the widgets are displayed.
1816          *
1817          * @deprecated (will remove 'public' in the future)
1818          */
1819         public ArrayList page = new ArrayList();
1820         /**
1821          * An array of <CODE>Integer</CODE> with the tab order of the field in the page.
1822          *
1823          * @deprecated (will remove 'public' in the future)
1824          */
1825         public ArrayList tabOrder = new ArrayList();
1826 
1827         /**
1828          * Preferred method of determining the number of instances
1829          * of a given field.
1830          *
1831          * @since 2.1.5
1832          * @return number of instances
1833          */
size()1834         public int size() {
1835             return values.size();
1836         }
1837 
1838         /**
1839          * Remove the given instance from this item.  It is possible to
1840          * remove all instances using this function.
1841          *
1842          * @since 2.1.5
1843          * @param killIdx
1844          */
remove(int killIdx)1845         void remove(int killIdx) {
1846             values.remove(killIdx);
1847             widgets.remove(killIdx);
1848             widget_refs.remove(killIdx);
1849             merged.remove(killIdx);
1850             page.remove(killIdx);
1851             tabOrder.remove(killIdx);
1852         }
1853 
1854         /**
1855          * Retrieve the value dictionary of the given instance
1856          *
1857          * @since 2.1.5
1858          * @param idx instance index
1859          * @return dictionary storing this instance's value.  It may be shared across instances.
1860          */
getValue(int idx)1861         public PdfDictionary getValue(int idx) {
1862             return (PdfDictionary) values.get(idx);
1863         }
1864 
1865         /**
1866          * Add a value dict to this Item
1867          *
1868          * @since 2.1.5
1869          * @param value new value dictionary
1870          */
addValue(PdfDictionary value)1871         void addValue(PdfDictionary value) {
1872             values.add(value);
1873         }
1874 
1875         /**
1876          * Retrieve the widget dictionary of the given instance
1877          *
1878          * @since 2.1.5
1879          * @param idx instance index
1880          * @return The dictionary found in the appropriate page's Annot array.
1881          */
getWidget(int idx)1882         public PdfDictionary getWidget(int idx) {
1883             return (PdfDictionary) widgets.get(idx);
1884         }
1885 
1886         /**
1887          * Add a widget dict to this Item
1888          *
1889          * @since 2.1.5
1890          * @param widget
1891          */
addWidget(PdfDictionary widget)1892         void addWidget(PdfDictionary widget) {
1893             widgets.add(widget);
1894         }
1895 
1896         /**
1897          * Retrieve the reference to the given instance
1898          *
1899          * @since 2.1.5
1900          * @param idx instance index
1901          * @return reference to the given field instance
1902          */
getWidgetRef(int idx)1903         public PdfIndirectReference getWidgetRef(int idx) {
1904             return (PdfIndirectReference) widget_refs.get(idx);
1905         }
1906 
1907         /**
1908          * Add a widget ref to this Item
1909          *
1910          * @since 2.1.5
1911          * @param widgRef
1912          */
addWidgetRef(PdfIndirectReference widgRef)1913         void addWidgetRef(PdfIndirectReference widgRef) {
1914             widget_refs.add(widgRef);
1915         }
1916 
1917         /**
1918          * Retrieve the merged dictionary for the given instance.  The merged
1919          * dictionary contains all the keys present in parent fields, though they
1920          * may have been overwritten (or modified?) by children.
1921          * Example: a merged radio field dict will contain /V
1922          *
1923          * @since 2.1.5
1924          * @param idx  instance index
1925          * @return the merged dictionary for the given instance
1926          */
getMerged(int idx)1927         public PdfDictionary getMerged(int idx) {
1928             return (PdfDictionary) merged.get(idx);
1929         }
1930 
1931         /**
1932          * Adds a merged dictionary to this Item.
1933          *
1934          * @since 2.1.5
1935          * @param mergeDict
1936          */
addMerged(PdfDictionary mergeDict)1937         void addMerged(PdfDictionary mergeDict) {
1938             merged.add(mergeDict);
1939         }
1940 
1941         /**
1942          * Retrieve the page number of the given instance
1943          *
1944          * @since 2.1.5
1945          * @param idx
1946          * @return remember, pages are "1-indexed", not "0-indexed" like field instances.
1947          */
getPage(int idx)1948         public Integer getPage(int idx) {
1949             return (Integer) page.get(idx);
1950         }
1951 
1952         /**
1953          * Adds a page to the current Item.
1954          *
1955          * @since 2.1.5
1956          * @param pg
1957          */
addPage(int pg)1958         void addPage(int pg) {
1959             page.add(new Integer(pg));
1960         }
1961 
1962         /**
1963          * forces a page value into the Item.
1964          *
1965          * @since 2.1.5
1966          * @param idx
1967          */
forcePage(int idx, int pg)1968         void forcePage(int idx, int pg) {
1969             page.set(idx, new Integer( pg ));
1970         }
1971 
1972         /**
1973          * Gets the tabOrder.
1974          *
1975          * @since 2.1.5
1976          * @param idx
1977          * @return tab index of the given field instance
1978          */
getTabOrder(int idx)1979         public Integer getTabOrder(int idx) {
1980             return (Integer) tabOrder.get(idx);
1981         }
1982 
1983         /**
1984          * Adds a tab order value to this Item.
1985          *
1986          * @since 2.1.5
1987          * @param order
1988          */
addTabOrder(int order)1989         void addTabOrder(int order) {
1990             tabOrder.add(new Integer(order));
1991         }
1992     }
1993 
1994     private static class InstHit {
1995         IntHashtable hits;
InstHit(int inst[])1996         public InstHit(int inst[]) {
1997             if (inst == null)
1998                 return;
1999             hits = new IntHashtable();
2000             for (int k = 0; k < inst.length; ++k)
2001                 hits.put(inst[k], 1);
2002         }
2003 
isHit(int n)2004         public boolean isHit(int n) {
2005             if (hits == null)
2006                 return true;
2007             return hits.containsKey(n);
2008         }
2009     }
2010 
2011     /**
2012      * Gets the field names that have signatures and are signed.
2013      *
2014      * @return the field names that have signatures and are signed
2015      */
getSignatureNames()2016     public ArrayList getSignatureNames() {
2017         if (sigNames != null)
2018             return new ArrayList(sigNames.keySet());
2019         sigNames = new HashMap();
2020         ArrayList sorter = new ArrayList();
2021         for (Iterator it = fields.entrySet().iterator(); it.hasNext();) {
2022             Map.Entry entry = (Map.Entry)it.next();
2023             Item item = (Item)entry.getValue();
2024             PdfDictionary merged = item.getMerged(0);
2025             if (!PdfName.SIG.equals(merged.get(PdfName.FT)))
2026                 continue;
2027             PdfDictionary v = merged.getAsDict(PdfName.V);
2028             if (v == null)
2029                 continue;
2030             PdfString contents = v.getAsString(PdfName.CONTENTS);
2031             if (contents == null)
2032                 continue;
2033             PdfArray ro = v.getAsArray(PdfName.BYTERANGE);
2034             if (ro == null)
2035                 continue;
2036             int rangeSize = ro.size();
2037             if (rangeSize < 2)
2038                 continue;
2039             int length = ro.getAsNumber(rangeSize - 1).intValue() + ro.getAsNumber(rangeSize - 2).intValue();
2040             sorter.add(new Object[]{entry.getKey(), new int[]{length, 0}});
2041         }
2042         Collections.sort(sorter, new AcroFields.SorterComparator());
2043         if (!sorter.isEmpty()) {
2044             if (((int[])((Object[])sorter.get(sorter.size() - 1))[1])[0] == reader.getFileLength())
2045                 totalRevisions = sorter.size();
2046             else
2047                 totalRevisions = sorter.size() + 1;
2048             for (int k = 0; k < sorter.size(); ++k) {
2049                 Object objs[] = (Object[])sorter.get(k);
2050                 String name = (String)objs[0];
2051                 int p[] = (int[])objs[1];
2052                 p[1] = k + 1;
2053                 sigNames.put(name, p);
2054             }
2055         }
2056         return new ArrayList(sigNames.keySet());
2057     }
2058 
2059     /**
2060      * Gets the field names that have blank signatures.
2061      *
2062      * @return the field names that have blank signatures
2063      */
getBlankSignatureNames()2064     public ArrayList getBlankSignatureNames() {
2065         getSignatureNames();
2066         ArrayList sigs = new ArrayList();
2067         for (Iterator it = fields.entrySet().iterator(); it.hasNext();) {
2068             Map.Entry entry = (Map.Entry)it.next();
2069             Item item = (Item)entry.getValue();
2070             PdfDictionary merged = item.getMerged(0);
2071             if (!PdfName.SIG.equals(merged.getAsName(PdfName.FT)))
2072                 continue;
2073             if (sigNames.containsKey(entry.getKey()))
2074                 continue;
2075             sigs.add(entry.getKey());
2076         }
2077         return sigs;
2078     }
2079 
2080     /**
2081      * Gets the signature dictionary, the one keyed by /V.
2082      *
2083      * @param name the field name
2084      * @return the signature dictionary keyed by /V or <CODE>null</CODE> if the field is not
2085      * a signature
2086      */
getSignatureDictionary(String name)2087     public PdfDictionary getSignatureDictionary(String name) {
2088         getSignatureNames();
2089         name = getTranslatedFieldName(name);
2090         if (!sigNames.containsKey(name))
2091             return null;
2092         Item item = (Item)fields.get(name);
2093         PdfDictionary merged = item.getMerged(0);
2094         return merged.getAsDict(PdfName.V);
2095     }
2096 
2097     /**
2098      * Checks is the signature covers the entire document or just part of it.
2099      *
2100      * @param name the signature field name
2101      * @return <CODE>true</CODE> if the signature covers the entire document,
2102      * <CODE>false</CODE> otherwise
2103      */
signatureCoversWholeDocument(String name)2104     public boolean signatureCoversWholeDocument(String name) {
2105         getSignatureNames();
2106         name = getTranslatedFieldName(name);
2107         if (!sigNames.containsKey(name))
2108             return false;
2109         return ((int[])sigNames.get(name))[0] == reader.getFileLength();
2110     }
2111 
2112     /**
2113      * Verifies a signature. An example usage is:
2114      * <p>
2115      * <pre>
2116      * KeyStore kall = PdfPKCS7.loadCacertsKeyStore();
2117      * PdfReader reader = new PdfReader("my_signed_doc.pdf");
2118      * AcroFields af = reader.getAcroFields();
2119      * ArrayList names = af.getSignatureNames();
2120      * for (int k = 0; k &lt; names.size(); ++k) {
2121      *    String name = (String)names.get(k);
2122      *    System.out.println("Signature name: " + name);
2123      *    System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name));
2124      *    PdfPKCS7 pk = af.verifySignature(name);
2125      *    Calendar cal = pk.getSignDate();
2126      *    Certificate pkc[] = pk.getCertificates();
2127      *    System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate()));
2128      *    System.out.println("Document modified: " + !pk.verify());
2129      *    Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal);
2130      *    if (fails == null)
2131      *        System.out.println("Certificates verified against the KeyStore");
2132      *    else
2133      *        System.out.println("Certificate failed: " + fails[1]);
2134      * }
2135      * </pre>
2136      *
2137      * @param name the signature field name
2138      * @return a <CODE>PdfPKCS7</CODE> class to continue the verification
2139      */
verifySignature(String name)2140     public PdfPKCS7 verifySignature(String name) {
2141         return verifySignature(name, null);
2142     }
2143 
2144     /**
2145      * Verifies a signature. An example usage is:
2146      * <p>
2147      * <pre>
2148      * KeyStore kall = PdfPKCS7.loadCacertsKeyStore();
2149      * PdfReader reader = new PdfReader("my_signed_doc.pdf");
2150      * AcroFields af = reader.getAcroFields();
2151      * ArrayList names = af.getSignatureNames();
2152      * for (int k = 0; k &lt; names.size(); ++k) {
2153      *    String name = (String)names.get(k);
2154      *    System.out.println("Signature name: " + name);
2155      *    System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name));
2156      *    PdfPKCS7 pk = af.verifySignature(name);
2157      *    Calendar cal = pk.getSignDate();
2158      *    Certificate pkc[] = pk.getCertificates();
2159      *    System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate()));
2160      *    System.out.println("Document modified: " + !pk.verify());
2161      *    Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal);
2162      *    if (fails == null)
2163      *        System.out.println("Certificates verified against the KeyStore");
2164      *    else
2165      *        System.out.println("Certificate failed: " + fails[1]);
2166      * }
2167      * </pre>
2168      *
2169      * @param name the signature field name
2170      * @param provider the provider or <code>null</code> for the default provider
2171      * @return a <CODE>PdfPKCS7</CODE> class to continue the verification
2172      */
verifySignature(String name, String provider)2173     public PdfPKCS7 verifySignature(String name, String provider) {
2174         PdfDictionary v = getSignatureDictionary(name);
2175         if (v == null)
2176             return null;
2177         try {
2178             PdfName sub = v.getAsName(PdfName.SUBFILTER);
2179             PdfString contents = v.getAsString(PdfName.CONTENTS);
2180             PdfPKCS7 pk = null;
2181             if (sub.equals(PdfName.ADBE_X509_RSA_SHA1)) {
2182                 PdfString cert = v.getAsString(PdfName.CERT);
2183                 pk = new PdfPKCS7(contents.getOriginalBytes(), cert.getBytes(), provider);
2184             }
2185             else
2186                 pk = new PdfPKCS7(contents.getOriginalBytes(), provider);
2187             updateByteRange(pk, v);
2188             PdfString str = v.getAsString(PdfName.M);
2189             if (str != null)
2190                 pk.setSignDate(PdfDate.decode(str.toString()));
2191             PdfObject obj = PdfReader.getPdfObject(v.get(PdfName.NAME));
2192             if (obj != null) {
2193               if (obj.isString())
2194                 pk.setSignName(((PdfString)obj).toUnicodeString());
2195               else if(obj.isName())
2196                 pk.setSignName(PdfName.decodeName(obj.toString()));
2197             }
2198             str = v.getAsString(PdfName.REASON);
2199             if (str != null)
2200                 pk.setReason(str.toUnicodeString());
2201             str = v.getAsString(PdfName.LOCATION);
2202             if (str != null)
2203                 pk.setLocation(str.toUnicodeString());
2204             return pk;
2205         }
2206         catch (Exception e) {
2207             throw new ExceptionConverter(e);
2208         }
2209     }
2210 
updateByteRange(PdfPKCS7 pkcs7, PdfDictionary v)2211     private void updateByteRange(PdfPKCS7 pkcs7, PdfDictionary v) {
2212         PdfArray b = v.getAsArray(PdfName.BYTERANGE);
2213         RandomAccessFileOrArray rf = reader.getSafeFile();
2214         try {
2215             rf.reOpen();
2216             byte buf[] = new byte[8192];
2217             for (int k = 0; k < b.size(); ++k) {
2218                 int start = b.getAsNumber(k).intValue();
2219                 int length = b.getAsNumber(++k).intValue();
2220                 rf.seek(start);
2221                 while (length > 0) {
2222                     int rd = rf.read(buf, 0, Math.min(length, buf.length));
2223                     if (rd <= 0)
2224                         break;
2225                     length -= rd;
2226                     pkcs7.update(buf, 0, rd);
2227                 }
2228             }
2229         }
2230         catch (Exception e) {
2231             throw new ExceptionConverter(e);
2232         }
2233         finally {
2234             try{rf.close();}catch(Exception e){}
2235         }
2236     }
2237 
markUsed(PdfObject obj)2238     private void markUsed(PdfObject obj) {
2239         if (!append)
2240             return;
2241         ((PdfStamperImp)writer).markUsed(obj);
2242     }
2243 
2244     /**
2245      * Gets the total number of revisions this document has.
2246      *
2247      * @return the total number of revisions
2248      */
getTotalRevisions()2249     public int getTotalRevisions() {
2250         getSignatureNames();
2251         return this.totalRevisions;
2252     }
2253 
2254     /**
2255      * Gets this <CODE>field</CODE> revision.
2256      *
2257      * @param field the signature field name
2258      * @return the revision or zero if it's not a signature field
2259      */
getRevision(String field)2260     public int getRevision(String field) {
2261         getSignatureNames();
2262         field = getTranslatedFieldName(field);
2263         if (!sigNames.containsKey(field))
2264             return 0;
2265         return ((int[])sigNames.get(field))[1];
2266     }
2267 
2268     /**
2269      * Extracts a revision from the document.
2270      *
2271      * @param field the signature field name
2272      * @return an <CODE>InputStream</CODE> covering the revision. Returns <CODE>null</CODE> if
2273      * it's not a signature field
2274      * @throws IOException on error
2275      */
extractRevision(String field)2276     public InputStream extractRevision(String field) throws IOException {
2277         getSignatureNames();
2278         field = getTranslatedFieldName(field);
2279         if (!sigNames.containsKey(field))
2280             return null;
2281         int length = ((int[])sigNames.get(field))[0];
2282         RandomAccessFileOrArray raf = reader.getSafeFile();
2283         raf.reOpen();
2284         raf.seek(0);
2285         return new RevisionStream(raf, length);
2286     }
2287 
2288     /**
2289      * Gets the appearances cache.
2290      *
2291      * @return the appearances cache
2292      * @since	2.1.5	this method used to return a HashMap
2293      */
getFieldCache()2294     public Map getFieldCache() {
2295         return this.fieldCache;
2296     }
2297 
2298     /**
2299      * Sets a cache for field appearances. Parsing the existing PDF to
2300      * create a new TextField is time expensive. For those tasks that repeatedly
2301      * fill the same PDF with different field values the use of the cache has dramatic
2302      * speed advantages. An example usage:
2303      * <p>
2304      * <pre>
2305      * String pdfFile = ...;// the pdf file used as template
2306      * ArrayList xfdfFiles = ...;// the xfdf file names
2307      * ArrayList pdfOutFiles = ...;// the output file names, one for each element in xpdfFiles
2308      * HashMap cache = new HashMap();// the appearances cache
2309      * PdfReader originalReader = new PdfReader(pdfFile);
2310      * for (int k = 0; k &lt; xfdfFiles.size(); ++k) {
2311      *    PdfReader reader = new PdfReader(originalReader);
2312      *    XfdfReader xfdf = new XfdfReader((String)xfdfFiles.get(k));
2313      *    PdfStamper stp = new PdfStamper(reader, new FileOutputStream((String)pdfOutFiles.get(k)));
2314      *    AcroFields af = stp.getAcroFields();
2315      *    af.setFieldCache(cache);
2316      *    af.setFields(xfdf);
2317      *    stp.close();
2318      * }
2319      * </pre>
2320      *
2321      * @param fieldCache a Map that will carry the cached appearances
2322      * @since	2.1.5	this method used to take a HashMap as parameter
2323      */
setFieldCache(Map fieldCache)2324     public void setFieldCache(Map fieldCache) {
2325         this.fieldCache = fieldCache;
2326     }
2327 
2328     /**
2329      * Sets extra margins in text fields to better mimic the Acrobat layout.
2330      *
2331      * @param extraMarginLeft the extra margin left
2332      * @param extraMarginTop the extra margin top
2333      */
setExtraMargin(float extraMarginLeft, float extraMarginTop)2334     public void setExtraMargin(float extraMarginLeft, float extraMarginTop) {
2335         this.extraMarginLeft = extraMarginLeft;
2336         this.extraMarginTop = extraMarginTop;
2337     }
2338 
2339     /**
2340      * Adds a substitution font to the list. The fonts in this list will be used if the original
2341      * font doesn't contain the needed glyphs.
2342      *
2343      * @param font the font
2344      */
addSubstitutionFont(BaseFont font)2345     public void addSubstitutionFont(BaseFont font) {
2346         if (substitutionFonts == null)
2347             substitutionFonts = new ArrayList();
2348         substitutionFonts.add(font);
2349     }
2350 
2351     private static final HashMap stdFieldFontNames = new HashMap();
2352 
2353     /**
2354      * Holds value of property totalRevisions.
2355      */
2356     private int totalRevisions;
2357 
2358     /**
2359      * Holds value of property fieldCache.
2360      *
2361      * @since	2.1.5	this used to be a HashMap
2362      */
2363     private Map fieldCache;
2364 
2365     static {
2366         stdFieldFontNames.put("CoBO", new String[]{"Courier-BoldOblique"});
2367         stdFieldFontNames.put("CoBo", new String[]{"Courier-Bold"});
2368         stdFieldFontNames.put("CoOb", new String[]{"Courier-Oblique"});
2369         stdFieldFontNames.put("Cour", new String[]{"Courier"});
2370         stdFieldFontNames.put("HeBO", new String[]{"Helvetica-BoldOblique"});
2371         stdFieldFontNames.put("HeBo", new String[]{"Helvetica-Bold"});
2372         stdFieldFontNames.put("HeOb", new String[]{"Helvetica-Oblique"});
2373         stdFieldFontNames.put("Helv", new String[]{"Helvetica"});
2374         stdFieldFontNames.put("Symb", new String[]{"Symbol"});
2375         stdFieldFontNames.put("TiBI", new String[]{"Times-BoldItalic"});
2376         stdFieldFontNames.put("TiBo", new String[]{"Times-Bold"});
2377         stdFieldFontNames.put("TiIt", new String[]{"Times-Italic"});
2378         stdFieldFontNames.put("TiRo", new String[]{"Times-Roman"});
2379         stdFieldFontNames.put("ZaDb", new String[]{"ZapfDingbats"});
2380         stdFieldFontNames.put("HySm", new String[]{"HYSMyeongJo-Medium", "UniKS-UCS2-H"});
2381         stdFieldFontNames.put("HyGo", new String[]{"HYGoThic-Medium", "UniKS-UCS2-H"});
2382         stdFieldFontNames.put("KaGo", new String[]{"HeiseiKakuGo-W5", "UniKS-UCS2-H"});
2383         stdFieldFontNames.put("KaMi", new String[]{"HeiseiMin-W3", "UniJIS-UCS2-H"});
2384         stdFieldFontNames.put("MHei", new String[]{"MHei-Medium", "UniCNS-UCS2-H"});
2385         stdFieldFontNames.put("MSun", new String[]{"MSung-Light", "UniCNS-UCS2-H"});
2386         stdFieldFontNames.put("STSo", new String[]{"STSong-Light", "UniGB-UCS2-H"});
2387     }
2388 
2389     private static class RevisionStream extends InputStream {
2390         private byte b[] = new byte[1];
2391         private RandomAccessFileOrArray raf;
2392         private int length;
2393         private int rangePosition = 0;
2394         private boolean closed;
2395 
RevisionStream(RandomAccessFileOrArray raf, int length)2396         private RevisionStream(RandomAccessFileOrArray raf, int length) {
2397             this.raf = raf;
2398             this.length = length;
2399         }
2400 
read()2401         public int read() throws IOException {
2402             int n = read(b);
2403             if (n != 1)
2404                 return -1;
2405             return b[0] & 0xff;
2406         }
2407 
read(byte[] b, int off, int len)2408         public int read(byte[] b, int off, int len) throws IOException {
2409             if (b == null) {
2410                 throw new NullPointerException();
2411             } else if ((off < 0) || (off > b.length) || (len < 0) ||
2412             ((off + len) > b.length) || ((off + len) < 0)) {
2413                 throw new IndexOutOfBoundsException();
2414             } else if (len == 0) {
2415                 return 0;
2416             }
2417             if (rangePosition >= length) {
2418                 close();
2419                 return -1;
2420             }
2421             int elen = Math.min(len, length - rangePosition);
2422             raf.readFully(b, off, elen);
2423             rangePosition += elen;
2424             return elen;
2425         }
2426 
close()2427         public void close() throws IOException {
2428             if (!closed) {
2429                 raf.close();
2430                 closed = true;
2431             }
2432         }
2433     }
2434 
2435     private static class SorterComparator implements Comparator {
compare(Object o1, Object o2)2436         public int compare(Object o1, Object o2) {
2437             int n1 = ((int[])((Object[])o1)[1])[0];
2438             int n2 = ((int[])((Object[])o2)[1])[0];
2439             return n1 - n2;
2440         }
2441     }
2442 
2443     /**
2444      * 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
2445      * font doesn't contain the needed glyphs.
2446      *
2447      * @return the list
2448      */
getSubstitutionFonts()2449     public ArrayList getSubstitutionFonts() {
2450         return substitutionFonts;
2451     }
2452 
2453     /**
2454      * 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
2455      * font doesn't contain the needed glyphs.
2456      *
2457      * @param substitutionFonts the list
2458      */
setSubstitutionFonts(ArrayList substitutionFonts)2459     public void setSubstitutionFonts(ArrayList substitutionFonts) {
2460         this.substitutionFonts = substitutionFonts;
2461     }
2462 
2463     /**
2464      * Gets the XFA form processor.
2465      *
2466      * @return the XFA form processor
2467      */
getXfa()2468     public XfaForm getXfa() {
2469         return xfa;
2470     }
2471 
2472     private static final PdfName[] buttonRemove = {PdfName.MK, PdfName.F , PdfName.FF , PdfName.Q , PdfName.BS , PdfName.BORDER};
2473 
2474     /**
2475      * Creates a new pushbutton from an existing field. If there are several pushbuttons with the same name
2476      * only the first one is used. This pushbutton can be changed and be used to replace
2477      * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton
2478      * call {@link #replacePushbuttonField(String,PdfFormField)}.
2479      *
2480      * @param field the field name that should be a pushbutton
2481      * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton
2482      */
getNewPushbuttonFromField(String field)2483     public PushbuttonField getNewPushbuttonFromField(String field) {
2484         return getNewPushbuttonFromField(field, 0);
2485     }
2486 
2487     /**
2488      * Creates a new pushbutton from an existing field. This pushbutton can be changed and be used to replace
2489      * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton
2490      * call {@link #replacePushbuttonField(String,PdfFormField,int)}.
2491      *
2492      * @param field the field name that should be a pushbutton
2493      * @param order the field order in fields with same name
2494      * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton
2495      *
2496      * @since 2.0.7
2497      */
getNewPushbuttonFromField(String field, int order)2498     public PushbuttonField getNewPushbuttonFromField(String field, int order) {
2499         try {
2500             if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON)
2501                 return null;
2502             Item item = getFieldItem(field);
2503             if (order >= item.size())
2504                 return null;
2505             int posi = order * 5;
2506             float[] pos = getFieldPositions(field);
2507             Rectangle box = new Rectangle(pos[posi + 1], pos[posi + 2], pos[posi + 3], pos[posi + 4]);
2508             PushbuttonField newButton = new PushbuttonField(writer, box, null);
2509             PdfDictionary dic = item.getMerged(order);
2510             decodeGenericDictionary(dic, newButton);
2511             PdfDictionary mk = dic.getAsDict(PdfName.MK);
2512             if (mk != null) {
2513                 PdfString text = mk.getAsString(PdfName.CA);
2514                 if (text != null)
2515                     newButton.setText(text.toUnicodeString());
2516                 PdfNumber tp = mk.getAsNumber(PdfName.TP);
2517                 if (tp != null)
2518                     newButton.setLayout(tp.intValue() + 1);
2519                 PdfDictionary ifit = mk.getAsDict(PdfName.IF);
2520                 if (ifit != null) {
2521                     PdfName sw = ifit.getAsName(PdfName.SW);
2522                     if (sw != null) {
2523                         int scale = PushbuttonField.SCALE_ICON_ALWAYS;
2524                         if (sw.equals(PdfName.B))
2525                             scale = PushbuttonField.SCALE_ICON_IS_TOO_BIG;
2526                         else if (sw.equals(PdfName.S))
2527                             scale = PushbuttonField.SCALE_ICON_IS_TOO_SMALL;
2528                         else if (sw.equals(PdfName.N))
2529                             scale = PushbuttonField.SCALE_ICON_NEVER;
2530                         newButton.setScaleIcon(scale);
2531                     }
2532                     sw = ifit.getAsName(PdfName.S);
2533                     if (sw != null) {
2534                         if (sw.equals(PdfName.A))
2535                             newButton.setProportionalIcon(false);
2536                     }
2537                     PdfArray aj = ifit.getAsArray(PdfName.A);
2538                     if (aj != null && aj.size() == 2) {
2539                         float left = aj.getAsNumber(0).floatValue();
2540                         float bottom = aj.getAsNumber(1).floatValue();
2541                         newButton.setIconHorizontalAdjustment(left);
2542                         newButton.setIconVerticalAdjustment(bottom);
2543                     }
2544                     PdfBoolean fb = ifit.getAsBoolean(PdfName.FB);
2545                     if (fb != null && fb.booleanValue())
2546                         newButton.setIconFitToBounds(true);
2547                 }
2548                 PdfObject i = mk.get(PdfName.I);
2549                 if (i != null && i.isIndirect())
2550                     newButton.setIconReference((PRIndirectReference)i);
2551             }
2552             return newButton;
2553         }
2554         catch (Exception e) {
2555             throw new ExceptionConverter(e);
2556         }
2557     }
2558 
2559     /**
2560      * Replaces the first field with a new pushbutton. The pushbutton can be created with
2561      * {@link #getNewPushbuttonFromField(String)} from the same document or it can be a
2562      * generic PdfFormField of the type pushbutton.
2563      *
2564      * @param field the field name
2565      * @param button the <CODE>PdfFormField</CODE> representing the pushbutton
2566      * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field
2567      * was not a pushbutton
2568      */
replacePushbuttonField(String field, PdfFormField button)2569     public boolean replacePushbuttonField(String field, PdfFormField button) {
2570         return replacePushbuttonField(field, button, 0);
2571     }
2572 
2573     /**
2574      * Replaces the designated field with a new pushbutton. The pushbutton can be created with
2575      * {@link #getNewPushbuttonFromField(String,int)} from the same document or it can be a
2576      * generic PdfFormField of the type pushbutton.
2577      *
2578      * @param field the field name
2579      * @param button the <CODE>PdfFormField</CODE> representing the pushbutton
2580      * @param order the field order in fields with same name
2581      * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field
2582      * was not a pushbutton
2583      *
2584      * @since 2.0.7
2585      */
replacePushbuttonField(String field, PdfFormField button, int order)2586     public boolean replacePushbuttonField(String field, PdfFormField button, int order) {
2587         if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON)
2588             return false;
2589         Item item = getFieldItem(field);
2590         if (order >= item.size())
2591             return false;
2592         PdfDictionary merged = item.getMerged(order);
2593         PdfDictionary values = item.getValue(order);
2594         PdfDictionary widgets = item.getWidget(order);
2595         for (int k = 0; k < buttonRemove.length; ++k) {
2596             merged.remove(buttonRemove[k]);
2597             values.remove(buttonRemove[k]);
2598             widgets.remove(buttonRemove[k]);
2599         }
2600         for (Iterator it = button.getKeys().iterator(); it.hasNext();) {
2601             PdfName key = (PdfName)it.next();
2602             if (key.equals(PdfName.T) || key.equals(PdfName.RECT))
2603                 continue;
2604             if (key.equals(PdfName.FF))
2605                 values.put(key, button.get(key));
2606             else
2607                 widgets.put(key, button.get(key));
2608             merged.put(key, button.get(key));
2609         }
2610         return true;
2611     }
2612 
2613 }
2614