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