0001: /*
0002: * Copyright 2003-2005 by Paulo Soares.
0003: *
0004: * The contents of this file are subject to the Mozilla Public License Version 1.1
0005: * (the "License"); you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
0007: *
0008: * Software distributed under the License is distributed on an "AS IS" basis,
0009: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0010: * for the specific language governing rights and limitations under the License.
0011: *
0012: * The Original Code is 'iText, a free JAVA-PDF library'.
0013: *
0014: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
0015: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
0016: * All Rights Reserved.
0017: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
0018: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
0019: *
0020: * Contributor(s): all the names of the contributors are added in the source code
0021: * where applicable.
0022: *
0023: * Alternatively, the contents of this file may be used under the terms of the
0024: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
0025: * provisions of LGPL are applicable instead of those above. If you wish to
0026: * allow use of your version of this file only under the terms of the LGPL
0027: * License and not to allow others to use your version of this file under
0028: * the MPL, indicate your decision by deleting the provisions above and
0029: * replace them with the notice and other provisions required by the LGPL.
0030: * If you do not delete the provisions above, a recipient may use your version
0031: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
0032: *
0033: * This library is free software; you can redistribute it and/or modify it
0034: * under the terms of the MPL as stated above or under the terms of the GNU
0035: * Library General Public License as published by the Free Software Foundation;
0036: * either version 2 of the License, or any later version.
0037: *
0038: * This library is distributed in the hope that it will be useful, but WITHOUT
0039: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
0040: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
0041: * details.
0042: *
0043: * If you didn't download this code from the following link, you should check if
0044: * you aren't using an obsolete version:
0045: * http://www.lowagie.com/iText/
0046: */
0047: package com.lowagie.text.pdf;
0048:
0049: import java.awt.Color;
0050: import java.io.IOException;
0051: import java.io.InputStream;
0052: import java.util.ArrayList;
0053: import java.util.Collections;
0054: import java.util.Comparator;
0055: import java.util.HashMap;
0056: import java.util.Iterator;
0057: import java.util.Map;
0058:
0059: import org.w3c.dom.Node;
0060:
0061: import com.lowagie.text.DocumentException;
0062: import com.lowagie.text.Element;
0063: import com.lowagie.text.ExceptionConverter;
0064: import com.lowagie.text.Rectangle;
0065:
0066: /** Query and change fields in existing documents either by method
0067: * calls or by FDF merging.
0068: * @author Paulo Soares (psoares@consiste.pt)
0069: */
0070: public class AcroFields {
0071:
0072: PdfReader reader;
0073: PdfWriter writer;
0074: HashMap fields;
0075: private int topFirst;
0076: private HashMap sigNames;
0077: private boolean append;
0078: public static final int DA_FONT = 0;
0079: public static final int DA_SIZE = 1;
0080: public static final int DA_COLOR = 2;
0081: private HashMap extensionFonts = new HashMap();
0082: private XfaForm xfa;
0083: /**
0084: * A field type invalid or not found.
0085: */
0086: public static final int FIELD_TYPE_NONE = 0;
0087: /**
0088: * A field type.
0089: */
0090: public static final int FIELD_TYPE_PUSHBUTTON = 1;
0091: /**
0092: * A field type.
0093: */
0094: public static final int FIELD_TYPE_CHECKBOX = 2;
0095: /**
0096: * A field type.
0097: */
0098: public static final int FIELD_TYPE_RADIOBUTTON = 3;
0099: /**
0100: * A field type.
0101: */
0102: public static final int FIELD_TYPE_TEXT = 4;
0103: /**
0104: * A field type.
0105: */
0106: public static final int FIELD_TYPE_LIST = 5;
0107: /**
0108: * A field type.
0109: */
0110: public static final int FIELD_TYPE_COMBO = 6;
0111: /**
0112: * A field type.
0113: */
0114: public static final int FIELD_TYPE_SIGNATURE = 7;
0115:
0116: private boolean lastWasString;
0117:
0118: /** Holds value of property generateAppearances. */
0119: private boolean generateAppearances = true;
0120:
0121: private HashMap localFonts = new HashMap();
0122:
0123: private float extraMarginLeft;
0124: private float extraMarginTop;
0125: private ArrayList substitutionFonts;
0126:
0127: AcroFields(PdfReader reader, PdfWriter writer) {
0128: this .reader = reader;
0129: this .writer = writer;
0130: try {
0131: xfa = new XfaForm(reader);
0132: } catch (Exception e) {
0133: throw new ExceptionConverter(e);
0134: }
0135: if (writer instanceof PdfStamperImp) {
0136: append = ((PdfStamperImp) writer).isAppend();
0137: }
0138: fill();
0139: }
0140:
0141: void fill() {
0142: fields = new HashMap();
0143: PdfDictionary top = (PdfDictionary) PdfReader
0144: .getPdfObjectRelease(reader.getCatalog().get(
0145: PdfName.ACROFORM));
0146: if (top == null)
0147: return;
0148: PdfArray arrfds = (PdfArray) PdfReader.getPdfObjectRelease(top
0149: .get(PdfName.FIELDS));
0150: if (arrfds == null || arrfds.size() == 0)
0151: return;
0152: arrfds = null;
0153: for (int k = 1; k <= reader.getNumberOfPages(); ++k) {
0154: PdfDictionary page = reader.getPageNRelease(k);
0155: PdfArray annots = (PdfArray) PdfReader.getPdfObjectRelease(
0156: page.get(PdfName.ANNOTS), page);
0157: if (annots == null)
0158: continue;
0159: ArrayList arr = annots.getArrayList();
0160: for (int j = 0; j < arr.size(); ++j) {
0161: PdfObject annoto = PdfReader.getPdfObject(
0162: (PdfObject) arr.get(j), annots);
0163: if (!(annoto instanceof PdfDictionary)) {
0164: PdfReader.releaseLastXrefPartial((PdfObject) arr
0165: .get(j));
0166: continue;
0167: }
0168: PdfDictionary annot = (PdfDictionary) annoto;
0169: if (!PdfName.WIDGET.equals(annot.get(PdfName.SUBTYPE))) {
0170: PdfReader.releaseLastXrefPartial((PdfObject) arr
0171: .get(j));
0172: continue;
0173: }
0174: PdfDictionary widget = annot;
0175: PdfDictionary dic = new PdfDictionary();
0176: dic.putAll(annot);
0177: String name = "";
0178: PdfDictionary value = null;
0179: PdfObject lastV = null;
0180: while (annot != null) {
0181: dic.mergeDifferent(annot);
0182: PdfString t = (PdfString) PdfReader
0183: .getPdfObject(annot.get(PdfName.T));
0184: if (t != null)
0185: name = t.toUnicodeString() + "." + name;
0186: if (lastV == null && annot.get(PdfName.V) != null)
0187: lastV = PdfReader.getPdfObjectRelease(annot
0188: .get(PdfName.V));
0189: if (value == null && t != null) {
0190: value = annot;
0191: if (annot.get(PdfName.V) == null
0192: && lastV != null)
0193: value.put(PdfName.V, lastV);
0194: }
0195: annot = (PdfDictionary) PdfReader.getPdfObject(
0196: annot.get(PdfName.PARENT), annot);
0197: }
0198: if (name.length() > 0)
0199: name = name.substring(0, name.length() - 1);
0200: Item item = (Item) fields.get(name);
0201: if (item == null) {
0202: item = new Item();
0203: fields.put(name, item);
0204: }
0205: if (value == null)
0206: item.values.add(widget);
0207: else
0208: item.values.add(value);
0209: item.widgets.add(widget);
0210: item.widget_refs.add(arr.get(j)); // must be a reference
0211: if (top != null)
0212: dic.mergeDifferent(top);
0213: item.merged.add(dic);
0214: item.page.add(new Integer(k));
0215: item.tabOrder.add(new Integer(j));
0216: }
0217: }
0218: }
0219:
0220: /** Gets the list of appearance names. Use it to get the names allowed
0221: * with radio and checkbox fields. If the /Opt key exists the values will
0222: * also be included. The name 'Off' may also be valid
0223: * even if not returned in the list.
0224: * @param fieldName the fully qualified field name
0225: * @return the list of names or <CODE>null</CODE> if the field does not exist
0226: */
0227: public String[] getAppearanceStates(String fieldName) {
0228: Item fd = (Item) fields.get(fieldName);
0229: if (fd == null)
0230: return null;
0231: HashMap names = new HashMap();
0232: PdfDictionary vals = (PdfDictionary) fd.values.get(0);
0233: PdfObject opts = PdfReader.getPdfObject(vals.get(PdfName.OPT));
0234: if (opts != null) {
0235: if (opts.isString())
0236: names.put(((PdfString) opts).toUnicodeString(), null);
0237: else if (opts.isArray()) {
0238: ArrayList list = ((PdfArray) opts).getArrayList();
0239: for (int k = 0; k < list.size(); ++k) {
0240: PdfObject v = PdfReader
0241: .getPdfObject((PdfObject) list.get(k));
0242: if (v != null && v.isString())
0243: names.put(((PdfString) v).toUnicodeString(),
0244: null);
0245: }
0246: }
0247: }
0248: ArrayList wd = fd.widgets;
0249: for (int k = 0; k < wd.size(); ++k) {
0250: PdfDictionary dic = (PdfDictionary) wd.get(k);
0251: dic = (PdfDictionary) PdfReader.getPdfObject(dic
0252: .get(PdfName.AP));
0253: if (dic == null)
0254: continue;
0255: PdfObject ob = PdfReader.getPdfObject(dic.get(PdfName.N));
0256: if (ob == null || !ob.isDictionary())
0257: continue;
0258: dic = (PdfDictionary) ob;
0259: for (Iterator it = dic.getKeys().iterator(); it.hasNext();) {
0260: String name = PdfName.decodeName(((PdfName) it.next())
0261: .toString());
0262: names.put(name, null);
0263: }
0264: }
0265: String out[] = new String[names.size()];
0266: return (String[]) names.keySet().toArray(out);
0267: }
0268:
0269: private String[] getListOption(String fieldName, int idx) {
0270: Item fd = getFieldItem(fieldName);
0271: if (fd == null)
0272: return null;
0273: PdfObject obj = PdfReader
0274: .getPdfObject(((PdfDictionary) fd.merged.get(0))
0275: .get(PdfName.OPT));
0276: if (obj == null || !obj.isArray())
0277: return null;
0278: PdfArray ar = (PdfArray) obj;
0279: String[] ret = new String[ar.size()];
0280: ArrayList a = ar.getArrayList();
0281: for (int k = 0; k < a.size(); ++k) {
0282: obj = PdfReader.getPdfObject((PdfObject) a.get(k));
0283: try {
0284: if (obj.isArray()) {
0285: obj = (PdfObject) ((PdfArray) obj).getArrayList()
0286: .get(idx);
0287: }
0288: if (obj.isString())
0289: ret[k] = ((PdfString) obj).toUnicodeString();
0290: else
0291: ret[k] = obj.toString();
0292: } catch (Exception e) {
0293: ret[k] = "";
0294: }
0295: }
0296: return ret;
0297: }
0298:
0299: /**
0300: * Gets the list of export option values from fields of type list or combo.
0301: * If the field doesn't exist or the field type is not list or combo it will return
0302: * <CODE>null</CODE>.
0303: * @param fieldName the field name
0304: * @return the list of export option values from fields of type list or combo
0305: */
0306: public String[] getListOptionExport(String fieldName) {
0307: return getListOption(fieldName, 0);
0308: }
0309:
0310: /**
0311: * Gets the list of display option values from fields of type list or combo.
0312: * If the field doesn't exist or the field type is not list or combo it will return
0313: * <CODE>null</CODE>.
0314: * @param fieldName the field name
0315: * @return the list of export option values from fields of type list or combo
0316: */
0317: public String[] getListOptionDisplay(String fieldName) {
0318: return getListOption(fieldName, 1);
0319: }
0320:
0321: /**
0322: * Sets the option list for fields of type list or combo. One of <CODE>exportValues</CODE>
0323: * or <CODE>displayValues</CODE> may be <CODE>null</CODE> but not both. This method will only
0324: * set the list but will not set the value or appearance. For that, calling <CODE>setField()</CODE>
0325: * is required.
0326: * <p>
0327: * An example:
0328: * <p>
0329: * <PRE>
0330: * PdfReader pdf = new PdfReader("input.pdf");
0331: * PdfStamper stp = new PdfStamper(pdf, new FileOutputStream("output.pdf"));
0332: * AcroFields af = stp.getAcroFields();
0333: * af.setListOption("ComboBox", new String[]{"a", "b", "c"}, new String[]{"first", "second", "third"});
0334: * af.setField("ComboBox", "b");
0335: * stp.close();
0336: * </PRE>
0337: * @param fieldName the field name
0338: * @param exportValues the export values
0339: * @param displayValues the display values
0340: * @return <CODE>true</CODE> if the operation succeeded, <CODE>false</CODE> otherwise
0341: */
0342: public boolean setListOption(String fieldName,
0343: String[] exportValues, String[] displayValues) {
0344: if (exportValues == null && displayValues == null)
0345: return false;
0346: if (exportValues != null && displayValues != null
0347: && exportValues.length != displayValues.length)
0348: throw new IllegalArgumentException(
0349: "The export and the display array must have the same size.");
0350: int ftype = getFieldType(fieldName);
0351: if (ftype != FIELD_TYPE_COMBO && ftype != FIELD_TYPE_LIST)
0352: return false;
0353: Item fd = (Item) fields.get(fieldName);
0354: String[] sing = null;
0355: if (exportValues == null && displayValues != null)
0356: sing = displayValues;
0357: else if (exportValues != null && displayValues == null)
0358: sing = exportValues;
0359: PdfArray opt = new PdfArray();
0360: if (sing != null) {
0361: for (int k = 0; k < sing.length; ++k)
0362: opt.add(new PdfString(sing[k], PdfObject.TEXT_UNICODE));
0363: } else {
0364: for (int k = 0; k < exportValues.length; ++k) {
0365: PdfArray a = new PdfArray();
0366: a.add(new PdfString(exportValues[k],
0367: PdfObject.TEXT_UNICODE));
0368: a.add(new PdfString(displayValues[k],
0369: PdfObject.TEXT_UNICODE));
0370: opt.add(a);
0371: }
0372: }
0373: ((PdfDictionary) fd.values.get(0)).put(PdfName.OPT, opt);
0374: for (int j = 0; j < fd.merged.size(); ++j)
0375: ((PdfDictionary) fd.merged.get(j)).put(PdfName.OPT, opt);
0376: return true;
0377: }
0378:
0379: /**
0380: * Gets the field type. The type can be one of: <CODE>FIELD_TYPE_PUSHBUTTON</CODE>,
0381: * <CODE>FIELD_TYPE_CHECKBOX</CODE>, <CODE>FIELD_TYPE_RADIOBUTTON</CODE>,
0382: * <CODE>FIELD_TYPE_TEXT</CODE>, <CODE>FIELD_TYPE_LIST</CODE>,
0383: * <CODE>FIELD_TYPE_COMBO</CODE> or <CODE>FIELD_TYPE_SIGNATURE</CODE>.
0384: * <p>
0385: * If the field does not exist or is invalid it returns
0386: * <CODE>FIELD_TYPE_NONE</CODE>.
0387: * @param fieldName the field name
0388: * @return the field type
0389: */
0390: public int getFieldType(String fieldName) {
0391: Item fd = getFieldItem(fieldName);
0392: if (fd == null)
0393: return FIELD_TYPE_NONE;
0394: PdfObject type = PdfReader
0395: .getPdfObject(((PdfDictionary) fd.merged.get(0))
0396: .get(PdfName.FT));
0397: if (type == null)
0398: return FIELD_TYPE_NONE;
0399: int ff = 0;
0400: PdfObject ffo = PdfReader
0401: .getPdfObject(((PdfDictionary) fd.merged.get(0))
0402: .get(PdfName.FF));
0403: if (ffo != null && ffo.type() == PdfObject.NUMBER)
0404: ff = ((PdfNumber) ffo).intValue();
0405: if (PdfName.BTN.equals(type)) {
0406: if ((ff & PdfFormField.FF_PUSHBUTTON) != 0)
0407: return FIELD_TYPE_PUSHBUTTON;
0408: if ((ff & PdfFormField.FF_RADIO) != 0)
0409: return FIELD_TYPE_RADIOBUTTON;
0410: else
0411: return FIELD_TYPE_CHECKBOX;
0412: } else if (PdfName.TX.equals(type)) {
0413: return FIELD_TYPE_TEXT;
0414: } else if (PdfName.CH.equals(type)) {
0415: if ((ff & PdfFormField.FF_COMBO) != 0)
0416: return FIELD_TYPE_COMBO;
0417: else
0418: return FIELD_TYPE_LIST;
0419: } else if (PdfName.SIG.equals(type)) {
0420: return FIELD_TYPE_SIGNATURE;
0421: }
0422: return FIELD_TYPE_NONE;
0423: }
0424:
0425: /**
0426: * Export the fields as a FDF.
0427: * @param writer the FDF writer
0428: */
0429: public void exportAsFdf(FdfWriter writer) {
0430: for (Iterator it = fields.entrySet().iterator(); it.hasNext();) {
0431: Map.Entry entry = (Map.Entry) it.next();
0432: Item item = (Item) entry.getValue();
0433: String name = (String) entry.getKey();
0434: PdfObject v = PdfReader
0435: .getPdfObject(((PdfDictionary) item.merged.get(0))
0436: .get(PdfName.V));
0437: if (v == null)
0438: continue;
0439: String value = getField(name);
0440: if (lastWasString)
0441: writer.setFieldAsString(name, value);
0442: else
0443: writer.setFieldAsName(name, value);
0444: }
0445: }
0446:
0447: /**
0448: * Renames a field. Only the last part of the name can be renamed. For example,
0449: * if the original field is "ab.cd.ef" only the "ef" part can be renamed.
0450: * @param oldName the old field name
0451: * @param newName the new field name
0452: * @return <CODE>true</CODE> if the renaming was successful, <CODE>false</CODE>
0453: * otherwise
0454: */
0455: public boolean renameField(String oldName, String newName) {
0456: int idx1 = oldName.lastIndexOf('.') + 1;
0457: int idx2 = newName.lastIndexOf('.') + 1;
0458: if (idx1 != idx2)
0459: return false;
0460: if (!oldName.substring(0, idx1).equals(
0461: newName.substring(0, idx2)))
0462: return false;
0463: if (fields.containsKey(newName))
0464: return false;
0465: Item item = (Item) fields.get(oldName);
0466: if (item == null)
0467: return false;
0468: newName = newName.substring(idx2);
0469: PdfString ss = new PdfString(newName, PdfObject.TEXT_UNICODE);
0470: for (int k = 0; k < item.merged.size(); ++k) {
0471: PdfDictionary dic = (PdfDictionary) item.values.get(k);
0472: dic.put(PdfName.T, ss);
0473: markUsed(dic);
0474: dic = (PdfDictionary) item.merged.get(k);
0475: dic.put(PdfName.T, ss);
0476: }
0477: fields.remove(oldName);
0478: fields.put(newName, item);
0479: return true;
0480: }
0481:
0482: public static Object[] splitDAelements(String da) {
0483: try {
0484: PRTokeniser tk = new PRTokeniser(PdfEncodings
0485: .convertToBytes(da, null));
0486: ArrayList stack = new ArrayList();
0487: Object ret[] = new Object[3];
0488: while (tk.nextToken()) {
0489: if (tk.getTokenType() == PRTokeniser.TK_COMMENT)
0490: continue;
0491: if (tk.getTokenType() == PRTokeniser.TK_OTHER) {
0492: String operator = tk.getStringValue();
0493: if (operator.equals("Tf")) {
0494: if (stack.size() >= 2) {
0495: ret[DA_FONT] = stack.get(stack.size() - 2);
0496: ret[DA_SIZE] = new Float((String) stack
0497: .get(stack.size() - 1));
0498: }
0499: } else if (operator.equals("g")) {
0500: if (stack.size() >= 1) {
0501: float gray = new Float((String) stack
0502: .get(stack.size() - 1))
0503: .floatValue();
0504: if (gray != 0)
0505: ret[DA_COLOR] = new GrayColor(gray);
0506: }
0507: } else if (operator.equals("rg")) {
0508: if (stack.size() >= 3) {
0509: float red = new Float((String) stack
0510: .get(stack.size() - 3))
0511: .floatValue();
0512: float green = new Float((String) stack
0513: .get(stack.size() - 2))
0514: .floatValue();
0515: float blue = new Float((String) stack
0516: .get(stack.size() - 1))
0517: .floatValue();
0518: ret[DA_COLOR] = new Color(red, green, blue);
0519: }
0520: } else if (operator.equals("k")) {
0521: if (stack.size() >= 4) {
0522: float cyan = new Float((String) stack
0523: .get(stack.size() - 4))
0524: .floatValue();
0525: float magenta = new Float((String) stack
0526: .get(stack.size() - 3))
0527: .floatValue();
0528: float yellow = new Float((String) stack
0529: .get(stack.size() - 2))
0530: .floatValue();
0531: float black = new Float((String) stack
0532: .get(stack.size() - 1))
0533: .floatValue();
0534: ret[DA_COLOR] = new CMYKColor(cyan,
0535: magenta, yellow, black);
0536: }
0537: }
0538: stack.clear();
0539: } else
0540: stack.add(tk.getStringValue());
0541: }
0542: return ret;
0543: } catch (IOException ioe) {
0544: throw new ExceptionConverter(ioe);
0545: }
0546: }
0547:
0548: public void decodeGenericDictionary(PdfDictionary merged,
0549: BaseField tx) throws IOException, DocumentException {
0550: int flags = 0;
0551: // the text size and color
0552: PdfString da = (PdfString) PdfReader.getPdfObject(merged
0553: .get(PdfName.DA));
0554: if (da != null) {
0555: Object dab[] = splitDAelements(da.toUnicodeString());
0556: if (dab[DA_SIZE] != null)
0557: tx.setFontSize(((Float) dab[DA_SIZE]).floatValue());
0558: if (dab[DA_COLOR] != null)
0559: tx.setTextColor((Color) dab[DA_COLOR]);
0560: if (dab[DA_FONT] != null) {
0561: PdfDictionary font = (PdfDictionary) PdfReader
0562: .getPdfObject(merged.get(PdfName.DR));
0563: if (font != null) {
0564: font = (PdfDictionary) PdfReader.getPdfObject(font
0565: .get(PdfName.FONT));
0566: if (font != null) {
0567: PdfObject po = font.get(new PdfName(
0568: (String) dab[DA_FONT]));
0569: if (po != null
0570: && po.type() == PdfObject.INDIRECT) {
0571: PRIndirectReference por = (PRIndirectReference) po;
0572: BaseFont bp = new DocumentFont(
0573: (PRIndirectReference) po);
0574: tx.setFont(bp);
0575: Integer porkey = new Integer(por
0576: .getNumber());
0577: BaseFont porf = (BaseFont) extensionFonts
0578: .get(porkey);
0579: if (porf == null) {
0580: if (!extensionFonts.containsKey(porkey)) {
0581: PdfDictionary fo = (PdfDictionary) PdfReader
0582: .getPdfObject(po);
0583: PdfDictionary fd = (PdfDictionary) PdfReader
0584: .getPdfObject(fo
0585: .get(PdfName.FONTDESCRIPTOR));
0586: if (fd != null) {
0587: PRStream prs = (PRStream) PdfReader
0588: .getPdfObject(fd
0589: .get(PdfName.FONTFILE2));
0590: if (prs == null)
0591: prs = (PRStream) PdfReader
0592: .getPdfObject(fd
0593: .get(PdfName.FONTFILE3));
0594: if (prs == null) {
0595: extensionFonts.put(porkey,
0596: null);
0597: } else {
0598: try {
0599: porf = BaseFont
0600: .createFont(
0601: "font.ttf",
0602: BaseFont.IDENTITY_H,
0603: true,
0604: false,
0605: PdfReader
0606: .getStreamBytes(prs),
0607: null);
0608: } catch (Exception e) {
0609: }
0610: extensionFonts.put(porkey,
0611: porf);
0612: }
0613: }
0614: }
0615: }
0616: if (tx instanceof TextField)
0617: ((TextField) tx).setExtensionFont(porf);
0618: } else {
0619: BaseFont bf = (BaseFont) localFonts
0620: .get(dab[DA_FONT]);
0621: if (bf == null) {
0622: String fn[] = (String[]) stdFieldFontNames
0623: .get(dab[DA_FONT]);
0624: if (fn != null) {
0625: try {
0626: String enc = "winansi";
0627: if (fn.length > 1)
0628: enc = fn[1];
0629: bf = BaseFont.createFont(fn[0],
0630: enc, false);
0631: tx.setFont(bf);
0632: } catch (Exception e) {
0633: // empty
0634: }
0635: }
0636: } else
0637: tx.setFont(bf);
0638: }
0639: }
0640: }
0641: }
0642: }
0643: //rotation, border and backgound color
0644: PdfDictionary mk = (PdfDictionary) PdfReader
0645: .getPdfObject(merged.get(PdfName.MK));
0646: if (mk != null) {
0647: PdfArray ar = (PdfArray) PdfReader.getPdfObject(mk
0648: .get(PdfName.BC));
0649: Color border = getMKColor(ar);
0650: tx.setBorderColor(border);
0651: if (border != null)
0652: tx.setBorderWidth(1);
0653: ar = (PdfArray) PdfReader.getPdfObject(mk.get(PdfName.BG));
0654: tx.setBackgroundColor(getMKColor(ar));
0655: PdfNumber rotation = (PdfNumber) PdfReader.getPdfObject(mk
0656: .get(PdfName.R));
0657: if (rotation != null)
0658: tx.setRotation(rotation.intValue());
0659: }
0660: //flags
0661: PdfNumber nfl = (PdfNumber) PdfReader.getPdfObject(merged
0662: .get(PdfName.F));
0663: flags = 0;
0664: if (nfl != null) {
0665: flags = nfl.intValue();
0666: if ((flags & PdfFormField.FLAGS_PRINT) != 0
0667: && (flags & PdfFormField.FLAGS_HIDDEN) != 0)
0668: tx.setVisibility(BaseField.HIDDEN);
0669: else if ((flags & PdfFormField.FLAGS_PRINT) != 0
0670: && (flags & PdfFormField.FLAGS_NOVIEW) != 0)
0671: tx.setVisibility(BaseField.HIDDEN_BUT_PRINTABLE);
0672: else if ((flags & PdfFormField.FLAGS_PRINT) != 0)
0673: tx.setVisibility(BaseField.VISIBLE);
0674: else
0675: tx.setVisibility(BaseField.VISIBLE_BUT_DOES_NOT_PRINT);
0676: }
0677: //multiline
0678: nfl = (PdfNumber) PdfReader
0679: .getPdfObject(merged.get(PdfName.FF));
0680: flags = 0;
0681: if (nfl != null)
0682: flags = nfl.intValue();
0683: tx.setOptions(flags);
0684: if ((flags & PdfFormField.FF_COMB) != 0) {
0685: PdfNumber maxLen = (PdfNumber) PdfReader
0686: .getPdfObject(merged.get(PdfName.MAXLEN));
0687: int len = 0;
0688: if (maxLen != null)
0689: len = maxLen.intValue();
0690: tx.setMaxCharacterLength(len);
0691: }
0692: //alignment
0693: nfl = (PdfNumber) PdfReader.getPdfObject(merged.get(PdfName.Q));
0694: if (nfl != null) {
0695: if (nfl.intValue() == PdfFormField.Q_CENTER)
0696: tx.setAlignment(Element.ALIGN_CENTER);
0697: else if (nfl.intValue() == PdfFormField.Q_RIGHT)
0698: tx.setAlignment(Element.ALIGN_RIGHT);
0699: }
0700: //border styles
0701: PdfDictionary bs = (PdfDictionary) PdfReader
0702: .getPdfObject(merged.get(PdfName.BS));
0703: if (bs != null) {
0704: PdfNumber w = (PdfNumber) PdfReader.getPdfObject(bs
0705: .get(PdfName.W));
0706: if (w != null)
0707: tx.setBorderWidth(w.floatValue());
0708: PdfName s = (PdfName) PdfReader.getPdfObject(bs
0709: .get(PdfName.S));
0710: if (PdfName.D.equals(s))
0711: tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
0712: else if (PdfName.B.equals(s))
0713: tx.setBorderStyle(PdfBorderDictionary.STYLE_BEVELED);
0714: else if (PdfName.I.equals(s))
0715: tx.setBorderStyle(PdfBorderDictionary.STYLE_INSET);
0716: else if (PdfName.U.equals(s))
0717: tx.setBorderStyle(PdfBorderDictionary.STYLE_UNDERLINE);
0718: } else {
0719: PdfArray bd = (PdfArray) PdfReader.getPdfObject(merged
0720: .get(PdfName.BORDER));
0721: if (bd != null) {
0722: ArrayList ar = bd.getArrayList();
0723: if (ar.size() >= 3)
0724: tx.setBorderWidth(((PdfNumber) ar.get(2))
0725: .floatValue());
0726: if (ar.size() >= 4)
0727: tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
0728: }
0729: }
0730: }
0731:
0732: PdfAppearance getAppearance(PdfDictionary merged, String text,
0733: String fieldName) throws IOException, DocumentException {
0734: topFirst = 0;
0735: TextField tx = null;
0736: if (fieldCache == null || !fieldCache.containsKey(fieldName)) {
0737: tx = new TextField(writer, null, null);
0738: tx.setExtraMargin(extraMarginLeft, extraMarginTop);
0739: tx.setBorderWidth(0);
0740: tx.setSubstitutionFonts(substitutionFonts);
0741: decodeGenericDictionary(merged, tx);
0742: //rect
0743: PdfArray rect = (PdfArray) PdfReader.getPdfObject(merged
0744: .get(PdfName.RECT));
0745: Rectangle box = PdfReader.getNormalizedRectangle(rect);
0746: if (tx.getRotation() == 90 || tx.getRotation() == 270)
0747: box = box.rotate();
0748: tx.setBox(box);
0749: if (fieldCache != null)
0750: fieldCache.put(fieldName, tx);
0751: } else {
0752: tx = (TextField) fieldCache.get(fieldName);
0753: tx.setWriter(writer);
0754: }
0755: PdfName fieldType = (PdfName) PdfReader.getPdfObject(merged
0756: .get(PdfName.FT));
0757: if (PdfName.TX.equals(fieldType)) {
0758: tx.setText(text);
0759: return tx.getAppearance();
0760: }
0761: if (!PdfName.CH.equals(fieldType))
0762: throw new DocumentException(
0763: "An appearance was requested without a variable text field.");
0764: PdfArray opt = (PdfArray) PdfReader.getPdfObject(merged
0765: .get(PdfName.OPT));
0766: int flags = 0;
0767: PdfNumber nfl = (PdfNumber) PdfReader.getPdfObject(merged
0768: .get(PdfName.FF));
0769: if (nfl != null)
0770: flags = nfl.intValue();
0771: if ((flags & PdfFormField.FF_COMBO) != 0 && opt == null) {
0772: tx.setText(text);
0773: return tx.getAppearance();
0774: }
0775: if (opt != null) {
0776: ArrayList op = opt.getArrayList();
0777: String choices[] = new String[op.size()];
0778: String choicesExp[] = new String[op.size()];
0779: for (int k = 0; k < op.size(); ++k) {
0780: PdfObject obj = (PdfObject) op.get(k);
0781: if (obj.isString()) {
0782: choices[k] = choicesExp[k] = ((PdfString) obj)
0783: .toUnicodeString();
0784: } else {
0785: ArrayList opar = ((PdfArray) obj).getArrayList();
0786: choicesExp[k] = ((PdfString) opar.get(0))
0787: .toUnicodeString();
0788: choices[k] = ((PdfString) opar.get(1))
0789: .toUnicodeString();
0790: }
0791: }
0792: if ((flags & PdfFormField.FF_COMBO) != 0) {
0793: for (int k = 0; k < choices.length; ++k) {
0794: if (text.equals(choicesExp[k])) {
0795: text = choices[k];
0796: break;
0797: }
0798: }
0799: tx.setText(text);
0800: return tx.getAppearance();
0801: }
0802: int idx = 0;
0803: for (int k = 0; k < choicesExp.length; ++k) {
0804: if (text.equals(choicesExp[k])) {
0805: idx = k;
0806: break;
0807: }
0808: }
0809: tx.setChoices(choices);
0810: tx.setChoiceExports(choicesExp);
0811: tx.setChoiceSelection(idx);
0812: }
0813: PdfAppearance app = tx.getListAppearance();
0814: topFirst = tx.getTopFirst();
0815: return app;
0816: }
0817:
0818: Color getMKColor(PdfArray ar) {
0819: if (ar == null)
0820: return null;
0821: ArrayList cc = ar.getArrayList();
0822: switch (cc.size()) {
0823: case 1:
0824: return new GrayColor(((PdfNumber) cc.get(0)).floatValue());
0825: case 3:
0826: return new Color(ExtendedColor.normalize(((PdfNumber) cc
0827: .get(0)).floatValue()), ExtendedColor
0828: .normalize(((PdfNumber) cc.get(1)).floatValue()),
0829: ExtendedColor.normalize(((PdfNumber) cc.get(2))
0830: .floatValue()));
0831: case 4:
0832: return new CMYKColor(((PdfNumber) cc.get(0)).floatValue(),
0833: ((PdfNumber) cc.get(1)).floatValue(),
0834: ((PdfNumber) cc.get(2)).floatValue(),
0835: ((PdfNumber) cc.get(3)).floatValue());
0836: default:
0837: return null;
0838: }
0839: }
0840:
0841: /** Gets the field value.
0842: * @param name the fully qualified field name
0843: * @return the field value
0844: */
0845: public String getField(String name) {
0846: if (xfa.isXfaPresent()) {
0847: name = xfa.findFieldName(name, this );
0848: if (name == null)
0849: return null;
0850: name = XfaForm.Xml2Som.getShortName(name);
0851: return XfaForm.getNodeText(xfa.findDatasetsNode(name));
0852: }
0853: Item item = (Item) fields.get(name);
0854: if (item == null)
0855: return null;
0856: lastWasString = false;
0857: PdfObject v = PdfReader
0858: .getPdfObject(((PdfDictionary) item.merged.get(0))
0859: .get(PdfName.V));
0860: if (v == null)
0861: return "";
0862: PdfName type = (PdfName) PdfReader
0863: .getPdfObject(((PdfDictionary) item.merged.get(0))
0864: .get(PdfName.FT));
0865: if (PdfName.BTN.equals(type)) {
0866: PdfNumber ff = (PdfNumber) PdfReader
0867: .getPdfObject(((PdfDictionary) item.merged.get(0))
0868: .get(PdfName.FF));
0869: int flags = 0;
0870: if (ff != null)
0871: flags = ff.intValue();
0872: if ((flags & PdfFormField.FF_PUSHBUTTON) != 0)
0873: return "";
0874: String value = "";
0875: if (v.isName())
0876: value = PdfName.decodeName(v.toString());
0877: else if (v.isString())
0878: value = ((PdfString) v).toUnicodeString();
0879: PdfObject opts = PdfReader
0880: .getPdfObject(((PdfDictionary) item.values.get(0))
0881: .get(PdfName.OPT));
0882: if (opts != null && opts.isArray()) {
0883: ArrayList list = ((PdfArray) opts).getArrayList();
0884: int idx = 0;
0885: try {
0886: idx = Integer.parseInt(value);
0887: PdfString ps = (PdfString) list.get(idx);
0888: value = ps.toUnicodeString();
0889: lastWasString = true;
0890: } catch (Exception e) {
0891: }
0892: }
0893: return value;
0894: }
0895: if (v.isString()) {
0896: lastWasString = true;
0897: return ((PdfString) v).toUnicodeString();
0898: }
0899: return PdfName.decodeName(v.toString());
0900: }
0901:
0902: /**
0903: * Sets a field property. Valid property names are:
0904: * <p>
0905: * <ul>
0906: * <li>textfont - sets the text font. The value for this entry is a <CODE>BaseFont</CODE>.<br>
0907: * <li>textcolor - sets the text color. The value for this entry is a <CODE>java.awt.Color</CODE>.<br>
0908: * <li>textsize - sets the text size. The value for this entry is a <CODE>Float</CODE>.
0909: * <li>bgcolor - sets the background color. The value for this entry is a <CODE>java.awt.Color</CODE>.
0910: * If <code>null</code> removes the background.<br>
0911: * <li>bordercolor - sets the border color. The value for this entry is a <CODE>java.awt.Color</CODE>.
0912: * If <code>null</code> removes the border.<br>
0913: * </ul>
0914: * @param field the field name
0915: * @param name the property name
0916: * @param value the property value
0917: * @param inst an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process.
0918: * Set to <CODE>null</CODE> to process all
0919: * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise
0920: */
0921: public boolean setFieldProperty(String field, String name,
0922: Object value, int inst[]) {
0923: if (writer == null)
0924: throw new RuntimeException(
0925: "This AcroFields instance is read-only.");
0926: try {
0927: Item item = (Item) fields.get(field);
0928: if (item == null)
0929: return false;
0930: InstHit hit = new InstHit(inst);
0931: if (name.equalsIgnoreCase("textfont")) {
0932: for (int k = 0; k < item.merged.size(); ++k) {
0933: if (hit.isHit(k)) {
0934: PdfString da = (PdfString) PdfReader
0935: .getPdfObject(((PdfDictionary) item.merged
0936: .get(k)).get(PdfName.DA));
0937: PdfDictionary dr = (PdfDictionary) PdfReader
0938: .getPdfObject(((PdfDictionary) item.merged
0939: .get(k)).get(PdfName.DR));
0940: if (da != null && dr != null) {
0941: Object dao[] = splitDAelements(da
0942: .toUnicodeString());
0943: PdfAppearance cb = new PdfAppearance();
0944: if (dao[DA_FONT] != null) {
0945: BaseFont bf = (BaseFont) value;
0946: PdfName psn = (PdfName) PdfAppearance.stdFieldFontNames
0947: .get(bf.getPostscriptFontName());
0948: if (psn == null) {
0949: psn = new PdfName(bf
0950: .getPostscriptFontName());
0951: }
0952: PdfDictionary fonts = (PdfDictionary) PdfReader
0953: .getPdfObject(dr
0954: .get(PdfName.FONT));
0955: if (fonts == null) {
0956: fonts = new PdfDictionary();
0957: dr.put(PdfName.FONT, fonts);
0958: }
0959: PdfIndirectReference fref = (PdfIndirectReference) fonts
0960: .get(psn);
0961: PdfDictionary top = (PdfDictionary) PdfReader
0962: .getPdfObject(reader
0963: .getCatalog()
0964: .get(PdfName.ACROFORM));
0965: markUsed(top);
0966: dr = (PdfDictionary) PdfReader
0967: .getPdfObject(top
0968: .get(PdfName.DR));
0969: if (dr == null) {
0970: dr = new PdfDictionary();
0971: top.put(PdfName.DR, dr);
0972: }
0973: markUsed(dr);
0974: PdfDictionary fontsTop = (PdfDictionary) PdfReader
0975: .getPdfObject(dr
0976: .get(PdfName.FONT));
0977: if (fontsTop == null) {
0978: fontsTop = new PdfDictionary();
0979: dr.put(PdfName.FONT, fontsTop);
0980: }
0981: markUsed(fontsTop);
0982: PdfIndirectReference frefTop = (PdfIndirectReference) fontsTop
0983: .get(psn);
0984: if (frefTop != null) {
0985: if (fref == null)
0986: fonts.put(psn, frefTop);
0987: } else if (fref == null) {
0988: FontDetails fd;
0989: if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) {
0990: fd = new FontDetails(
0991: null,
0992: ((DocumentFont) bf)
0993: .getIndirectReference(),
0994: bf);
0995: } else {
0996: bf.setSubset(false);
0997: fd = writer.addSimple(bf);
0998: localFonts.put(psn.toString()
0999: .substring(1), bf);
1000: }
1001: fontsTop.put(psn, fd
1002: .getIndirectReference());
1003: fonts.put(psn, fd
1004: .getIndirectReference());
1005: }
1006: ByteBuffer buf = cb.getInternalBuffer();
1007: buf.append(psn.getBytes()).append(' ')
1008: .append(
1009: ((Float) dao[DA_SIZE])
1010: .floatValue())
1011: .append(" Tf ");
1012: if (dao[DA_COLOR] != null)
1013: cb
1014: .setColorFill((Color) dao[DA_COLOR]);
1015: PdfString s = new PdfString(cb
1016: .toString());
1017: ((PdfDictionary) item.merged.get(k))
1018: .put(PdfName.DA, s);
1019: ((PdfDictionary) item.widgets.get(k))
1020: .put(PdfName.DA, s);
1021: markUsed((PdfDictionary) item.widgets
1022: .get(k));
1023: }
1024: }
1025: }
1026: }
1027: } else if (name.equalsIgnoreCase("textcolor")) {
1028: for (int k = 0; k < item.merged.size(); ++k) {
1029: if (hit.isHit(k)) {
1030: PdfString da = (PdfString) PdfReader
1031: .getPdfObject(((PdfDictionary) item.merged
1032: .get(k)).get(PdfName.DA));
1033: if (da != null) {
1034: Object dao[] = splitDAelements(da
1035: .toUnicodeString());
1036: PdfAppearance cb = new PdfAppearance();
1037: if (dao[DA_FONT] != null) {
1038: ByteBuffer buf = cb.getInternalBuffer();
1039: buf.append(
1040: new PdfName(
1041: (String) dao[DA_FONT])
1042: .getBytes())
1043: .append(' ').append(
1044: ((Float) dao[DA_SIZE])
1045: .floatValue())
1046: .append(" Tf ");
1047: cb.setColorFill((Color) value);
1048: PdfString s = new PdfString(cb
1049: .toString());
1050: ((PdfDictionary) item.merged.get(k))
1051: .put(PdfName.DA, s);
1052: ((PdfDictionary) item.widgets.get(k))
1053: .put(PdfName.DA, s);
1054: markUsed((PdfDictionary) item.widgets
1055: .get(k));
1056: }
1057: }
1058: }
1059: }
1060: } else if (name.equalsIgnoreCase("textsize")) {
1061: for (int k = 0; k < item.merged.size(); ++k) {
1062: if (hit.isHit(k)) {
1063: PdfString da = (PdfString) PdfReader
1064: .getPdfObject(((PdfDictionary) item.merged
1065: .get(k)).get(PdfName.DA));
1066: if (da != null) {
1067: Object dao[] = splitDAelements(da
1068: .toUnicodeString());
1069: PdfAppearance cb = new PdfAppearance();
1070: if (dao[DA_FONT] != null) {
1071: ByteBuffer buf = cb.getInternalBuffer();
1072: buf.append(
1073: new PdfName(
1074: (String) dao[DA_FONT])
1075: .getBytes())
1076: .append(' ').append(
1077: ((Float) value)
1078: .floatValue())
1079: .append(" Tf ");
1080: if (dao[DA_COLOR] != null)
1081: cb
1082: .setColorFill((Color) dao[DA_COLOR]);
1083: PdfString s = new PdfString(cb
1084: .toString());
1085: ((PdfDictionary) item.merged.get(k))
1086: .put(PdfName.DA, s);
1087: ((PdfDictionary) item.widgets.get(k))
1088: .put(PdfName.DA, s);
1089: markUsed((PdfDictionary) item.widgets
1090: .get(k));
1091: }
1092: }
1093: }
1094: }
1095: } else if (name.equalsIgnoreCase("bgcolor")
1096: || name.equalsIgnoreCase("bordercolor")) {
1097: PdfName dname = (name.equalsIgnoreCase("bgcolor") ? PdfName.BG
1098: : PdfName.BC);
1099: for (int k = 0; k < item.merged.size(); ++k) {
1100: if (hit.isHit(k)) {
1101: PdfObject obj = PdfReader
1102: .getPdfObject(((PdfDictionary) item.merged
1103: .get(k)).get(PdfName.MK));
1104: markUsed(obj);
1105: PdfDictionary mk = (PdfDictionary) obj;
1106: if (mk == null) {
1107: if (value == null)
1108: return true;
1109: mk = new PdfDictionary();
1110: ((PdfDictionary) item.merged.get(k)).put(
1111: PdfName.MK, mk);
1112: ((PdfDictionary) item.widgets.get(k)).put(
1113: PdfName.MK, mk);
1114: markUsed((PdfDictionary) item.widgets
1115: .get(k));
1116: }
1117: if (value == null)
1118: mk.remove(dname);
1119: else
1120: mk.put(dname, PdfFormField
1121: .getMKColor((Color) value));
1122: }
1123: }
1124: } else
1125: return false;
1126: return true;
1127: } catch (Exception e) {
1128: throw new ExceptionConverter(e);
1129: }
1130: }
1131:
1132: /**
1133: * Sets a field property. Valid property names are:
1134: * <p>
1135: * <ul>
1136: * <li>flags - a set of flags specifying various characteristics of the field's widget annotation.
1137: * The value of this entry replaces that of the F entry in the form's corresponding annotation dictionary.<br>
1138: * <li>setflags - a set of flags to be set (turned on) in the F entry of the form's corresponding
1139: * widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 1.<br>
1140: * <li>clrflags - a set of flags to be cleared (turned off) in the F entry of the form's corresponding
1141: * widget annotation dictionary. Bits equal to 1 cause the corresponding
1142: * bits in F to be set to 0.<br>
1143: * <li>fflags - a set of flags specifying various characteristics of the field. The value
1144: * of this entry replaces that of the Ff entry in the form's corresponding field dictionary.<br>
1145: * <li>setfflags - a set of flags to be set (turned on) in the Ff entry of the form's corresponding
1146: * field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 1.<br>
1147: * <li>clrfflags - a set of flags to be cleared (turned off) in the Ff entry of the form's corresponding
1148: * field dictionary. Bits equal to 1 cause the corresponding bits in Ff
1149: * to be set to 0.<br>
1150: * </ul>
1151: * @param field the field name
1152: * @param name the property name
1153: * @param value the property value
1154: * @param inst an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process.
1155: * Set to <CODE>null</CODE> to process all
1156: * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise
1157: */
1158: public boolean setFieldProperty(String field, String name,
1159: int value, int inst[]) {
1160: if (writer == null)
1161: throw new RuntimeException(
1162: "This AcroFields instance is read-only.");
1163: Item item = (Item) fields.get(field);
1164: if (item == null)
1165: return false;
1166: InstHit hit = new InstHit(inst);
1167: if (name.equalsIgnoreCase("flags")) {
1168: PdfNumber num = new PdfNumber(value);
1169: for (int k = 0; k < item.merged.size(); ++k) {
1170: if (hit.isHit(k)) {
1171: ((PdfDictionary) item.merged.get(k)).put(PdfName.F,
1172: num);
1173: ((PdfDictionary) item.widgets.get(k)).put(
1174: PdfName.F, num);
1175: markUsed((PdfDictionary) item.widgets.get(k));
1176: }
1177: }
1178: } else if (name.equalsIgnoreCase("setflags")) {
1179: for (int k = 0; k < item.merged.size(); ++k) {
1180: if (hit.isHit(k)) {
1181: PdfNumber num = (PdfNumber) PdfReader
1182: .getPdfObject(((PdfDictionary) item.widgets
1183: .get(k)).get(PdfName.F));
1184: int val = 0;
1185: if (num != null)
1186: val = num.intValue();
1187: num = new PdfNumber(val | value);
1188: ((PdfDictionary) item.merged.get(k)).put(PdfName.F,
1189: num);
1190: ((PdfDictionary) item.widgets.get(k)).put(
1191: PdfName.F, num);
1192: markUsed((PdfDictionary) item.widgets.get(k));
1193: }
1194: }
1195: } else if (name.equalsIgnoreCase("clrflags")) {
1196: for (int k = 0; k < item.merged.size(); ++k) {
1197: if (hit.isHit(k)) {
1198: PdfNumber num = (PdfNumber) PdfReader
1199: .getPdfObject(((PdfDictionary) item.widgets
1200: .get(k)).get(PdfName.F));
1201: int val = 0;
1202: if (num != null)
1203: val = num.intValue();
1204: num = new PdfNumber(val & (~value));
1205: ((PdfDictionary) item.merged.get(k)).put(PdfName.F,
1206: num);
1207: ((PdfDictionary) item.widgets.get(k)).put(
1208: PdfName.F, num);
1209: markUsed((PdfDictionary) item.widgets.get(k));
1210: }
1211: }
1212: } else if (name.equalsIgnoreCase("fflags")) {
1213: PdfNumber num = new PdfNumber(value);
1214: for (int k = 0; k < item.merged.size(); ++k) {
1215: if (hit.isHit(k)) {
1216: ((PdfDictionary) item.merged.get(k)).put(
1217: PdfName.FF, num);
1218: ((PdfDictionary) item.values.get(k)).put(
1219: PdfName.FF, num);
1220: markUsed((PdfDictionary) item.values.get(k));
1221: }
1222: }
1223: } else if (name.equalsIgnoreCase("setfflags")) {
1224: for (int k = 0; k < item.merged.size(); ++k) {
1225: if (hit.isHit(k)) {
1226: PdfNumber num = (PdfNumber) PdfReader
1227: .getPdfObject(((PdfDictionary) item.values
1228: .get(k)).get(PdfName.FF));
1229: int val = 0;
1230: if (num != null)
1231: val = num.intValue();
1232: num = new PdfNumber(val | value);
1233: ((PdfDictionary) item.merged.get(k)).put(
1234: PdfName.FF, num);
1235: ((PdfDictionary) item.values.get(k)).put(
1236: PdfName.FF, num);
1237: markUsed((PdfDictionary) item.values.get(k));
1238: }
1239: }
1240: } else if (name.equalsIgnoreCase("clrfflags")) {
1241: for (int k = 0; k < item.merged.size(); ++k) {
1242: if (hit.isHit(k)) {
1243: PdfNumber num = (PdfNumber) PdfReader
1244: .getPdfObject(((PdfDictionary) item.values
1245: .get(k)).get(PdfName.FF));
1246: int val = 0;
1247: if (num != null)
1248: val = num.intValue();
1249: num = new PdfNumber(val & (~value));
1250: ((PdfDictionary) item.merged.get(k)).put(
1251: PdfName.FF, num);
1252: ((PdfDictionary) item.values.get(k)).put(
1253: PdfName.FF, num);
1254: markUsed((PdfDictionary) item.values.get(k));
1255: }
1256: }
1257: } else
1258: return false;
1259: return true;
1260: }
1261:
1262: /**
1263: * Merges an XML data structure into this form.
1264: * @param n the top node of the data structure
1265: * @throws java.io.IOException on error
1266: * @throws com.lowagie.text.DocumentException o error
1267: */
1268: public void mergeXfaData(Node n) throws IOException,
1269: DocumentException {
1270: XfaForm.Xml2SomDatasets data = new XfaForm.Xml2SomDatasets(n);
1271: for (Iterator it = data.getOrder().iterator(); it.hasNext();) {
1272: String name = (String) it.next();
1273: String text = XfaForm.getNodeText((Node) data
1274: .getName2Node().get(name));
1275: setField(name, text);
1276: }
1277: }
1278:
1279: /** Sets the fields by FDF merging.
1280: * @param fdf the FDF form
1281: * @throws IOException on error
1282: * @throws DocumentException on error
1283: */
1284: public void setFields(FdfReader fdf) throws IOException,
1285: DocumentException {
1286: HashMap fd = fdf.getFields();
1287: for (Iterator i = fd.keySet().iterator(); i.hasNext();) {
1288: String f = (String) i.next();
1289: String v = fdf.getFieldValue(f);
1290: if (v != null)
1291: setField(f, v);
1292: }
1293: }
1294:
1295: /** Sets the fields by XFDF merging.
1296: * @param xfdf the XFDF form
1297: * @throws IOException on error
1298: * @throws DocumentException on error
1299: */
1300:
1301: public void setFields(XfdfReader xfdf) throws IOException,
1302: DocumentException {
1303: HashMap fd = xfdf.getFields();
1304: for (Iterator i = fd.keySet().iterator(); i.hasNext();) {
1305: String f = (String) i.next();
1306: String v = xfdf.getFieldValue(f);
1307: if (v != null)
1308: setField(f, v);
1309: }
1310: }
1311:
1312: /**
1313: * Regenerates the field appearance.
1314: * This is usefull when you change a field property, but not its value,
1315: * for instance form.setFieldProperty("f", "bgcolor", Color.BLUE, null);
1316: * This won't have any effect, unless you use regenerateField("f") after changing
1317: * the property.
1318: *
1319: * @param name the fully qualified field name or the partial name in the case of XFA forms
1320: * @throws IOException on error
1321: * @throws DocumentException on error
1322: * @return <CODE>true</CODE> if the field was found and changed,
1323: * <CODE>false</CODE> otherwise
1324: */
1325: public boolean regenerateField(String name) throws IOException,
1326: DocumentException {
1327: String value = getField(name);
1328: return setField(name, value, value);
1329: }
1330:
1331: /** Sets the field value.
1332: * @param name the fully qualified field name or the partial name in the case of XFA forms
1333: * @param value the field value
1334: * @throws IOException on error
1335: * @throws DocumentException on error
1336: * @return <CODE>true</CODE> if the field was found and changed,
1337: * <CODE>false</CODE> otherwise
1338: */
1339: public boolean setField(String name, String value)
1340: throws IOException, DocumentException {
1341: return setField(name, value, null);
1342: }
1343:
1344: /** Sets the field value and the display string. The display string
1345: * is used to build the appearance in the cases where the value
1346: * is modified by Acrobat with JavaScript and the algorithm is
1347: * known.
1348: * @param name the fully qualified field name or the partial name in the case of XFA forms
1349: * @param value the field value
1350: * @param display the string that is used for the appearance. If <CODE>null</CODE>
1351: * the <CODE>value</CODE> parameter will be used
1352: * @return <CODE>true</CODE> if the field was found and changed,
1353: * <CODE>false</CODE> otherwise
1354: * @throws IOException on error
1355: * @throws DocumentException on error
1356: */
1357: public boolean setField(String name, String value, String display)
1358: throws IOException, DocumentException {
1359: if (writer == null)
1360: throw new DocumentException(
1361: "This AcroFields instance is read-only.");
1362: if (xfa.isXfaPresent()) {
1363: name = xfa.findFieldName(name, this );
1364: if (name == null)
1365: return false;
1366: String shortName = XfaForm.Xml2Som.getShortName(name);
1367: Node xn = xfa.findDatasetsNode(shortName);
1368: if (xn == null) {
1369: xn = xfa.getDatasetsSom().insertNode(
1370: xfa.getDatasetsNode(), shortName);
1371: }
1372: xfa.setNodeText(xn, value);
1373: }
1374: Item item = (Item) fields.get(name);
1375: if (item == null)
1376: return false;
1377: PdfName type = (PdfName) PdfReader
1378: .getPdfObject(((PdfDictionary) item.merged.get(0))
1379: .get(PdfName.FT));
1380: if (PdfName.TX.equals(type)) {
1381: PdfNumber maxLen = (PdfNumber) PdfReader
1382: .getPdfObject(((PdfDictionary) item.merged.get(0))
1383: .get(PdfName.MAXLEN));
1384: int len = 0;
1385: if (maxLen != null)
1386: len = maxLen.intValue();
1387: if (len > 0)
1388: value = value.substring(0, Math
1389: .min(len, value.length()));
1390: }
1391: if (display == null)
1392: display = value;
1393: if (PdfName.TX.equals(type) || PdfName.CH.equals(type)) {
1394: PdfString v = new PdfString(value, PdfObject.TEXT_UNICODE);
1395: for (int idx = 0; idx < item.values.size(); ++idx) {
1396: PdfDictionary valueDic = (PdfDictionary) item.values
1397: .get(idx);
1398: valueDic.put(PdfName.V, v);
1399: valueDic.remove(PdfName.I);
1400: markUsed(valueDic);
1401: PdfDictionary merged = (PdfDictionary) item.merged
1402: .get(idx);
1403: merged.remove(PdfName.I);
1404: merged.put(PdfName.V, v);
1405: PdfDictionary widget = (PdfDictionary) item.widgets
1406: .get(idx);
1407: if (generateAppearances) {
1408: PdfAppearance app = getAppearance(merged, display,
1409: name);
1410: if (PdfName.CH.equals(type)) {
1411: PdfNumber n = new PdfNumber(topFirst);
1412: widget.put(PdfName.TI, n);
1413: merged.put(PdfName.TI, n);
1414: }
1415: PdfDictionary appDic = (PdfDictionary) PdfReader
1416: .getPdfObject(widget.get(PdfName.AP));
1417: if (appDic == null) {
1418: appDic = new PdfDictionary();
1419: widget.put(PdfName.AP, appDic);
1420: merged.put(PdfName.AP, appDic);
1421: }
1422: appDic.put(PdfName.N, app.getIndirectReference());
1423: writer.releaseTemplate(app);
1424: } else {
1425: widget.remove(PdfName.AP);
1426: merged.remove(PdfName.AP);
1427: }
1428: markUsed(widget);
1429: }
1430: return true;
1431: } else if (PdfName.BTN.equals(type)) {
1432: PdfNumber ff = (PdfNumber) PdfReader
1433: .getPdfObject(((PdfDictionary) item.merged.get(0))
1434: .get(PdfName.FF));
1435: int flags = 0;
1436: if (ff != null)
1437: flags = ff.intValue();
1438: if ((flags & PdfFormField.FF_PUSHBUTTON) != 0)
1439: return true;
1440: PdfName v = new PdfName(value);
1441: if ((flags & PdfFormField.FF_RADIO) == 0) {
1442: for (int idx = 0; idx < item.values.size(); ++idx) {
1443: ((PdfDictionary) item.values.get(idx)).put(
1444: PdfName.V, v);
1445: markUsed((PdfDictionary) item.values.get(idx));
1446: PdfDictionary merged = (PdfDictionary) item.merged
1447: .get(idx);
1448: merged.put(PdfName.V, v);
1449: merged.put(PdfName.AS, v);
1450: PdfDictionary widget = (PdfDictionary) item.widgets
1451: .get(idx);
1452: if (isInAP(widget, v))
1453: widget.put(PdfName.AS, v);
1454: else
1455: widget.put(PdfName.AS, PdfName.Off);
1456: markUsed(widget);
1457: }
1458: } else {
1459: ArrayList lopt = new ArrayList();
1460: PdfObject opts = PdfReader
1461: .getPdfObject(((PdfDictionary) item.values
1462: .get(0)).get(PdfName.OPT));
1463: if (opts != null && opts.isArray()) {
1464: ArrayList list = ((PdfArray) opts).getArrayList();
1465: for (int k = 0; k < list.size(); ++k) {
1466: PdfObject vv = PdfReader
1467: .getPdfObject((PdfObject) list.get(k));
1468: if (vv != null && vv.isString())
1469: lopt
1470: .add(((PdfString) vv)
1471: .toUnicodeString());
1472: else
1473: lopt.add(null);
1474: }
1475: }
1476: int vidx = lopt.indexOf(value);
1477: PdfName valt = null;
1478: PdfName vt;
1479: if (vidx >= 0) {
1480: vt = valt = new PdfName(String.valueOf(vidx));
1481: } else
1482: vt = v;
1483: for (int idx = 0; idx < item.values.size(); ++idx) {
1484: PdfDictionary merged = (PdfDictionary) item.merged
1485: .get(idx);
1486: PdfDictionary widget = (PdfDictionary) item.widgets
1487: .get(idx);
1488: markUsed((PdfDictionary) item.values.get(idx));
1489: if (valt != null) {
1490: PdfString ps = new PdfString(value,
1491: PdfObject.TEXT_UNICODE);
1492: ((PdfDictionary) item.values.get(idx)).put(
1493: PdfName.V, ps);
1494: merged.put(PdfName.V, ps);
1495: } else {
1496: ((PdfDictionary) item.values.get(idx)).put(
1497: PdfName.V, v);
1498: merged.put(PdfName.V, v);
1499: }
1500: markUsed(widget);
1501: if (isInAP(widget, vt)) {
1502: merged.put(PdfName.AS, vt);
1503: widget.put(PdfName.AS, vt);
1504: } else {
1505: merged.put(PdfName.AS, PdfName.Off);
1506: widget.put(PdfName.AS, PdfName.Off);
1507: }
1508: }
1509: }
1510: return true;
1511: }
1512: return false;
1513: }
1514:
1515: boolean isInAP(PdfDictionary dic, PdfName check) {
1516: PdfDictionary appDic = (PdfDictionary) PdfReader
1517: .getPdfObject(dic.get(PdfName.AP));
1518: if (appDic == null)
1519: return false;
1520: PdfDictionary NDic = (PdfDictionary) PdfReader
1521: .getPdfObject(appDic.get(PdfName.N));
1522: return (NDic != null && NDic.get(check) != null);
1523: }
1524:
1525: /** Gets all the fields. The fields are keyed by the fully qualified field name and
1526: * the value is an instance of <CODE>AcroFields.Item</CODE>.
1527: * @return all the fields
1528: */
1529: public HashMap getFields() {
1530: return fields;
1531: }
1532:
1533: /**
1534: * Gets the field structure.
1535: * @param name the name of the field
1536: * @return the field structure or <CODE>null</CODE> if the field
1537: * does not exist
1538: */
1539: public Item getFieldItem(String name) {
1540: if (xfa.isXfaPresent()) {
1541: name = xfa.findFieldName(name, this );
1542: if (name == null)
1543: return null;
1544: }
1545: return (Item) fields.get(name);
1546: }
1547:
1548: /**
1549: * Gets the long XFA translated name.
1550: * @param name the name of the field
1551: * @return the long field name
1552: */
1553: public String getTranslatedFieldName(String name) {
1554: if (xfa.isXfaPresent()) {
1555: String namex = xfa.findFieldName(name, this );
1556: if (namex != null)
1557: name = namex;
1558: }
1559: return name;
1560: }
1561:
1562: /**
1563: * Gets the field box positions in the document. The return is an array of <CODE>float</CODE>
1564: * multiple of 5. For each of this groups the values are: [page, llx, lly, urx,
1565: * ury]. The coordinates have the page rotation in consideration.
1566: * @param name the field name
1567: * @return the positions or <CODE>null</CODE> if field does not exist
1568: */
1569: public float[] getFieldPositions(String name) {
1570: Item item = getFieldItem(name);
1571: if (item == null)
1572: return null;
1573: float ret[] = new float[item.page.size() * 5];
1574: int ptr = 0;
1575: for (int k = 0; k < item.page.size(); ++k) {
1576: try {
1577: PdfDictionary wd = (PdfDictionary) item.widgets.get(k);
1578: PdfArray rect = (PdfArray) wd.get(PdfName.RECT);
1579: if (rect == null)
1580: continue;
1581: Rectangle r = PdfReader.getNormalizedRectangle(rect);
1582: int page = ((Integer) item.page.get(k)).intValue();
1583: int rotation = reader.getPageRotation(page);
1584: ret[ptr++] = page;
1585: if (rotation != 0) {
1586: Rectangle pageSize = reader.getPageSize(page);
1587: switch (rotation) {
1588: case 270:
1589: r = new Rectangle(pageSize.getTop()
1590: - r.getBottom(), r.getLeft(), pageSize
1591: .getTop()
1592: - r.getTop(), r.getRight());
1593: break;
1594: case 180:
1595: r = new Rectangle(pageSize.getRight()
1596: - r.getLeft(), pageSize.getTop()
1597: - r.getBottom(), pageSize.getRight()
1598: - r.getRight(), pageSize.getTop()
1599: - r.getTop());
1600: break;
1601: case 90:
1602: r = new Rectangle(r.getBottom(), pageSize
1603: .getRight()
1604: - r.getLeft(), r.getTop(), pageSize
1605: .getRight()
1606: - r.getRight());
1607: break;
1608: }
1609: r.normalize();
1610: }
1611: ret[ptr++] = r.getLeft();
1612: ret[ptr++] = r.getBottom();
1613: ret[ptr++] = r.getRight();
1614: ret[ptr++] = r.getTop();
1615: } catch (Exception e) {
1616: // empty on purpose
1617: }
1618: }
1619: if (ptr < ret.length) {
1620: float ret2[] = new float[ptr];
1621: System.arraycopy(ret, 0, ret2, 0, ptr);
1622: return ret2;
1623: }
1624: return ret;
1625: }
1626:
1627: private int removeRefFromArray(PdfArray array, PdfObject refo) {
1628: ArrayList ar = array.getArrayList();
1629: if (refo == null || !refo.isIndirect())
1630: return ar.size();
1631: PdfIndirectReference ref = (PdfIndirectReference) refo;
1632: for (int j = 0; j < ar.size(); ++j) {
1633: PdfObject obj = (PdfObject) ar.get(j);
1634: if (!obj.isIndirect())
1635: continue;
1636: if (((PdfIndirectReference) obj).getNumber() == ref
1637: .getNumber())
1638: ar.remove(j--);
1639: }
1640: return ar.size();
1641: }
1642:
1643: /**
1644: * Removes all the fields from <CODE>page</CODE>.
1645: * @param page the page to remove the fields from
1646: * @return <CODE>true</CODE> if any field was removed, <CODE>false otherwise</CODE>
1647: */
1648: public boolean removeFieldsFromPage(int page) {
1649: if (page < 1)
1650: return false;
1651: String names[] = new String[fields.size()];
1652: fields.keySet().toArray(names);
1653: boolean found = false;
1654: for (int k = 0; k < names.length; ++k) {
1655: boolean fr = removeField(names[k], page);
1656: found = (found || fr);
1657: }
1658: return found;
1659: }
1660:
1661: /**
1662: * Removes a field from the document. If page equals -1 all the fields with this
1663: * <CODE>name</CODE> are removed from the document otherwise only the fields in
1664: * that particular page are removed.
1665: * @param name the field name
1666: * @param page the page to remove the field from or -1 to remove it from all the pages
1667: * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE>
1668: */
1669: public boolean removeField(String name, int page) {
1670: Item item = getFieldItem(name);
1671: if (item == null)
1672: return false;
1673: PdfDictionary acroForm = (PdfDictionary) PdfReader
1674: .getPdfObject(
1675: reader.getCatalog().get(PdfName.ACROFORM),
1676: reader.getCatalog());
1677:
1678: if (acroForm == null)
1679: return false;
1680: PdfArray arrayf = (PdfArray) PdfReader.getPdfObject(acroForm
1681: .get(PdfName.FIELDS), acroForm);
1682: if (arrayf == null)
1683: return false;
1684: for (int k = 0; k < item.widget_refs.size(); ++k) {
1685: int pageV = ((Integer) item.page.get(k)).intValue();
1686: if (page != -1 && page != pageV)
1687: continue;
1688: PdfIndirectReference ref = (PdfIndirectReference) item.widget_refs
1689: .get(k);
1690: PdfDictionary wd = (PdfDictionary) PdfReader
1691: .getPdfObject(ref);
1692: PdfDictionary pageDic = reader.getPageN(pageV);
1693: PdfArray annots = (PdfArray) PdfReader.getPdfObject(pageDic
1694: .get(PdfName.ANNOTS), pageDic);
1695: if (annots != null) {
1696: if (removeRefFromArray(annots, ref) == 0) {
1697: pageDic.remove(PdfName.ANNOTS);
1698: markUsed(pageDic);
1699: } else
1700: markUsed(annots);
1701: }
1702: PdfReader.killIndirect(ref);
1703: PdfIndirectReference kid = ref;
1704: while ((ref = (PdfIndirectReference) wd.get(PdfName.PARENT)) != null) {
1705: wd = (PdfDictionary) PdfReader.getPdfObject(ref);
1706: PdfArray kids = (PdfArray) PdfReader.getPdfObject(wd
1707: .get(PdfName.KIDS));
1708: if (removeRefFromArray(kids, kid) != 0)
1709: break;
1710: kid = ref;
1711: PdfReader.killIndirect(ref);
1712: }
1713: if (ref == null) {
1714: removeRefFromArray(arrayf, kid);
1715: markUsed(arrayf);
1716: }
1717: if (page != -1) {
1718: item.merged.remove(k);
1719: item.page.remove(k);
1720: item.values.remove(k);
1721: item.widget_refs.remove(k);
1722: item.widgets.remove(k);
1723: --k;
1724: }
1725: }
1726: if (page == -1 || item.merged.size() == 0)
1727: fields.remove(name);
1728: return true;
1729: }
1730:
1731: /**
1732: * Removes a field from the document.
1733: * @param name the field name
1734: * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE>
1735: */
1736: public boolean removeField(String name) {
1737: return removeField(name, -1);
1738: }
1739:
1740: /** Gets the property generateAppearances.
1741: * @return the property generateAppearances
1742: */
1743: public boolean isGenerateAppearances() {
1744: return this .generateAppearances;
1745: }
1746:
1747: /** Sets the option to generate appearances. Not generating apperances
1748: * will speed-up form filling but the results can be
1749: * unexpected in Acrobat. Don't use it unless your environment is well
1750: * controlled. The default is <CODE>true</CODE>.
1751: * @param generateAppearances the option to generate appearances
1752: */
1753: public void setGenerateAppearances(boolean generateAppearances) {
1754: this .generateAppearances = generateAppearances;
1755: PdfDictionary top = (PdfDictionary) PdfReader
1756: .getPdfObject(reader.getCatalog().get(PdfName.ACROFORM));
1757: if (generateAppearances)
1758: top.remove(PdfName.NEEDAPPEARANCES);
1759: else
1760: top.put(PdfName.NEEDAPPEARANCES, PdfBoolean.PDFTRUE);
1761: }
1762:
1763: /** The field representations for retrieval and modification. */
1764: public static class Item {
1765: /** An array of <CODE>PdfDictionary</CODE> where the value tag /V
1766: * is present.
1767: */
1768: public ArrayList values = new ArrayList();
1769: /** An array of <CODE>PdfDictionary</CODE> with the widgets.
1770: */
1771: public ArrayList widgets = new ArrayList();
1772: /** An array of <CODE>PdfDictionary</CODE> with the widget references.
1773: */
1774: public ArrayList widget_refs = new ArrayList();
1775: /** An array of <CODE>PdfDictionary</CODE> with all the field
1776: * and widget tags merged.
1777: */
1778: public ArrayList merged = new ArrayList();
1779: /** An array of <CODE>Integer</CODE> with the page numbers where
1780: * the widgets are displayed.
1781: */
1782: public ArrayList page = new ArrayList();
1783: /** An array of <CODE>Integer</CODE> with the tab order of the field in the page.
1784: */
1785: public ArrayList tabOrder = new ArrayList();
1786: }
1787:
1788: private static class InstHit {
1789: IntHashtable hits;
1790:
1791: public InstHit(int inst[]) {
1792: if (inst == null)
1793: return;
1794: hits = new IntHashtable();
1795: for (int k = 0; k < inst.length; ++k)
1796: hits.put(inst[k], 1);
1797: }
1798:
1799: public boolean isHit(int n) {
1800: if (hits == null)
1801: return true;
1802: return hits.containsKey(n);
1803: }
1804: }
1805:
1806: /**
1807: * Gets the field names that have signatures and are signed.
1808: * @return the field names that have signatures and are signed
1809: */
1810: public ArrayList getSignatureNames() {
1811: if (sigNames != null)
1812: return new ArrayList(sigNames.keySet());
1813: sigNames = new HashMap();
1814: ArrayList sorter = new ArrayList();
1815: for (Iterator it = fields.entrySet().iterator(); it.hasNext();) {
1816: Map.Entry entry = (Map.Entry) it.next();
1817: Item item = (Item) entry.getValue();
1818: PdfDictionary merged = (PdfDictionary) item.merged.get(0);
1819: if (!PdfName.SIG.equals(merged.get(PdfName.FT)))
1820: continue;
1821: PdfObject vo = PdfReader
1822: .getPdfObject(merged.get(PdfName.V));
1823: if (vo == null || vo.type() != PdfObject.DICTIONARY)
1824: continue;
1825: PdfDictionary v = (PdfDictionary) vo;
1826: PdfObject contents = v.get(PdfName.CONTENTS);
1827: if (contents == null || contents.type() != PdfObject.STRING)
1828: continue;
1829: PdfObject ro = v.get(PdfName.BYTERANGE);
1830: if (ro == null || ro.type() != PdfObject.ARRAY)
1831: continue;
1832: ArrayList ra = ((PdfArray) ro).getArrayList();
1833: if (ra.size() < 2)
1834: continue;
1835: int length = ((PdfNumber) ra.get(ra.size() - 1)).intValue()
1836: + ((PdfNumber) ra.get(ra.size() - 2)).intValue();
1837: sorter.add(new Object[] { entry.getKey(),
1838: new int[] { length, 0 } });
1839: }
1840: Collections.sort(sorter, new AcroFields.SorterComparator());
1841: if (!sorter.isEmpty()) {
1842: if (((int[]) ((Object[]) sorter.get(sorter.size() - 1))[1])[0] == reader
1843: .getFileLength())
1844: totalRevisions = sorter.size();
1845: else
1846: totalRevisions = sorter.size() + 1;
1847: for (int k = 0; k < sorter.size(); ++k) {
1848: Object objs[] = (Object[]) sorter.get(k);
1849: String name = (String) objs[0];
1850: int p[] = (int[]) objs[1];
1851: p[1] = k + 1;
1852: sigNames.put(name, p);
1853: }
1854: }
1855: return new ArrayList(sigNames.keySet());
1856: }
1857:
1858: /**
1859: * Gets the field names that have blank signatures.
1860: * @return the field names that have blank signatures
1861: */
1862: public ArrayList getBlankSignatureNames() {
1863: getSignatureNames();
1864: ArrayList sigs = new ArrayList();
1865: for (Iterator it = fields.entrySet().iterator(); it.hasNext();) {
1866: Map.Entry entry = (Map.Entry) it.next();
1867: Item item = (Item) entry.getValue();
1868: PdfDictionary merged = (PdfDictionary) item.merged.get(0);
1869: if (!PdfName.SIG.equals(merged.get(PdfName.FT)))
1870: continue;
1871: if (sigNames.containsKey(entry.getKey()))
1872: continue;
1873: sigs.add(entry.getKey());
1874: }
1875: return sigs;
1876: }
1877:
1878: /**
1879: * Gets the signature dictionary, the one keyed by /V.
1880: * @param name the field name
1881: * @return the signature dictionary keyed by /V or <CODE>null</CODE> if the field is not
1882: * a signature
1883: */
1884: public PdfDictionary getSignatureDictionary(String name) {
1885: getSignatureNames();
1886: name = getTranslatedFieldName(name);
1887: if (!sigNames.containsKey(name))
1888: return null;
1889: Item item = (Item) fields.get(name);
1890: PdfDictionary merged = (PdfDictionary) item.merged.get(0);
1891: return (PdfDictionary) PdfReader.getPdfObject(merged
1892: .get(PdfName.V));
1893: }
1894:
1895: /**
1896: * Checks is the signature covers the entire document or just part of it.
1897: * @param name the signature field name
1898: * @return <CODE>true</CODE> if the signature covers the entire document,
1899: * <CODE>false</CODE> otherwise
1900: */
1901: public boolean signatureCoversWholeDocument(String name) {
1902: getSignatureNames();
1903: name = getTranslatedFieldName(name);
1904: if (!sigNames.containsKey(name))
1905: return false;
1906: return ((int[]) sigNames.get(name))[0] == reader
1907: .getFileLength();
1908: }
1909:
1910: /**
1911: * Verifies a signature. An example usage is:
1912: * <p>
1913: * <pre>
1914: * KeyStore kall = PdfPKCS7.loadCacertsKeyStore();
1915: * PdfReader reader = new PdfReader("my_signed_doc.pdf");
1916: * AcroFields af = reader.getAcroFields();
1917: * ArrayList names = af.getSignatureNames();
1918: * for (int k = 0; k < names.size(); ++k) {
1919: * String name = (String)names.get(k);
1920: * System.out.println("Signature name: " + name);
1921: * System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name));
1922: * PdfPKCS7 pk = af.verifySignature(name);
1923: * Calendar cal = pk.getSignDate();
1924: * Certificate pkc[] = pk.getCertificates();
1925: * System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate()));
1926: * System.out.println("Document modified: " + !pk.verify());
1927: * Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal);
1928: * if (fails == null)
1929: * System.out.println("Certificates verified against the KeyStore");
1930: * else
1931: * System.out.println("Certificate failed: " + fails[1]);
1932: * }
1933: * </pre>
1934: * @param name the signature field name
1935: * @return a <CODE>PdfPKCS7</CODE> class to continue the verification
1936: */
1937: public PdfPKCS7 verifySignature(String name) {
1938: return verifySignature(name, null);
1939: }
1940:
1941: /**
1942: * Verifies a signature. An example usage is:
1943: * <p>
1944: * <pre>
1945: * KeyStore kall = PdfPKCS7.loadCacertsKeyStore();
1946: * PdfReader reader = new PdfReader("my_signed_doc.pdf");
1947: * AcroFields af = reader.getAcroFields();
1948: * ArrayList names = af.getSignatureNames();
1949: * for (int k = 0; k < names.size(); ++k) {
1950: * String name = (String)names.get(k);
1951: * System.out.println("Signature name: " + name);
1952: * System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name));
1953: * PdfPKCS7 pk = af.verifySignature(name);
1954: * Calendar cal = pk.getSignDate();
1955: * Certificate pkc[] = pk.getCertificates();
1956: * System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate()));
1957: * System.out.println("Document modified: " + !pk.verify());
1958: * Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal);
1959: * if (fails == null)
1960: * System.out.println("Certificates verified against the KeyStore");
1961: * else
1962: * System.out.println("Certificate failed: " + fails[1]);
1963: * }
1964: * </pre>
1965: * @param name the signature field name
1966: * @param provider the provider or <code>null</code> for the default provider
1967: * @return a <CODE>PdfPKCS7</CODE> class to continue the verification
1968: */
1969: public PdfPKCS7 verifySignature(String name, String provider) {
1970: PdfDictionary v = getSignatureDictionary(name);
1971: if (v == null)
1972: return null;
1973: try {
1974: PdfName sub = (PdfName) PdfReader.getPdfObject(v
1975: .get(PdfName.SUBFILTER));
1976: PdfString contents = (PdfString) PdfReader.getPdfObject(v
1977: .get(PdfName.CONTENTS));
1978: PdfPKCS7 pk = null;
1979: if (sub.equals(PdfName.ADBE_X509_RSA_SHA1)) {
1980: PdfString cert = (PdfString) PdfReader.getPdfObject(v
1981: .get(PdfName.CERT));
1982: pk = new PdfPKCS7(contents.getOriginalBytes(), cert
1983: .getBytes(), provider);
1984: } else
1985: pk = new PdfPKCS7(contents.getOriginalBytes(), provider);
1986: updateByteRange(pk, v);
1987: PdfString str = (PdfString) PdfReader.getPdfObject(v
1988: .get(PdfName.M));
1989: if (str != null)
1990: pk.setSignDate(PdfDate.decode(str.toString()));
1991: PdfObject obj = PdfReader.getPdfObject(v.get(PdfName.NAME));
1992: if (obj != null) {
1993: if (obj.isString())
1994: pk.setSignName(((PdfString) obj).toUnicodeString());
1995: else if (obj.isName())
1996: pk.setSignName(PdfName.decodeName(obj.toString()));
1997: }
1998: str = (PdfString) PdfReader.getPdfObject(v
1999: .get(PdfName.REASON));
2000: if (str != null)
2001: pk.setReason(str.toUnicodeString());
2002: str = (PdfString) PdfReader.getPdfObject(v
2003: .get(PdfName.LOCATION));
2004: if (str != null)
2005: pk.setLocation(str.toUnicodeString());
2006: return pk;
2007: } catch (Exception e) {
2008: throw new ExceptionConverter(e);
2009: }
2010: }
2011:
2012: private void updateByteRange(PdfPKCS7 pkcs7, PdfDictionary v) {
2013: PdfArray b = (PdfArray) PdfReader.getPdfObject(v
2014: .get(PdfName.BYTERANGE));
2015: RandomAccessFileOrArray rf = reader.getSafeFile();
2016: try {
2017: rf.reOpen();
2018: byte buf[] = new byte[8192];
2019: ArrayList ar = b.getArrayList();
2020: for (int k = 0; k < ar.size(); ++k) {
2021: int start = ((PdfNumber) ar.get(k)).intValue();
2022: int length = ((PdfNumber) ar.get(++k)).intValue();
2023: rf.seek(start);
2024: while (length > 0) {
2025: int rd = rf.read(buf, 0, Math.min(length,
2026: buf.length));
2027: if (rd <= 0)
2028: break;
2029: length -= rd;
2030: pkcs7.update(buf, 0, rd);
2031: }
2032: }
2033: } catch (Exception e) {
2034: throw new ExceptionConverter(e);
2035: } finally {
2036: try {
2037: rf.close();
2038: } catch (Exception e) {
2039: }
2040: }
2041: }
2042:
2043: private void markUsed(PdfObject obj) {
2044: if (!append)
2045: return;
2046: ((PdfStamperImp) writer).markUsed(obj);
2047: }
2048:
2049: /**
2050: * Gets the total number of revisions this document has.
2051: * @return the total number of revisions
2052: */
2053: public int getTotalRevisions() {
2054: getSignatureNames();
2055: return this .totalRevisions;
2056: }
2057:
2058: /**
2059: * Gets this <CODE>field</CODE> revision.
2060: * @param field the signature field name
2061: * @return the revision or zero if it's not a signature field
2062: */
2063: public int getRevision(String field) {
2064: getSignatureNames();
2065: field = getTranslatedFieldName(field);
2066: if (!sigNames.containsKey(field))
2067: return 0;
2068: return ((int[]) sigNames.get(field))[1];
2069: }
2070:
2071: /**
2072: * Extracts a revision from the document.
2073: * @param field the signature field name
2074: * @return an <CODE>InputStream</CODE> covering the revision. Returns <CODE>null</CODE> if
2075: * it's not a signature field
2076: * @throws IOException on error
2077: */
2078: public InputStream extractRevision(String field) throws IOException {
2079: getSignatureNames();
2080: field = getTranslatedFieldName(field);
2081: if (!sigNames.containsKey(field))
2082: return null;
2083: int length = ((int[]) sigNames.get(field))[0];
2084: RandomAccessFileOrArray raf = reader.getSafeFile();
2085: raf.reOpen();
2086: raf.seek(0);
2087: return new RevisionStream(raf, length);
2088: }
2089:
2090: /**
2091: * Gets the appearances cache.
2092: * @return the appearances cache
2093: */
2094: public HashMap getFieldCache() {
2095: return this .fieldCache;
2096: }
2097:
2098: /**
2099: * Sets a cache for field appearances. Parsing the existing PDF to
2100: * create a new TextField is time expensive. For those tasks that repeatedly
2101: * fill the same PDF with different field values the use of the cache has dramatic
2102: * speed advantages. An example usage:
2103: * <p>
2104: * <pre>
2105: * String pdfFile = ...;// the pdf file used as template
2106: * ArrayList xfdfFiles = ...;// the xfdf file names
2107: * ArrayList pdfOutFiles = ...;// the output file names, one for each element in xpdfFiles
2108: * HashMap cache = new HashMap();// the appearances cache
2109: * PdfReader originalReader = new PdfReader(pdfFile);
2110: * for (int k = 0; k < xfdfFiles.size(); ++k) {
2111: * PdfReader reader = new PdfReader(originalReader);
2112: * XfdfReader xfdf = new XfdfReader((String)xfdfFiles.get(k));
2113: * PdfStamper stp = new PdfStamper(reader, new FileOutputStream((String)pdfOutFiles.get(k)));
2114: * AcroFields af = stp.getAcroFields();
2115: * af.setFieldCache(cache);
2116: * af.setFields(xfdf);
2117: * stp.close();
2118: * }
2119: * </pre>
2120: * @param fieldCache an HasMap that will carry the cached appearances
2121: */
2122: public void setFieldCache(HashMap fieldCache) {
2123: this .fieldCache = fieldCache;
2124: }
2125:
2126: /**
2127: * Sets extra margins in text fields to better mimic the Acrobat layout.
2128: * @param extraMarginLeft the extra marging left
2129: * @param extraMarginTop the extra margin top
2130: */
2131: public void setExtraMargin(float extraMarginLeft,
2132: float extraMarginTop) {
2133: this .extraMarginLeft = extraMarginLeft;
2134: this .extraMarginTop = extraMarginTop;
2135: }
2136:
2137: /**
2138: * Adds a substitution font to the list. The fonts in this list will be used if the original
2139: * font doesn't contain the needed glyphs.
2140: * @param font the font
2141: */
2142: public void addSubstitutionFont(BaseFont font) {
2143: if (substitutionFonts == null)
2144: substitutionFonts = new ArrayList();
2145: substitutionFonts.add(font);
2146: }
2147:
2148: private static final HashMap stdFieldFontNames = new HashMap();
2149:
2150: /**
2151: * Holds value of property totalRevisions.
2152: */
2153: private int totalRevisions;
2154:
2155: /**
2156: * Holds value of property fieldCache.
2157: */
2158: private HashMap fieldCache;
2159:
2160: static {
2161: stdFieldFontNames.put("CoBO",
2162: new String[] { "Courier-BoldOblique" });
2163: stdFieldFontNames.put("CoBo", new String[] { "Courier-Bold" });
2164: stdFieldFontNames.put("CoOb",
2165: new String[] { "Courier-Oblique" });
2166: stdFieldFontNames.put("Cour", new String[] { "Courier" });
2167: stdFieldFontNames.put("HeBO",
2168: new String[] { "Helvetica-BoldOblique" });
2169: stdFieldFontNames
2170: .put("HeBo", new String[] { "Helvetica-Bold" });
2171: stdFieldFontNames.put("HeOb",
2172: new String[] { "Helvetica-Oblique" });
2173: stdFieldFontNames.put("Helv", new String[] { "Helvetica" });
2174: stdFieldFontNames.put("Symb", new String[] { "Symbol" });
2175: stdFieldFontNames.put("TiBI",
2176: new String[] { "Times-BoldItalic" });
2177: stdFieldFontNames.put("TiBo", new String[] { "Times-Bold" });
2178: stdFieldFontNames.put("TiIt", new String[] { "Times-Italic" });
2179: stdFieldFontNames.put("TiRo", new String[] { "Times-Roman" });
2180: stdFieldFontNames.put("ZaDb", new String[] { "ZapfDingbats" });
2181: stdFieldFontNames.put("HySm", new String[] {
2182: "HYSMyeongJo-Medium", "UniKS-UCS2-H" });
2183: stdFieldFontNames.put("HyGo", new String[] { "HYGoThic-Medium",
2184: "UniKS-UCS2-H" });
2185: stdFieldFontNames.put("KaGo", new String[] { "HeiseiKakuGo-W5",
2186: "UniKS-UCS2-H" });
2187: stdFieldFontNames.put("KaMi", new String[] { "HeiseiMin-W3",
2188: "UniJIS-UCS2-H" });
2189: stdFieldFontNames.put("MHei", new String[] { "MHei-Medium",
2190: "UniCNS-UCS2-H" });
2191: stdFieldFontNames.put("MSun", new String[] { "MSung-Light",
2192: "UniCNS-UCS2-H" });
2193: stdFieldFontNames.put("STSo", new String[] { "STSong-Light",
2194: "UniGB-UCS2-H" });
2195: }
2196:
2197: private static class RevisionStream extends InputStream {
2198: private byte b[] = new byte[1];
2199: private RandomAccessFileOrArray raf;
2200: private int length;
2201: private int rangePosition = 0;
2202: private boolean closed;
2203:
2204: private RevisionStream(RandomAccessFileOrArray raf, int length) {
2205: this .raf = raf;
2206: this .length = length;
2207: }
2208:
2209: public int read() throws IOException {
2210: int n = read(b);
2211: if (n != 1)
2212: return -1;
2213: return b[0] & 0xff;
2214: }
2215:
2216: public int read(byte[] b, int off, int len) throws IOException {
2217: if (b == null) {
2218: throw new NullPointerException();
2219: } else if ((off < 0) || (off > b.length) || (len < 0)
2220: || ((off + len) > b.length) || ((off + len) < 0)) {
2221: throw new IndexOutOfBoundsException();
2222: } else if (len == 0) {
2223: return 0;
2224: }
2225: if (rangePosition >= length) {
2226: close();
2227: return -1;
2228: }
2229: int elen = Math.min(len, length - rangePosition);
2230: raf.readFully(b, off, elen);
2231: rangePosition += elen;
2232: return elen;
2233: }
2234:
2235: public void close() throws IOException {
2236: if (!closed) {
2237: raf.close();
2238: closed = true;
2239: }
2240: }
2241: }
2242:
2243: private static class SorterComparator implements Comparator {
2244: public int compare(Object o1, Object o2) {
2245: int n1 = ((int[]) ((Object[]) o1)[1])[0];
2246: int n2 = ((int[]) ((Object[]) o2)[1])[0];
2247: return n1 - n2;
2248: }
2249: }
2250:
2251: /**
2252: * 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
2253: * font doesn't contain the needed glyphs.
2254: * @return the list
2255: */
2256: public ArrayList getSubstitutionFonts() {
2257: return substitutionFonts;
2258: }
2259:
2260: /**
2261: * 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
2262: * font doesn't contain the needed glyphs.
2263: * @param substitutionFonts the list
2264: */
2265: public void setSubstitutionFonts(ArrayList substitutionFonts) {
2266: this .substitutionFonts = substitutionFonts;
2267: }
2268:
2269: /**
2270: * Gets the XFA form processor.
2271: * @return the XFA form processor
2272: */
2273: public XfaForm getXfa() {
2274: return xfa;
2275: }
2276:
2277: private static final PdfName[] buttonRemove = { PdfName.MK,
2278: PdfName.F, PdfName.FF, PdfName.Q, PdfName.BS,
2279: PdfName.BORDER };
2280:
2281: /**
2282: * Creates a new pushbutton from an existing field. This pushbutton can be changed and be used to replace
2283: * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton
2284: * call {@link #replacePushbuttonField(String,PdfFormField)}.
2285: * @param field the field name that should be a pushbutton
2286: * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton
2287: */
2288: public PushbuttonField getNewPushbuttonFromField(String field) {
2289: try {
2290: if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON)
2291: return null;
2292: float[] pos = getFieldPositions(field);
2293: Rectangle box = new Rectangle(pos[1], pos[2], pos[3],
2294: pos[4]);
2295: PushbuttonField newButton = new PushbuttonField(writer,
2296: box, null);
2297: Item item = getFieldItem(field);
2298: PdfDictionary dic = (PdfDictionary) item.merged.get(0);
2299: decodeGenericDictionary(dic, newButton);
2300: PdfDictionary mk = (PdfDictionary) PdfReader
2301: .getPdfObject(dic.get(PdfName.MK));
2302: if (mk != null) {
2303: PdfString text = (PdfString) PdfReader.getPdfObject(mk
2304: .get(PdfName.CA));
2305: if (text != null)
2306: newButton.setText(text.toUnicodeString());
2307: PdfNumber tp = (PdfNumber) PdfReader.getPdfObject(mk
2308: .get(PdfName.TP));
2309: if (tp != null)
2310: newButton.setLayout(tp.intValue() + 1);
2311: PdfDictionary ifit = (PdfDictionary) PdfReader
2312: .getPdfObject(mk.get(PdfName.IF));
2313: if (ifit != null) {
2314: PdfName sw = (PdfName) PdfReader.getPdfObject(ifit
2315: .get(PdfName.SW));
2316: if (sw != null) {
2317: int scale = PushbuttonField.SCALE_ICON_ALWAYS;
2318: if (sw.equals(PdfName.B))
2319: scale = PushbuttonField.SCALE_ICON_IS_TOO_BIG;
2320: else if (sw.equals(PdfName.S))
2321: scale = PushbuttonField.SCALE_ICON_IS_TOO_SMALL;
2322: else if (sw.equals(PdfName.N))
2323: scale = PushbuttonField.SCALE_ICON_NEVER;
2324: newButton.setScaleIcon(scale);
2325: }
2326: sw = (PdfName) PdfReader.getPdfObject(ifit
2327: .get(PdfName.S));
2328: if (sw != null) {
2329: if (sw.equals(PdfName.A))
2330: newButton.setProportionalIcon(false);
2331: }
2332: PdfArray aj = (PdfArray) PdfReader
2333: .getPdfObject(ifit.get(PdfName.A));
2334: if (aj != null && aj.size() == 2) {
2335: float left = ((PdfNumber) PdfReader
2336: .getPdfObject((PdfObject) aj
2337: .getArrayList().get(0)))
2338: .floatValue();
2339: float bottom = ((PdfNumber) PdfReader
2340: .getPdfObject((PdfObject) aj
2341: .getArrayList().get(1)))
2342: .floatValue();
2343: newButton.setIconHorizontalAdjustment(left);
2344: newButton.setIconVerticalAdjustment(bottom);
2345: }
2346: PdfObject fb = PdfReader.getPdfObject(ifit
2347: .get(PdfName.FB));
2348: if (fb != null && fb.toString().equals("true"))
2349: newButton.setIconFitToBounds(true);
2350: }
2351: PdfObject i = mk.get(PdfName.I);
2352: if (i != null && i.isIndirect())
2353: newButton.setIconReference((PRIndirectReference) i);
2354: }
2355: return newButton;
2356: } catch (Exception e) {
2357: throw new ExceptionConverter(e);
2358: }
2359: }
2360:
2361: /**
2362: * Replaces the field with a new pushbutton. The pushbutton can be created with
2363: * {@link #getNewPushbuttonFromField(String)} from the same document or it can be a
2364: * generic PdfFormField of the type pushbutton.
2365: * @param field the field name
2366: * @param button the <CODE>PdfFormField</CODE> representing the pushbutton
2367: * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field
2368: * was not a pushbutton
2369: */
2370: public boolean replacePushbuttonField(String field,
2371: PdfFormField button) {
2372: if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON)
2373: return false;
2374: Item item = getFieldItem(field);
2375: PdfDictionary merged = (PdfDictionary) item.merged.get(0);
2376: PdfDictionary values = (PdfDictionary) item.values.get(0);
2377: PdfDictionary widgets = (PdfDictionary) item.widgets.get(0);
2378: for (int k = 0; k < buttonRemove.length; ++k) {
2379: merged.remove(buttonRemove[k]);
2380: values.remove(buttonRemove[k]);
2381: widgets.remove(buttonRemove[k]);
2382: }
2383: for (Iterator it = button.getKeys().iterator(); it.hasNext();) {
2384: PdfName key = (PdfName) it.next();
2385: if (key.equals(PdfName.T) || key.equals(PdfName.RECT))
2386: continue;
2387: merged.put(key, button.get(key));
2388: widgets.put(key, button.get(key));
2389: }
2390: return true;
2391: }
2392: }
|