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