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

Save This Page
Home » iText-src-2.1.3 » com.lowagie » text » pdf » [javadoc | source]