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