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 < 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 < 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 < 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 }